Builds: design 47 G §272, §356 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
74 lines
2.2 KiB
Go
74 lines
2.2 KiB
Go
package assigngraph
|
|
|
|
import (
|
|
"fmt"
|
|
"maps"
|
|
"slices"
|
|
"strings"
|
|
|
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshwants"
|
|
)
|
|
|
|
// Cycles are the cycles among the waits within one step (design 47 G §272, ADR 0656): an entry waits for its acting parts in the step an
|
|
// edge names, so two acting parts acting on each other in one step wait for each other for ever. Each is `<step>: a -> b -> a`, by
|
|
// node and module, sorted. Edges of two steps never close a cycle together: the indexer taking in the checker and the checker checking
|
|
// the indexer are none, and a holder's own inputs are no edge here (the graph gives an acting edge only from a part to the entries it
|
|
// acts on, never to what the part is built from).
|
|
func Cycles(entries []meshwants.AssignmentEntry) []string {
|
|
type key struct{ step, at string }
|
|
next := map[key][]string{}
|
|
steps := map[string]bool{}
|
|
for _, e := range entries {
|
|
for _, edge := range e.Edges {
|
|
if edge.Kind != EdgeActing {
|
|
continue
|
|
}
|
|
k := key{edge.Step, e.Node + "/" + e.Module}
|
|
next[k] = append(next[k], edge.Node+"/"+edge.Module)
|
|
steps[edge.Step] = true
|
|
}
|
|
}
|
|
var out []string
|
|
for _, step := range slices.Sorted(maps.Keys(steps)) {
|
|
const (
|
|
open = iota + 1
|
|
done
|
|
)
|
|
state := map[string]int{}
|
|
var path []string
|
|
var walk func(at string)
|
|
walk = func(at string) {
|
|
state[at] = open
|
|
path = append(path, at)
|
|
for _, to := range next[key{step, at}] {
|
|
switch state[to] {
|
|
case 0:
|
|
walk(to)
|
|
case open:
|
|
from := slices.Index(path, to)
|
|
cycle := slices.Clone(path[from:])
|
|
// start at the smallest member so one cycle is said once, whichever member is met first
|
|
lowest := slices.Index(cycle, slices.Min(cycle))
|
|
cycle = append(cycle[lowest:], cycle[:lowest]...)
|
|
out = append(out, fmt.Sprintf("%s: %s -> %s", step, strings.Join(cycle, " -> "), cycle[0]))
|
|
}
|
|
}
|
|
path = path[:len(path)-1]
|
|
state[at] = done
|
|
}
|
|
starts := map[string]bool{}
|
|
for k := range next {
|
|
if k.step == step {
|
|
starts[k.at] = true
|
|
}
|
|
}
|
|
for _, at := range slices.Sorted(maps.Keys(starts)) {
|
|
if state[at] == 0 {
|
|
walk(at)
|
|
}
|
|
}
|
|
}
|
|
slices.Sort(out)
|
|
return slices.Compact(out)
|
|
}
|