-
Notifications
You must be signed in to change notification settings - Fork 125
Graphs
Graphs (subgraphs, nets) in flow-based programming represent entities of more complex nature than components. FBP networks may have multi-level hierarchical structure. A graph consists of components, other graphs and connections between them. A graph which is a part of another higher level graph is called a Subgraph or a Composite Component. Top-level graphs represent entire programs or applications.
Flow-based programming proposes visual representation of graphs over textual. Each composite component has its own diagram but on a parent (higher level) diagram it is displayed like an ordinary component. An example diagram made with DrawFBP is shown on the following figure:
In GoFlow graph structure is defined at run-time using methods of flow.Graph structure. A minimal declaration of a new graph type looks like this:
type SimpleGraph struct {
flow.Graph
}
Apart from the required flow.Graph composite graphs may contain any other fields but GoFlow runtime doesn't take them into account.
One might ask: if most graphs have same struct type and all the graph nodes and connections are added in functions called at run time, why isn't there just a single Graph class for all the networks that are instantiated from it? The answer is methods. Definition of network structure and reaction of the network to special events is done in its methods and in Go methods are declared for specific types.
It is recommended to provide constructor functions for your networks and define (initial) network structure and ports there:
func NewSimpleNet() *SimpleGraph {
n := new(SimpleGraph)
n.InitGraphState() // required! allocates memory
// Graph nodes are created here
// ...
// Connections are made here
// ...
// Network ports are declared here
// ...
return n
}
Notice that any graph that you define needs to call InitGraphState() method before adding any contents, otherwise you will try to access unallocated memory.
New processes are added to network's graph using Add() method:
n.Add(new(ComponentType), "processName")
The first argument of the Add() method is the actual process object, the second argument is a string name which will be used to reference the process within the network.
After a process has been added to a network, it starts to "belong" to it. A process notifies its parent network when it starts or terminates. It can also reference its parent network and even modify its structure at run time.
Once processes have been added to a graph, they should be connected to each other using Connect() method:
n.Connect("senderProc", "senderPort", "receiverProc", "receiverPort", make(chan ElementType))
The first 2 arguments are the name of the sender process in the network and the name of its outport that sends data. Next 2 arguments are the name of the receiver process in the network and the name of its inport which receives data. The 5th argument creates a physical channel between them.
It is required that sender outport, receiver inport and the connection channel have the same underlying element type. Channel directions may be different (sender port can be send-only or bidirectional, receiver port can be receive-only or bidirectional, connection channel must be bidirectional), but the elements must be of the same type.
Network ports are different from component ports in GoFlow. While component ports are physical struct fields of channel type, net ports are virtual mappings. Each net port is mapped directly to a port of a contained process. Networks don't react to events on ports, they only route data between components and subgraphs.
To define an input or output port of a network, you need to map it to an appropriate input or outport of a contained process:
n.MapInPort("NetInportName", "processName", "processInport")
n.MapOutPort("NetOutportName", "processName", "processOutport")
A network knows how to connect its components and subgraphs using both physical and virtual ports. Normally you don't need to care about the difference. But if you want to use network's ports in top-level application, you can assign channels to them and use these channels to communicate with the network:
func main() {
// create the net
net := NewSimpleNet()
// make channels
in := make(chan InDataType)
out := make(chan OutDataType)
// connect to the net
net.SetInPort("NetInportName", in)
net.SetOutPort("NetOutportName", out)
// run the network
flow.RunNet(net)
// now you can use channels to communicate
in <- someValue
result := <-out
}
SetInPort() and SetOutPort() methods are used to assign real channels to network's ports.
As mentioned at the beginning of this article, networks can be nested within each other forming a hierarchical structure. Subgraphs are added to networks and connected just like other processes:
n.Add(NewSubgraphType(), "subnetProcessName")
n.Connect("proc1", "Out", "subnetProcessName", "In")
This feature allows you to build reusable composite components and design complex software in an easy way.