-
Notifications
You must be signed in to change notification settings - Fork 127
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 *goflow.Graph
structure. You don't need to create any specific types for your graphs.
Network processes, connections, and initial data (IIPs) are defined in the constructor function:
func NewSimpleNet() *SimpleGraph {
n := goflow.NewGraph()
// Graph nodes are created here
// ...
// Connections are made here
// ...
// Network ports are declared here
// ...
return n
}
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")
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.
By default a connection created in the above way is unbuffered. Such connections are blocking and cannot queue up the packets to be processed. You can specify a custom buffer size for a connection:
n.ConnectBuf("senderProc", "senderPort", "receiverProc", "receiverPort", bufferSize)
Where bufferSize is an integer. For unbuffered channels bufferSize == 0. The default buffer size used by Connect() method can be changed using GraphConfig.BufferSize
variable passed to NewGraph
function.
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
wait := goflow.Run(net)
// now you can use channels to communicate
in <- someValue
close(in)
result := <-out
<-wait
}
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.
Top-level networks are started with goflow.Run() call:
wait := flow.RunNet(net)
This call is non-blocking, so the caller routine continues to execute as soon as the network is initialized. Like components, networks must be passed by pointer.
The returned value is a channel that is closed when the network shuts down.