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  }