tree.go

v0.1.0
Doc Versions Source
1
package steward
2
3
import (
4
	"context"
5
6
	"github.com/dominikbraun/graph"
7
	"github.com/soverenio/vanilla/throw"
8
9
	"sourcecraft.dev/bigbes/auxilia/collect"
10
	"sourcecraft.dev/bigbes/auxilia/internal/graphutil"
11
	"sourcecraft.dev/bigbes/auxilia/internal/logutil"
12
)
13
14
type assetTreeNode struct {
15
	filled     bool
16
	isOverride bool
17
	position   int
18
}
19
20
func (n assetTreeNode) Empty() bool {
21
	return !n.filled
22
}
23
24
func getTreeNode(manager *Manager, svc *serviceAsset) assetTreeNode {
25
	for pos, override := range manager.overrides {
26
		if override == svc {
27
			return assetTreeNode{
28
				filled:     true,
29
				isOverride: true,
30
				position:   pos,
31
			}
32
		}
33
	}
34
35
	for pos, component := range manager.components {
36
		if component == svc {
37
			return assetTreeNode{
38
				filled:     true,
39
				isOverride: false,
40
				position:   pos,
41
			}
42
		}
43
	}
44
45
	return assetTreeNode{filled: false}
46
}
47
48
func nullHasher[T comparable](node T) T {
49
	return node
50
}
51
52
func (m *Manager) generateTree() graph.Graph[assetTreeNode, assetTreeNode] {
53
	tree := graph.New(nullHasher[assetTreeNode], graph.Tree(), graph.Directed())
54
55
	for i := range m.overrides {
56
		err := tree.AddVertex(assetTreeNode{
57
			isOverride: true,
58
			position:   i,
59
			filled:     true,
60
		})
61
62
		if err != nil {
63
			panic(throw.IllegalState())
64
		}
65
	}
66
67
	for i := range m.components {
68
		err := tree.AddVertex(assetTreeNode{
69
			isOverride: false,
70
			position:   i,
71
			filled:     true,
72
		})
73
74
		if err != nil {
75
			panic(throw.IllegalState())
76
		}
77
	}
78
79
	// TODO: rewrite dependency searches (dependency may be in parent), now it's continue
80
	for _, component := range append(m.overrides, m.components...) {
81
		componentID := getTreeNode(m, component)
82
		if componentID.Empty() {
83
			panic(throw.IllegalState())
84
		}
85
86
		for _, dependency := range component.dependencies {
87
			dependencyID := getTreeNode(m, dependency)
88
			if dependencyID.Empty() {
89
				continue
90
			}
91
92
			switch err := tree.AddEdge(componentID, dependencyID); err {
93
			case nil:
94
			case graph.ErrEdgeAlreadyExists:
95
			default:
96
				panic(throw.W(err, throw.IllegalState().Error()))
97
			}
98
		}
99
	}
100
101
	return tree
102
}
103
104
func assetFromNode(m *Manager, node assetTreeNode) *serviceAsset {
105
	if node.isOverride {
106
		return m.overrides[node.position]
107
	}
108
109
	return m.components[node.position]
110
}
111
112
func (m *Manager) buildExecutionOrder(ctx context.Context) ([]*serviceAsset, error) {
113
	tree := m.generateTree()
114
115
	for _, node := range append(m.overrides, m.components...) {
116
		switch {
117
		case node.dependents != nil:
118
			continue // has dependents
119
		case !node.root && node.ignoreUnused:
120
			continue // it's not root and it can be unused
121
		case !node.root:
122
			logutil.FromContext(ctx).Error("empty dependents asset without root option", "asset", node.Name())
123
124
			continue
125
		}
126
127
		if err := graph.DFS(tree, getTreeNode(m, node), func(node assetTreeNode) bool {
128
			asset := assetFromNode(m, node)
129
			asset.roots = append(asset.roots, node)
130
131
			return false
132
		}); err != nil {
133
			return nil, err
134
		}
135
	}
136
137
	ordered, err := graphutil.ChronologicalSort(tree)
138
	if err != nil {
139
		return nil, err
140
	}
141
142
	return collect.ConvertList(
143
		collect.FromSlice(ordered).Filter(func(node assetTreeNode) bool {
144
			asset := assetFromNode(m, node)
145
146
			if asset.roots == nil {
147
				if !asset.ignoreUnused {
148
					logutil.FromContext(ctx).Warn("skipping initialization of asset without root attachment", "asset", asset.Name())
149
				}
150
151
				return false
152
			}
153
			return true
154
		}),
155
		func(node assetTreeNode) *serviceAsset { return assetFromNode(m, node) },
156
	).Collect(), nil
157
}
158

Source Files