package dag
import (
"context"
"ina-fei/api/internal/config"
"ina-fei/api/internal/types"
"log"
"sync"
)
type Graph struct {
// nodes to run
Nodes []*Node
// where to fetch all node result
ResultGather chan *NodeResult
// adjoint table for this graph
cache map[string][]*Node
}
func NewGraph() *Graph {
return &Graph{ResultGather: make(chan *NodeResult), cache: make(map[string][]*Node)}
}
func (g *Graph) Add(node *Node) {
// add to adjoint table
// check if this node already be exists
// panic if exists
if _, ok := g.cache[node.Meta.Name]; ok == true {
panic("node already exists in this graph")
}
// add to adjoint table
g.cache[node.Meta.Name] = make([]*Node, 0)
// connect node to graph
node.ResultGather = g.ResultGather
//add to list prepare for run
g.Nodes = append(g.Nodes, node)
}
func (g *Graph) Connect(from, to *Node) {
// check both node exists in this graph
if _, ok := g.cache[from.Meta.Name]; ok != true {
panic("node not exists in this graph; call Add before connect")
}
if _, ok := g.cache[to.Meta.Name]; ok != true {
panic("node not exists in this graph; call Add before connect")
}
// check if already connected
for _, n := range g.cache[from.Meta.Name] {
if n.Meta.Name == to.Meta.Name {
log.Println("already connect, will do nothing")
return
}
}
// do connect
// modify adjoint table
g.cache[from.Meta.Name] = append(g.cache[from.Meta.Name], to)
// modify channel of both side node
c := make(chan *NodeResult)
from.OutputChannels = append(from.OutputChannels, c)
to.InputChannels = append(to.InputChannels, c)
log.Printf("connect from node %s to node %s", from.Meta.Name, to.Meta.Name)
}
func (g *Graph) findCycle() {
// inDegrees stores in degree count for every node
inDegrees := make(map[string]int)
// set up
for _, n := range g.Nodes {
// set current node
if _, ok := inDegrees[n.Meta.Name]; ok != true {
inDegrees[n.Meta.Name] = 0
}
// set successor of current node
successors := g.cache[n.Meta.Name]
for _, successor := range successors {
inDegrees[successor.Meta.Name] += 1
}
}
//
for len(inDegrees) != 0 {
readyNodes := make([]string, 0)
for k, v := range inDegrees {
if v == 0 {
readyNodes = append(readyNodes, k)
}
}
if len(readyNodes) == 0 {
panic("find cycle")
}
for _, readyNode := range readyNodes {
successorOfReadyNode := g.cache[readyNode]
for _, s := range successorOfReadyNode {
inDegrees[s.Meta.Name] -= 1
}
// delete ready node
delete(inDegrees, readyNode)
}
}
}
// total async
// check g.ResultGather
func (g *Graph) Run(ctx context.Context, config config.Config, req types.Request) {
// find cycle first
g.findCycle()
var wg sync.WaitGroup
for _, n := range g.Nodes {
wg.Add(1)
go func(n *Node) {
defer wg.Done()
n.Run(ctx, config, req)
}(n)
}
go func() {
wg.Wait()
close(g.ResultGather)
}()
}