github.com/egonelbre/exp@v0.0.0-20240430123955-ed1d3aa93911/fbp/graph.go (about) 1 package fbp 2 3 import ( 4 "bufio" 5 "bytes" 6 "errors" 7 "fmt" 8 "reflect" 9 "regexp" 10 "strings" 11 "sync/atomic" 12 ) 13 14 const BufferSize = 0 15 16 type Registry map[Type]MakeFn 17 type MakeFn func() Node 18 19 type Graph struct { 20 // Comm is used for input/output from the Graph 21 Comm interface{} 22 // Registry contains all the constructors for nodes 23 Registry Registry 24 // Nodes contains created node information 25 Nodes map[Name]Node 26 // Ports contains all tha ports 27 Ports map[string]*Port 28 } 29 30 type Node interface{} 31 32 type Runnable interface { 33 Run() error 34 } 35 36 type Port struct { 37 Name string 38 Chan reflect.Value 39 Refs int32 40 } 41 42 func NewPort(name string, v reflect.Value) *Port { return &Port{name, v, 0} } 43 44 func (c *Port) Acquire() { atomic.AddInt32(&c.Refs, 1) } 45 func (c *Port) Release() { 46 if atomic.AddInt32(&c.Refs, -1) == 0 { 47 c.Chan.Close() 48 } 49 } 50 51 func New(comm interface{}) *Graph { 52 return &Graph{ 53 Comm: comm, 54 55 Registry: make(Registry), 56 Nodes: make(map[Name]Node), 57 Ports: make(map[string]*Port), 58 } 59 } 60 61 // Setup is convenience for parsing the wiring and wiring up the graph 62 func (g *Graph) Setup(def string) error { 63 wiring, err := ParseWiring(def) 64 if err != nil { 65 return err 66 } 67 return g.WireUp(wiring) 68 } 69 70 func (g *Graph) WireUp(w *Wiring) error { 71 // we add comm as a node to the graph, this simplifies all the wiring 72 // I can use $ in the wiring and avoid multiple lookup mechanisms 73 g.Nodes["$"] = g.Comm 74 75 // create all the nodes 76 for name, typ := range w.Decls { 77 mk, ok := g.Registry[typ] 78 if !ok { 79 return fmt.Errorf("cannot create %s type %s does not exist", name, typ) 80 } 81 g.Nodes[name] = mk() 82 } 83 84 for _, wire := range w.Wires { 85 // lookup the source node 86 from, ok := g.Nodes[wire.From] 87 if !ok { 88 return fmt.Errorf("source node %s does not exist", wire.From) 89 } 90 91 // lookup the destination node 92 to, ok := g.Nodes[wire.To] 93 if !ok { 94 return fmt.Errorf("target node %s does not exist", wire.To) 95 } 96 97 // find the actual source node struct 98 rfrom := reflect.ValueOf(from) 99 // deref pointers 100 // we need to have the actual struct for the FieldByName to work 101 for rfrom.Kind() == reflect.Ptr { 102 rfrom = rfrom.Elem() 103 } 104 // same as previous, but for destination node 105 rto := reflect.ValueOf(to) 106 for rto.Kind() == reflect.Ptr { 107 rto = rto.Elem() 108 } 109 110 // find the channel fields from nodes 111 rsrc := rfrom.FieldByName(string(wire.Src)) 112 rdst := rto.FieldByName(string(wire.Dst)) 113 114 // sanity checks 115 switch { 116 case !rsrc.IsValid(): 117 return fmt.Errorf("source node %s does not have port %s", wire.From, wire.Src) 118 case rsrc.Kind() != reflect.Chan: 119 return fmt.Errorf("source %s.%s is not a chan", wire.From, wire.Src) 120 case !rdst.IsValid(): 121 return fmt.Errorf("target node %s does not have port %s", wire.To, wire.Dst) 122 case rdst.Kind() != reflect.Chan: 123 return fmt.Errorf("target %s.%s is not a chan", wire.To, wire.Dst) 124 } 125 126 // recreate the full port names 127 srcname := string(wire.From) + "." + string(wire.Src) 128 dstname := string(wire.To) + "." + string(wire.Dst) 129 130 var src, dst *Port 131 // have attached the channel to the node already? 132 if rsrc.IsNil() { 133 // create new channel with correct type 134 ch := reflect.MakeChan(rsrc.Type(), BufferSize) 135 // add it to the struct 136 rsrc.Set(ch) 137 // create a port for it 138 src = NewPort(srcname, ch) 139 // add it to graph 140 g.Ports[srcname] = src 141 } else { 142 // look up the port 143 src, ok = g.Ports[srcname] 144 // sanity check 145 if !ok { 146 panic("uninitialized src " + srcname) 147 } 148 } 149 150 // have attached the channel to the node already? 151 if rdst.IsNil() { 152 // create new channel with correct type 153 ch := reflect.MakeChan(rsrc.Type(), BufferSize) 154 // add it to the struct 155 rdst.Set(ch) 156 // create a port for it 157 dst = NewPort(dstname, ch) 158 // add it to graph 159 g.Ports[dstname] = dst 160 } else { 161 // look up the port 162 dst, ok = g.Ports[dstname] 163 // sanity check 164 if !ok { 165 panic("uninitialized dst " + dstname) 166 } 167 } 168 169 // we acquire the destination port 170 dst.Acquire() 171 172 // create a copying routine 173 go func() { 174 // when the source finishes we release the destination port 175 // this way when the counter hits 0 i.e. there are no more incoming 176 // values to the In port of a node then it can be closed 177 defer dst.Release() 178 for { 179 // pull out a value from the output of a node 180 v, ok := src.Chan.Recv() 181 if !ok { 182 return 183 } 184 // put it into result 185 dst.Chan.Send(v) 186 } 187 }() 188 } 189 return nil 190 } 191 192 // starts all the nodes 193 func (g *Graph) Start() { 194 for _, n := range g.Nodes { 195 r, ok := n.(Runnable) 196 if ok { 197 go func() { 198 //TODO: do something smarter with errors 199 if err := r.Run(); err != nil { 200 panic(err) 201 } 202 }() 203 } 204 } 205 } 206 207 func ParseWiring(def string) (*Wiring, error) { 208 wiring := &Wiring{Decls: make(map[Name]Type)} 209 210 // really stupid hacky parsing 211 rxDecl := regexp.MustCompile(`:\s+([$a-zA-Z]+)\s+([a-zA-Z]+)`) 212 rxPipe := regexp.MustCompile(`([\$a-zA-Z]+)\.([a-zA-Z]+)\s*->\s*([\$a-zA-Z]+)\.([a-zA-Z]+)`) 213 214 line := bufio.NewScanner(bytes.NewBufferString(def)) 215 for line.Scan() { 216 stmt := strings.TrimSpace(line.Text()) 217 if len(stmt) == 0 { 218 continue 219 } 220 221 if stmt[0] == ':' { 222 xs := rxDecl.FindAllStringSubmatch(stmt, -1) 223 if len(xs) != 1 { 224 return nil, errors.New("invalid line: " + stmt) 225 } 226 227 wiring.Decls[Name(xs[0][1])] = Type(xs[0][2]) 228 } else { 229 xs := rxPipe.FindAllStringSubmatch(stmt, -1) 230 if len(xs) != 1 { 231 return nil, errors.New("invalid line: " + stmt) 232 } 233 234 wiring.Wires = append(wiring.Wires, Wire{ 235 From: Name(xs[0][1]), 236 Src: PortName(xs[0][2]), 237 To: Name(xs[0][3]), 238 Dst: PortName(xs[0][4]), 239 }) 240 } 241 } 242 243 return wiring, nil 244 } 245 246 type Name string 247 type Type string 248 type PortName string 249 250 type Wiring struct { 251 Decls map[Name]Type 252 Wires []Wire 253 } 254 255 type Wire struct { 256 From Name 257 Src PortName 258 To Name 259 Dst PortName 260 }