├── .gitignore ├── README.md ├── config └── config.exs ├── doc ├── 404.html ├── ElixirFBP.Behaviour.html ├── ElixirFBP.Component.html ├── ElixirFBP.ComponentLoader.html ├── ElixirFBP.Graph.html ├── ElixirFBP.InitialInformationPacket.html ├── ElixirFBP.Network.html ├── ElixirFBP.Subscription.html ├── ElixirFBP.html ├── dist │ ├── app.css │ ├── app.js │ └── sidebar_items.js ├── extra-api-reference.html ├── fonts │ ├── icomoon.eot │ ├── icomoon.svg │ ├── icomoon.ttf │ └── icomoon.woff └── index.html ├── elixirFBP-0.0.1.tar ├── lib ├── elixirFBP.ex └── elixirFBP │ ├── behaviour.ex │ ├── component.ex │ ├── component_loader.ex │ ├── graph.ex │ ├── initial_information_packet.ex │ ├── network.ex │ └── subscription.ex ├── mix.exs ├── mix.lock ├── test ├── ElixirFBP_network_test.exs ├── elixirFBP_component_test.exs ├── elixirFBP_graph_test.exs └── test_helper.exs └── ttb_last_config /.gitignore: -------------------------------------------------------------------------------- 1 | /_build 2 | /deps 3 | erl_crash.dump 4 | *.ez 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ElixirFBP 2 | ========= 3 | 4 | This repository will contain an implementation of Flow-based Programming in the 5 | [Elixir language](http://elixir-lang.org). For more on FBP, see [Wikipedia](http://en.wikipedia.org/wiki/Flow-based_programming), 6 | [J. Paul Morrison](http://www.jpaulmorrison.com/fbp/), and [NoFlo](http://noflojs.org) 7 | 8 | This implementation is discussed [here](http://www.elixirfbp.org). 9 | 10 | # Description 11 | This Elixir implementation of an FBP system is influenced by the FBP Protocol as described at the NoFlo [website](http://noflojs.org/documentation/protocol/). These modules, however, can be used without regard to any particular runtime by using the Network and Graph modules directly. 12 | 13 | Note that an earlier release of this repository contained a runtime implementation 14 | that communicated, via websockets, with a noflo-ui client. The client could be running locally or remotely, using the on-line version at 15 | [app.flowhub.io](http:/app.flowhub.io). This code was refactored out and will appear in another repository. 16 | 17 | # Architecture 18 | ElixirFBP is made up of the following Elixir modules: 19 | * ElixirFBP.Network 20 | * ElixirFBP.Graph 21 | * ElixirFBP.Subscription 22 | 23 | The first two modules are implemented as Elixir [GenServers](http://elixir-lang.org/docs/stable/elixir/GenServer.html) 24 | 25 | ElixirFBP.Network 26 | keeps track of the FBP network that is currently being built and/or run. It 27 | knows how to handle FBP protocol network commands. 28 | 29 | ElixirFBP.Graph keeps track of the FBP graph that is currently being built and/or 30 | run. It knows how to handle FBP protocol graph commands. 31 | 32 | An ElixirFBP.Subscription serves as connection between any two components, hence 33 | there is one Subscription per FBP graph edge. The components correspond to the 34 | publisher and subscriber in the [Reactive Stream protocol](http://www.reactive-streams.org/). A Subscription can limit the number of Information Packets that can be sent from a 35 | publisher to a subscriber, that is, "back pressure" can be applied to the 36 | publisher. 37 | 38 | # Limitations 39 | * The components for this runtime are hard-wired in ElixirFBP. A "discovery" 40 | mechanism to locate Elixir components will be implemented in a future release. 41 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for third- 9 | # party users, it should be done in your mix.exs file. 10 | 11 | # Sample configuration: 12 | # 13 | # config :logger, :console, 14 | # level: :info, 15 | # format: "$date $time [$level] $metadata$message\n", 16 | # metadata: [:user_id] 17 | 18 | # It is also possible to import configuration files, relative to this 19 | # directory. For example, you can emulate configuration per environment 20 | # by uncommenting the line below and defining dev.exs, test.exs and such. 21 | # Configuration from the imported file will override the ones defined 22 | # here (which is why it is important to import them last). 23 | # 24 | # import_config "#{Mix.env}.exs" 25 | -------------------------------------------------------------------------------- /doc/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 404 – ElixirFBP v0.0.1 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 19 | 57 | 58 |
59 |
60 | 61 | 62 |

Page not found

63 | 64 |

Sorry, but the page you were trying to get to, does not exist. You 65 | may want to try searching this site using the sidebar or using our 66 | API Reference page to find what 67 | you were looking for.

68 | 69 | 81 |
82 |
83 |
84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /doc/ElixirFBP.Behaviour.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ElixirFBP.Behaviour – ElixirFBP v0.0.1 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 19 | 57 | 58 |
59 |
60 | 61 | 62 |

63 | ElixirFBP.Behaviour 64 | 65 | behaviour 66 | 67 | 68 | 69 | 70 | 71 | 72 |

73 | 74 | 75 | 76 | 77 |
78 |

79 | 80 | 81 | 82 | Summary 83 |

84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 |
92 |

93 | Callbacks 94 |

95 |
96 |
97 | description() 98 |
99 | 100 |
101 |
102 |
103 | inports() 104 |
105 | 106 |
107 |
108 |
109 | loop(%{}, %{}) 110 |
111 | 112 |
113 |
114 |
115 | outports() 116 |
117 | 118 |
119 | 120 |
121 | 122 | 123 |
124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 |
134 |

135 | 136 | 137 | 138 | Callbacks 139 |

140 |
141 |
142 | 143 | 144 | 145 | description() 146 | 147 | 148 | 149 | 150 | 151 |
152 | 153 |
154 |

Specs

155 |
156 | 157 |
description :: String.t
158 | 159 |
160 |
161 | 162 |
163 | 164 |
165 |
166 |
167 |
168 | 169 | 170 | 171 | inports() 172 | 173 | 174 | 175 | 176 | 177 |
178 | 179 |
180 |

Specs

181 |
182 | 183 |
inports :: [{:atom, atom}]
184 | 185 |
186 |
187 | 188 |
189 | 190 |
191 |
192 |
193 |
194 | 195 | 196 | 197 | loop(%{}, %{}) 198 | 199 | 200 | 201 | 202 | 203 |
204 | 205 |
206 |

Specs

207 |
208 | 209 |
loop(%{}, %{}) :: any
210 | 211 |
212 |
213 | 214 |
215 | 216 |
217 |
218 |
219 |
220 | 221 | 222 | 223 | outports() 224 | 225 | 226 | 227 | 228 | 229 |
230 | 231 |
232 |

Specs

233 |
234 | 235 |
outports :: [{:atom, atom}]
236 | 237 |
238 |
239 | 240 |
241 | 242 |
243 |
244 | 245 |
246 | 247 | 259 |
260 |
261 |
262 | 263 | 264 | 265 | 266 | -------------------------------------------------------------------------------- /doc/ElixirFBP.Component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ElixirFBP.Component – ElixirFBP v0.0.1 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 19 | 57 | 58 |
59 |
60 | 61 | 62 |

63 | ElixirFBP.Component 64 | 65 | 66 | 67 | 68 | 69 | 70 |

71 | 72 | 73 |
74 |

Component is used to start an FBP component. It takes the component’s name - 75 | an Elixir module name - and spawns a process. It is assumed that the component 76 | module supports a loop function which will be called once the component is 77 | spawned.

78 | 79 |
80 | 81 | 82 | 83 |
84 |

85 | 86 | 87 | 88 | Summary 89 |

90 | 91 | 92 | 93 |
94 |

95 | Functions 96 |

97 |
98 | 101 | 102 |

Start the execution of a component in its own process(es). Spawn as many 103 | processes as are specified in the no_of_processes value (the default value is one)

104 |
105 | 106 |
107 |
108 | 111 | 112 |

Stop a component. This means find the pid of all of the component’s processes, 113 | unregister it and force an exit

114 |
115 | 116 |
117 | 118 |
119 | 120 | 121 | 122 | 123 | 124 | 125 |
126 | 127 | 128 | 129 | 130 | 131 |
132 |

133 | 134 | 135 | 136 | Functions 137 |

138 |
139 |
140 | 141 | 142 | 143 | start(component, node_id, inports, outports, no_of_processes \\ 1) 144 | 145 | 146 | 147 | 148 | 149 |
150 | 151 |
152 |

Start the execution of a component in its own process(es). Spawn as many 153 | processes as are specified in the no_of_processes value (the default value is one).

154 |

inports is a list of {port, value} tuples where value is an initial value or 155 | a Subscription pid. outports is a list of {port, pid} tuples where the pid is 156 | a Subscription.

157 |

inports and outports are used to create the initial arguments sent to a 158 | Component’s loop function. They are also used to create lists of component 159 | pids that must be sent to any Subscriptions that a component’s in or out ports 160 | are connected to.

161 | 162 |
163 |
164 |
165 |
166 | 167 | 168 | 169 | stop(graph_reg_name, node_id, label) 170 | 171 | 172 | 173 | 174 | 175 |
176 | 177 |
178 |

Stop a component. This means find the pid of all of the component’s processes, 179 | unregister it and force an exit.

180 | 181 |
182 |
183 | 184 |
185 | 186 | 187 | 188 | 189 | 190 | 202 |
203 |
204 |
205 | 206 | 207 | 208 | 209 | -------------------------------------------------------------------------------- /doc/ElixirFBP.ComponentLoader.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ElixirFBP.ComponentLoader – ElixirFBP v0.0.1 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 19 | 57 | 58 |
59 |
60 | 61 | 62 |

63 | ElixirFBP.ComponentLoader 64 | 65 | 66 | 67 | 68 | 69 | 70 |

71 | 72 | 73 |
74 |

This module is responsible for loading ElixirFBP components at run time. Given 75 | a list of file paths, it will examine all modules, select those which implement 76 | the ElixirFBP.ComponentBehaviour and develop a list of Component attributes 77 | such as description, inports, and outports.

78 |

This code is based on the answer 79 | to a question about loading Elixir modules. The code is from the hex package 80 | manager.

81 | 82 |
83 | 84 | 85 | 86 |
87 |

88 | 89 | 90 | 91 | Summary 92 |

93 | 94 | 95 | 96 |
97 |

98 | Functions 99 |

100 |
101 | 104 | 105 |
106 |
107 |
108 | is_component?(module) 109 |
110 | 111 |
112 |
113 | 116 | 117 |
118 | 119 |
120 | 121 | 122 | 123 | 124 | 125 | 126 |
127 | 128 | 129 | 130 | 131 | 132 |
133 |

134 | 135 | 136 | 137 | Functions 138 |

139 |
140 |
141 | 142 | 143 | 144 | get_components(module_list) 145 | 146 | 147 | 148 | 149 | 150 |
151 | 152 |
153 | 154 |
155 |
156 |
157 |
158 | 159 | 160 | 161 | is_component?(module) 162 | 163 | 164 | 165 | 166 | 167 |
168 | 169 |
170 | 171 |
172 |
173 |
174 |
175 | 176 | 177 | 178 | retrieve_components(paths) 179 | 180 | 181 | 182 | 183 | 184 |
185 | 186 |
187 | 188 |
189 |
190 | 191 |
192 | 193 | 194 | 195 | 196 | 197 | 209 |
210 |
211 |
212 | 213 | 214 | 215 | 216 | -------------------------------------------------------------------------------- /doc/ElixirFBP.Graph.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ElixirFBP.Graph – ElixirFBP v0.0.1 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 19 | 57 | 58 |
59 |
60 | 61 | 62 |

63 | ElixirFBP.Graph 64 | 65 | 66 | 67 | 68 | 69 | 70 |

71 | 72 | 73 |
74 |

ElxirFBP.Graph is a GenServer that provides support for the creation and 75 | maintainance of an FBP Graph.

76 |

An FBP graph contains Nodes connected by Edges. Facilities are provided for 77 | the creation, deletion, and modification of nodes and edges. Initial Information 78 | Packets (IIP’s) can also be specified.

79 |

Graphs are implemented using Erlang’s digraph library.

80 |

The digraph Label associated with a node (digraph vertex) is 81 | [component, inports, inport_types, outports, outport_types, metadata] where 82 | component is the string name of a component e.g., “Math.Add”. inports 83 | and outports are lists of atomic name, initial value pairs, e.g., {:augend, 2} and 84 | inport_types and outport_types are lists of atomic name, type, e.g., {:augend, :integer}.

85 |

Initial values can be set using the add_initial graph command.

86 |

The digraph Label associated with an edge is [src.port,, tgt.port, metadata] where src.port 87 | and tgt.port are atom values for the component’s ports.

88 |

Functions supported by this module are based on NoFlo’s FBP Network Protocol, 89 | specifically the graph sub-protocol. See http://noflojs.org/documentation/protocol/ 90 | for the details.

91 |

TODO: Provide support for Port and Group maintenance. 92 | TODO: Use secret parameter 93 | TODO: Handle :digraph errors 94 | TODO: Metadata needs to be stored somewhere in add_initial()

95 | 96 |
97 | 98 | 99 | 100 |
101 |

102 | 103 | 104 | 105 | Summary 106 |

107 | 108 |
109 |

110 | Types 111 |

112 |
113 |
114 | t() 115 |
116 | 117 |
118 | 119 |
120 | 121 | 122 | 123 |
124 |

125 | Functions 126 |

127 |
128 | 131 | 132 |

Add an edge to the FBP Graph

133 |
134 | 135 |
136 |
137 | 140 | 141 |

Place an initial value at the port of a node in and FBP Graph

142 |
143 | 144 |
145 |
146 | 149 | 150 |

Add a node to the FBP Graph. Note the number of default processes is 1

151 |
152 | 153 |
154 |
155 | 158 | 159 |

Change an edge’s metadata in an FBP Graph

160 |
161 | 162 |
163 |
164 | 167 | 168 |

Change the metadata associated with a node in a graph

169 |
170 | 171 |
172 |
173 | 176 | 177 |

Return the current list of edges - primarily used for testing/debugging

178 |
179 | 180 |
181 |
182 | 185 | 186 |

Return the edges attached to a node

187 |
188 | 189 |
190 |
191 | 194 | 195 |

Retreive the FBP Graph structure - primarily used for testing/debugging

196 |
197 | 198 |
199 |
200 | 203 | 204 |

Return info about a node

205 |
206 | 207 |
208 |
209 | 212 | 213 |

Return the status variables for this graph

214 |
215 | 216 |
217 |
218 | 221 | 222 |

Get the subscription pid for this edge

223 |
224 | 225 |
226 |
227 | 230 | 231 |

Callback implementation for stopping this GenServer

232 |
233 | 234 |
235 |
236 |
237 | init(args) 238 |
239 | 240 |

Callback implementation for ElixirFBP.Graph.clear() 241 | Create and initialize the FBP Graph Structure which becomes the State

242 |
243 | 244 |
245 |
246 | 249 | 250 |

Return the current list of nodes

251 |
252 | 253 |
254 |
255 | 258 | 259 |

Remove the edge between the two given node/ports in an FBP Graph

260 |
261 | 262 |
263 |
264 | 267 | 268 |

Remove an initial value at the port of a node in the FBP Graph. It is set to 269 | the value nil

270 |
271 | 272 |
273 |
274 | 277 | 278 |

Remove a node from an FBP Graph

279 |
280 | 281 |
282 |
283 | 286 | 287 |

Rename a node in an FBP Graph

288 |
289 | 290 |
291 |
292 | 295 | 296 |

Set the parameters associated with this graph

297 |
298 | 299 |
300 |
301 | 304 | 305 |

Start the execution of the components in this graph. Optionally supplying 306 | a run mode of :pull or :push (default)

307 |
308 | 309 |
310 |
311 | 314 | 315 |

Starts things off with the creation of the state. Register it with the name 316 | graph_id - converted to an atom

317 |
318 | 319 |
320 |
321 | 324 | 325 |

Stop this GenServer

326 |
327 | 328 |
329 |
330 | 333 | 334 |

Stop the execution of the components in this graph. 335 | This should normally be called via the Network.stop(graph_id) function

336 |
337 | 338 |
339 |
340 | 343 | 344 |

Callback implementation triggered by asking the GenServer to :stop

345 |
346 | 347 |
348 |
349 | 352 | 353 |
354 | 355 |
356 | 357 | 358 | 359 | 360 | 361 | 362 |
363 | 364 | 365 | 366 |
367 |

368 | 369 | 370 | 371 | Types 372 |

373 |
374 |
375 |
t :: %ElixirFBP.Graph{id: String.t, name: String.t, library: module, main: boolean, icon: String.t, description: String.t, registered_name: atom, running: boolean, started: boolean, graph: atom}
376 | 377 |
378 | 379 |
380 |
381 | 382 | 383 | 384 |
385 |

386 | 387 | 388 | 389 | Functions 390 |

391 |
392 |
393 | 394 | 395 | 396 | add_edge(fbp_graph_reg_name, src_node_id, src_port, tgt_node_id, tgt_port, metadata \\ %{}) 397 | 398 | 399 | 400 | 401 | 402 |
403 | 404 |
405 |

Add an edge to the FBP Graph

406 | 407 |
408 |
409 |
410 |
411 | 412 | 413 | 414 | add_initial(fbp_graph_reg_name, data, node_id, port, metadata \\ %{}) 415 | 416 | 417 | 418 | 419 | 420 |
421 | 422 |
423 |

Place an initial value at the port of a node in and FBP Graph

424 | 425 |
426 |
427 |
428 |
429 | 430 | 431 | 432 | add_node(fbp_graph_reg_name, node_id, component, metadata \\ %{}) 433 | 434 | 435 | 436 | 437 | 438 |
439 | 440 |
441 |

Add a node to the FBP Graph. Note the number of default processes is 1.

442 | 443 |
444 |
445 |
446 |
447 | 448 | 449 | 450 | change_edge(fbp_graph_reg_name, src_node_id, src_port, tgt_node_id, tgt_port, metadata, secret) 451 | 452 | 453 | 454 | 455 | 456 |
457 | 458 |
459 |

Change an edge’s metadata in an FBP Graph

460 | 461 |
462 |
463 |
464 |
465 | 466 | 467 | 468 | change_node(fbp_graph_reg_name, node_id, metadata, secret) 469 | 470 | 471 | 472 | 473 | 474 |
475 | 476 |
477 |

Change the metadata associated with a node in a graph

478 | 479 |
480 |
481 |
482 |
483 | 484 | 485 | 486 | edges(fbp_graph_reg_name) 487 | 488 | 489 | 490 | 491 | 492 |
493 | 494 |
495 |

Return the current list of edges - primarily used for testing/debugging.

496 | 497 |
498 |
499 |
500 |
501 | 502 | 503 | 504 | edges(fbp_graph_reg_name, node_id) 505 | 506 | 507 | 508 | 509 | 510 |
511 | 512 |
513 |

Return the edges attached to a node

514 | 515 |
516 |
517 |
518 |
519 | 520 | 521 | 522 | get(fbp_graph_reg_name) 523 | 524 | 525 | 526 | 527 | 528 |
529 | 530 |
531 |

Retreive the FBP Graph structure - primarily used for testing/debugging

532 | 533 |
534 |
535 |
536 |
537 | 538 | 539 | 540 | get_node(fbp_graph_reg_name, node_id) 541 | 542 | 543 | 544 | 545 | 546 |
547 | 548 |
549 |

Return info about a node.

550 | 551 |
552 |
553 |
554 |
555 | 556 | 557 | 558 | get_status(fbp_graph_reg_name) 559 | 560 | 561 | 562 | 563 | 564 |
565 | 566 |
567 |

Return the status variables for this graph

568 | 569 |
570 |
571 |
572 |
573 | 574 | 575 | 576 | get_subscription(fbp_graph_reg_name, src_node_id, src_port, tgt_node_id, tgt_port) 577 | 578 | 579 | 580 | 581 | 582 |
583 | 584 |
585 |

Get the subscription pid for this edge.

586 | 587 |
588 |
589 |
590 |
591 | 592 | 593 | 594 | handle_call(msg, arg2, state) 595 | 596 | 597 | 598 | 599 | 600 |
601 | 602 |
603 |

Callback implementation for stopping this GenServer.

604 | 605 |
606 |
607 |
608 |
609 | 610 | 611 | 612 | init(args) 613 | 614 | 615 | 616 | 617 | 618 |
619 | 620 |
621 |

Callback implementation for ElixirFBP.Graph.clear() 622 | Create and initialize the FBP Graph Structure which becomes the State

623 | 624 |
625 |
626 |
627 |
628 | 629 | 630 | 631 | nodes(fbp_graph_reg_name) 632 | 633 | 634 | 635 | 636 | 637 |
638 | 639 |
640 |

Return the current list of nodes

641 | 642 |
643 |
644 |
645 |
646 | 647 | 648 | 649 | remove_edge(fbp_graph_reg_name, src_node_id, src_port, tgt_node_id, tgt_port) 650 | 651 | 652 | 653 | 654 | 655 |
656 | 657 |
658 |

Remove the edge between the two given node/ports in an FBP Graph

659 | 660 |
661 |
662 |
663 |
664 | 665 | 666 | 667 | remove_initial(fbp_graph_reg_name, node_id, port, secret) 668 | 669 | 670 | 671 | 672 | 673 |
674 | 675 |
676 |

Remove an initial value at the port of a node in the FBP Graph. It is set to 677 | the value nil.

678 | 679 |
680 |
681 |
682 |
683 | 684 | 685 | 686 | remove_node(fbp_graph_reg_name, node_id) 687 | 688 | 689 | 690 | 691 | 692 |
693 | 694 |
695 |

Remove a node from an FBP Graph

696 | 697 |
698 |
699 |
700 |
701 | 702 | 703 | 704 | rename_node(fbp_graph_reg_name, from, to, secret) 705 | 706 | 707 | 708 | 709 | 710 |
711 | 712 |
713 |

Rename a node in an FBP Graph

714 | 715 |
716 |
717 |
718 |
719 | 720 | 721 | 722 | set_parameters(fbp_graph_reg_name, parameters \\ %{}) 723 | 724 | 725 | 726 | 727 | 728 |
729 | 730 |
731 |

Set the parameters associated with this graph

732 | 733 |
734 |
735 |
736 |
737 | 738 | 739 | 740 | start(fbp_graph_reg_name, run_mode \\ :push) 741 | 742 | 743 | 744 | 745 | 746 |
747 | 748 |
749 |

Start the execution of the components in this graph. Optionally supplying 750 | a run mode of :pull or :push (default)

751 | 752 |
753 |
754 |
755 |
756 | 757 | 758 | 759 | start_link(graph_id, parameters \\ %{}) 760 | 761 | 762 | 763 | 764 | 765 |
766 | 767 |
768 |

Starts things off with the creation of the state. Register it with the name 769 | graph_id - converted to an atom.

770 | 771 |
772 |
773 |
774 |
775 | 776 | 777 | 778 | stop(fbp_graph_reg_name) 779 | 780 | 781 | 782 | 783 | 784 |
785 | 786 |
787 |

Stop this GenServer

788 | 789 |
790 |
791 |
792 |
793 | 794 | 795 | 796 | stop_graph(fbp_graph_reg_name) 797 | 798 | 799 | 800 | 801 | 802 |
803 | 804 |
805 |

Stop the execution of the components in this graph. 806 | This should normally be called via the Network.stop(graph_id) function.

807 | 808 |
809 |
810 |
811 |
812 | 813 | 814 | 815 | terminate(reason, arg2) 816 | 817 | 818 | 819 | 820 | 821 |
822 | 823 |
824 |

Callback implementation triggered by asking the GenServer to :stop

825 | 826 |
827 |
828 |
829 |
830 | 831 | 832 | 833 | verify_out_ports(graph) 834 | 835 | 836 | 837 | 838 | 839 |
840 | 841 |
842 | 843 |
844 |
845 | 846 |
847 | 848 | 849 | 850 | 851 | 852 | 864 |
865 |
866 |
867 | 868 | 869 | 870 | 871 | -------------------------------------------------------------------------------- /doc/ElixirFBP.InitialInformationPacket.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ElixirFBP.InitialInformationPacket – ElixirFBP v0.0.1 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 19 | 57 | 58 |
59 |
60 | 61 | 62 |

63 | ElixirFBP.InitialInformationPacket 64 | 65 | 66 | 67 | 68 | 69 | 70 |

71 | 72 | 73 |
74 |

A special component that can deliver a constant anytime it is sent 75 | a :value message.

76 | 77 |
78 | 79 | 80 | 81 |
82 |

83 | 84 | 85 | 86 | Summary 87 |

88 | 89 | 90 | 91 |
92 |

93 | Functions 94 |

95 |
96 |
97 | description() 98 |
99 | 100 |

Callback implementation for ElixirFBP.Behaviour.description/0

101 |
102 | 103 |
104 |
105 |
106 | inports() 107 |
108 | 109 |

Callback implementation for ElixirFBP.Behaviour.inports/0

110 |
111 | 112 |
113 |
114 | 117 | 118 |

Callback implementation for ElixirFBP.Behaviour.loop/2

119 |
120 | 121 |
122 |
123 |
124 | outports() 125 |
126 | 127 |

Callback implementation for ElixirFBP.Behaviour.outports/0

128 |
129 | 130 |
131 | 132 |
133 | 134 | 135 | 136 | 137 | 138 | 139 |
140 | 141 | 142 | 143 | 144 | 145 |
146 |

147 | 148 | 149 | 150 | Functions 151 |

152 |
153 |
154 | 155 | 156 | 157 | description() 158 | 159 | 160 | 161 | 162 | 163 |
164 | 165 |
166 |

Callback implementation for ElixirFBP.Behaviour.description/0.

167 | 168 |
169 |
170 |
171 |
172 | 173 | 174 | 175 | inports() 176 | 177 | 178 | 179 | 180 | 181 |
182 | 183 |
184 |

Callback implementation for ElixirFBP.Behaviour.inports/0.

185 | 186 |
187 |
188 |
189 |
190 | 191 | 192 | 193 | loop(inports, outports) 194 | 195 | 196 | 197 | 198 | 199 |
200 | 201 |
202 |

Callback implementation for ElixirFBP.Behaviour.loop/2.

203 | 204 |
205 |
206 |
207 |
208 | 209 | 210 | 211 | outports() 212 | 213 | 214 | 215 | 216 | 217 |
218 | 219 |
220 |

Callback implementation for ElixirFBP.Behaviour.outports/0.

221 | 222 |
223 |
224 | 225 |
226 | 227 | 228 | 229 | 230 | 231 | 243 |
244 |
245 |
246 | 247 | 248 | 249 | 250 | -------------------------------------------------------------------------------- /doc/ElixirFBP.Network.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ElixirFBP.Network – ElixirFBP v0.0.1 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 19 | 57 | 58 |
59 |
60 | 61 | 62 |

63 | ElixirFBP.Network 64 | 65 | 66 | 67 | 68 | 69 | 70 |

71 | 72 | 73 |
74 |

ElxirFBP.Network is a GenServer that provides support for starting and stopping a 75 | FBP network, and finding out about its state. The network keeps 76 | a dictionary of graphs ids, each of which points to an ElixirFBP.Graph structure. 77 | Graphs are also implemented as GenServers

78 |

Functions supported by this module are based on NoFlo’s FBP Network Protocol, 79 | specifically the network sub-protocol. See http://noflojs.org/documentation/protocol/ 80 | for the details. There is one exception: the clear graph command is implemented here.

81 |

There is a function - remove_graph - that is not part of the Network Protocol.

82 |

This module is registered with its module name.

83 |

TODO: Finish implementation of data function

84 | 85 |
86 | 87 | 88 | 89 |
90 |

91 | 92 | 93 | 94 | Summary 95 |

96 | 97 | 98 | 99 |
100 |

101 | Functions 102 |

103 |
104 | 107 | 108 |

Clear adds a new graph in the network. This command is part of the Graph protocol 109 | but here it is implemented as a network command because it seems like a better fit

110 |
111 | 112 |
113 |
114 | 117 | 118 |

Data transmission on an edge

119 |
120 | 121 |
122 |
123 |
124 | get_graph(graph_id) 125 |
126 | 127 |

Retrieve the registered graph name if it exists. A call to clear will have created 128 | and registered it

129 |
130 | 131 |
132 |
133 | 136 | 137 |

Get the current status of a graph

138 |
139 | 140 |
141 |
142 | 145 | 146 |

Callback implementation for ElixirFBP.Network.stop() 147 | Stop the execution of a graph identified by its id (string)

148 |
149 | 150 |
151 |
152 | 155 | 156 |

Callback implementation for ElixirFBP.Network.data

157 |
158 | 159 |
160 |
161 |
162 | init(args) 163 |
164 | 165 |

Callback implementation for ElixirFBP.Network.start_link() 166 | Initialize the state - no graphs. There are no initialization args

167 |
168 | 169 |
170 |
171 | 174 | 175 |

Remove a graph from this network

176 |
177 | 178 |
179 |
180 |
181 | set_debug(value) 182 |
183 | 184 |

Set the network debug switch on or off

185 |
186 | 187 |
188 |
189 | 192 | 193 |

Start execution of a graph, optionally specifing whether it is to 194 | run in either :pull or :push (default) mode

195 |
196 | 197 |
198 |
199 |
200 | start_link() 201 |
202 | 203 |

Starts things off with the creation of the empty state

204 |
205 | 206 |
207 |
208 |
209 | stop() 210 |
211 | 212 |

Stop the Network GenServer process

213 |
214 | 215 |
216 |
217 |
218 | stop(graph_id) 219 |
220 | 221 |

Stop the execution of the graph

222 |
223 | 224 |
225 |
226 | 229 | 230 |

Callback implmentation for having asked the GenServer to stop processing

231 |
232 | 233 |
234 | 235 |
236 | 237 | 238 | 239 | 240 | 241 | 242 |
243 | 244 | 245 | 246 | 247 | 248 |
249 |

250 | 251 | 252 | 253 | Functions 254 |

255 |
256 |
257 | 258 | 259 | 260 | clear(graph_id, parameters \\ %{}) 261 | 262 | 263 | 264 | 265 | 266 |
267 | 268 |
269 |

Clear adds a new graph in the network. This command is part of the Graph protocol 270 | but here it is implemented as a network command because it seems like a better fit.

271 | 272 |
273 |
274 |
275 |
276 | 277 | 278 | 279 | data(graph_id, edge_id, src, tgt, subgraph \\ nil) 280 | 281 | 282 | 283 | 284 | 285 |
286 | 287 |
288 |

Data transmission on an edge.

289 | 290 |
291 |
292 |
293 |
294 | 295 | 296 | 297 | get_graph(graph_id) 298 | 299 | 300 | 301 | 302 | 303 |
304 | 305 |
306 |

Retrieve the registered graph name if it exists. A call to clear will have created 307 | and registered it.

308 | 309 |
310 |
311 |
312 |
313 | 314 | 315 | 316 | get_status(graph_id, secret \\ nil) 317 | 318 | 319 | 320 | 321 | 322 |
323 | 324 |
325 |

Get the current status of a graph

326 | 327 |
328 |
329 |
330 |
331 | 332 | 333 | 334 | handle_call(msg, arg2, state) 335 | 336 | 337 | 338 | 339 | 340 |
341 | 342 |
343 |

Callback implementation for ElixirFBP.Network.stop() 344 | Stop the execution of a graph identified by its id (string).

345 | 346 |
347 |
348 |
349 |
350 | 351 | 352 | 353 | handle_cast(msg, state) 354 | 355 | 356 | 357 | 358 | 359 |
360 | 361 |
362 |

Callback implementation for ElixirFBP.Network.data

363 | 364 |
365 |
366 |
367 |
368 | 369 | 370 | 371 | init(args) 372 | 373 | 374 | 375 | 376 | 377 |
378 | 379 |
380 |

Callback implementation for ElixirFBP.Network.start_link() 381 | Initialize the state - no graphs. There are no initialization args.

382 | 383 |
384 |
385 |
386 |
387 | 388 | 389 | 390 | remove_graph(graph_id) 391 | 392 | 393 | 394 | 395 | 396 |
397 | 398 |
399 |

Remove a graph from this network.

400 | 401 |
402 |
403 |
404 |
405 | 406 | 407 | 408 | set_debug(value) 409 | 410 | 411 | 412 | 413 | 414 |
415 | 416 |
417 |

Set the network debug switch on or off

418 | 419 |
420 |
421 |
422 |
423 | 424 | 425 | 426 | start(graph_id, run_mode \\ :push) 427 | 428 | 429 | 430 | 431 | 432 |
433 | 434 |
435 |

Start execution of a graph, optionally specifing whether it is to 436 | run in either :pull or :push (default) mode.

437 | 438 |
439 |
440 |
441 |
442 | 443 | 444 | 445 | start_link() 446 | 447 | 448 | 449 | 450 | 451 |
452 | 453 |
454 |

Starts things off with the creation of the empty state.

455 | 456 |
457 |
458 |
459 |
460 | 461 | 462 | 463 | stop() 464 | 465 | 466 | 467 | 468 | 469 |
470 | 471 |
472 |

Stop the Network GenServer process

473 | 474 |
475 |
476 |
477 |
478 | 479 | 480 | 481 | stop(graph_id) 482 | 483 | 484 | 485 | 486 | 487 |
488 | 489 |
490 |

Stop the execution of the graph

491 | 492 |
493 |
494 |
495 |
496 | 497 | 498 | 499 | terminate(reason, network) 500 | 501 | 502 | 503 | 504 | 505 |
506 | 507 |
508 |

Callback implmentation for having asked the GenServer to stop processing

509 | 510 |
511 |
512 | 513 |
514 | 515 | 516 | 517 | 518 | 519 | 531 |
532 |
533 |
534 | 535 | 536 | 537 | 538 | -------------------------------------------------------------------------------- /doc/ElixirFBP.Subscription.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ElixirFBP.Subscription – ElixirFBP v0.0.1 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 19 | 57 | 58 |
59 |
60 | 61 | 62 |

63 | ElixirFBP.Subscription 64 | 65 | 66 | 67 | 68 | 69 | 70 |

71 | 72 | 73 |
74 |

A Subscription serves as a conduit and a control mechanism for the delivery of 75 | data from Publishers (Components) to Subscribers (Components). A Subscriber 76 | must specify how many IPs it is willing to receive via a {:request, n} message 77 | where n is some integer or the atom :infinity. The Subscription will ensure that 78 | no more IPs than have been asked for will be sent.

79 |

When a subscriber asks for an infinite number of values via a {:request, :infinity} 80 | message, the Subscription effectively becoming a push flow without any back pressure.

81 |

A Subscription is able to deal with multiple Publisher and/or Subscriber 82 | Component processes. Values are devlivered to multiple Subscriber processes 83 | in a round-robin manner.

84 |

The design of this module borrows ideas and terminology from the Reactive Stream 85 | project: http://www.reactive-streams.org/

86 | 87 |
88 | 89 | 90 | 91 |
92 |

93 | 94 | 95 | 96 | Summary 97 |

98 | 99 | 100 | 101 |
102 |

103 | Functions 104 |

105 |
106 | 109 | 110 |

This function serves as a Subscriptions’s main computational loop, dealing 111 | with requests for data from Subscribers and responses from Publishers. The 112 | subscriber_index points to the next subscriber that is to receive data

113 |
114 | 115 |
116 |
117 | 120 | 121 |

The new function doesn’t do much except initialize a Subscription structure 122 | with values for the names of the publisher and subscriber ports that this 123 | subscription will connect to and manage. Also see the start() function below. 124 | The initial value for a subscription’s capacity is set to :infinity

125 |
126 | 127 |
128 |
129 | 132 | 133 |

The new function doesn’t do much except initialize a Subscription structure 134 | with values for the names of the publisher and subscriber ports that this 135 | subscription will connect to and manage. Also see the start() function below. 136 | An initial value for the subscription’s capacity is supplied

137 |
138 | 139 |
140 |
141 | 144 | 145 |

The start function does nothing more than spawn a Subscription process. The other 146 | values in the Subscription structure are initialized after the Components that 147 | are connected to this subscription have been started. See Component.start(); 148 | it is then that we know how many subscriber and publisher processes are 149 | attached to this subscription

150 |
151 | 152 |
153 |
154 | 157 | 158 |
159 | 160 |
161 | 162 | 163 | 164 | 165 | 166 | 167 |
168 | 169 | 170 | 171 | 172 | 173 |
174 |

175 | 176 | 177 | 178 | Functions 179 |

180 |
181 |
182 | 183 | 184 | 185 | loop(subscription, subscriber_index) 186 | 187 | 188 | 189 | 190 | 191 |
192 | 193 |
194 |

This function serves as a Subscriptions’s main computational loop, dealing 195 | with requests for data from Subscribers and responses from Publishers. The 196 | subscriber_index points to the next subscriber that is to receive data.

197 |

The subscriber and publisher processes are started with Component.start. after 198 | starting a component’s process(es), the function sends lists of process pids 199 | as messages to this subscription process.

200 | 201 |
202 |
203 |
204 |
205 | 206 | 207 | 208 | new(publisher_port, subscriber_port) 209 | 210 | 211 | 212 | 213 | 214 |
215 | 216 |
217 |

The new function doesn’t do much except initialize a Subscription structure 218 | with values for the names of the publisher and subscriber ports that this 219 | subscription will connect to and manage. Also see the start() function below. 220 | The initial value for a subscription’s capacity is set to :infinity.

221 | 222 |
223 |
224 |
225 |
226 | 227 | 228 | 229 | new(publisher_port, subscriber_port, capacity) 230 | 231 | 232 | 233 | 234 | 235 |
236 | 237 |
238 |

The new function doesn’t do much except initialize a Subscription structure 239 | with values for the names of the publisher and subscriber ports that this 240 | subscription will connect to and manage. Also see the start() function below. 241 | An initial value for the subscription’s capacity is supplied.

242 | 243 |
244 |
245 |
246 |
247 | 248 | 249 | 250 | start(inport, outport) 251 | 252 | 253 | 254 | 255 | 256 |
257 | 258 |
259 |

The start function does nothing more than spawn a Subscription process. The other 260 | values in the Subscription structure are initialized after the Components that 261 | are connected to this subscription have been started. See Component.start(); 262 | it is then that we know how many subscriber and publisher processes are 263 | attached to this subscription.

264 | 265 |
266 |
267 |
268 |
269 | 270 | 271 | 272 | start(inport, outport, capacity) 273 | 274 | 275 | 276 | 277 | 278 |
279 | 280 |
281 | 282 |
283 |
284 | 285 |
286 | 287 | 288 | 289 | 290 | 291 | 303 |
304 |
305 |
306 | 307 | 308 | 309 | 310 | -------------------------------------------------------------------------------- /doc/ElixirFBP.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ElixirFBP – ElixirFBP v0.0.1 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 19 | 57 | 58 |
59 |
60 | 61 | 62 |

63 | ElixirFBP 64 | 65 | 66 | 67 | 68 | 69 | 70 |

71 | 72 | 73 |
74 |

ElixirFBP is an implementation of the flow-based programming (FBP) technique. 75 | An FBP program is represented by a directed graph where the nodes are 76 | components that can receive Information Packets (IPs) via in ports and send 77 | IPs - typically after some computation - out the out ports.

78 |

In ElixirFBP, each component is a module that must specify the names of its 79 | in and out ports and it must implement a loop function.

80 |

An FBP program is created by adding components to an ElixrFBP.Graph. The 81 | graph, in turn, is kept inside and managed by an ElixirFBP.Network.

82 | 83 |
84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 107 |
108 |
109 |
110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /doc/dist/app.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Lato:400,300,700,900|Merriweather:300italic,300,700,700italic|Inconsolata:400,700);.hljs,article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}img,legend{border:0}.sidebar a,.sidebar-toggle{transition:color .3s ease-in-out}.sidebar .sidebar-search .sidebar-searchInput:focus,.sidebar .sidebar-search .sidebar-searchInput:hover,.sidebar-toggle:active,.sidebar-toggle:focus,.sidebar-toggle:hover,a:active,a:hover{outline:0}.results ul,.sidebar ul{list-style:none}.hljs-comment{color:#8e908c}.css .hljs-class,.css .hljs-id,.css .hljs-pseudo,.hljs-attribute,.hljs-regexp,.hljs-tag,.hljs-variable,.html .hljs-doctype,.ruby .hljs-constant,.xml .hljs-doctype,.xml .hljs-pi,.xml .hljs-tag .hljs-title{color:#c82829}.hljs-built_in,.hljs-constant,.hljs-literal,.hljs-number,.hljs-params,.hljs-pragma,.hljs-preprocessor{color:#f5871f}.css .hljs-rule .hljs-attribute,.ruby .hljs-class .hljs-title{color:#eab700}.hljs-header,.hljs-inheritance,.hljs-name,.hljs-string,.hljs-value,.ruby .hljs-symbol,.xml .hljs-cdata{color:#718c00}.css .hljs-hexcolor,.hljs-title{color:#3e999f}.coffeescript .hljs-title,.hljs-function,.javascript .hljs-title,.perl .hljs-sub,.python .hljs-decorator,.python .hljs-title,.ruby .hljs-function .hljs-title,.ruby .hljs-title .hljs-keyword{color:#4271ae}.hljs-keyword,.javascript .hljs-function{color:#8959a8}.hljs{overflow-x:auto;background:#fff;color:#4d4d4c;padding:.5em;-webkit-text-size-adjust:none}legend,td,th{padding:0}.coffeescript .javascript,.javascript .xml,.tex .hljs-formula,.xml .css,.xml .hljs-cdata,.xml .javascript,.xml .vbscript{opacity:.5}/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}abbr[title]{border-bottom:1px dotted}b,optgroup,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre,textarea{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}.main,body,html{overflow:hidden}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}.content,.main,.sidebar,body,html{height:100%}input[type=search]{-webkit-appearance:textfield;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}table{border-collapse:collapse;border-spacing:0}@font-face{font-family:icomoon;src:url(../fonts/icomoon.eot?h5z89e);src:url(../fonts/icomoon.eot?#iefixh5z89e) format('embedded-opentype'),url(../fonts/icomoon.ttf?h5z89e) format('truetype'),url(../fonts/icomoon.woff?h5z89e) format('woff'),url(../fonts/icomoon.svg?h5z89e#icomoon) format('svg');font-weight:400;font-style:normal}.icon-elem,[class*=" icon-"],[class^=icon-]{font-family:icomoon;speak:none;font-style:normal;font-weight:400;font-variant:normal;text-transform:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.sidebar,body{font-family:Lato,sans-serif}.icon-link:before{content:"\e005"}.icon-search:before{content:"\e036"}.icon-cross:before{content:"\e117"}.icon-menu:before{content:"\e120"}.icon-angle-right:before{content:"\f105"}.icon-code:before{content:"\f121"}body,html{box-sizing:border-box;width:100%}body{margin:0;font-size:16px;line-height:1.6875em}*,:after,:before{box-sizing:inherit}.main{display:-webkit-flex;display:-ms-flexbox;display:-ms-flex;display:flex}.sidebar,body.sidebar-closed .sidebar{display:none}.sidebar{-webkit-flex:0 1 300px;-moz-flex:0 1 300px;-ms-flex:0 1 300px;flex:0 1 300px;-ms-flex-positive:0;-ms-flex-negative:1;-ms-flex-preferred-size:300px;-webkit-box-orient:vertical;-moz-box-orient:vertical;-webkit-box-direction:normal;-moz-box-direction:normal;min-height:0;-webkit-flex-direction:column;-moz-flex-direction:column;-ms-flex-direction:column;flex-direction:column;position:absolute;z-index:999}.content{-webkit-flex:1 1 .01%;-moz-flex:1 1 .01%;-ms-flex:1 1 .01%;flex:1 1 .01%;-ms-flex-positive:1;-ms-flex-negative:1;-ms-flex-preferred-size:.01%;overflow-y:auto;-webkit-overflow-scrolling:touch}.content-inner{max-width:949px;margin:0 auto;padding:3px 60px}@media screen and (max-width:768px){.content-inner{padding:27px 20px 27px 40px}}body.sidebar-closed .sidebar-toggle{display:block}.sidebar-toggle{position:fixed;z-index:99;left:18px;top:8px;background-color:transparent;border:none;padding:0;font-size:16px}.sidebar-toggle:hover{color:#e1e1e1}@media screen and (min-width:768px){.sidebar-toggle{display:none}}.sidebar{font-size:14px;line-height:18px;background:#373f52;color:#d5dae6;overflow:hidden}.sidebar .sidebar-toggle{display:block;left:275px;color:#e1e1e1}.sidebar .sidebar-toggle:hover{color:#fff}.sidebar ul li{margin:0;padding:0 10px}.sidebar a{color:#d5dae6;text-decoration:none}.sidebar a:hover{color:#fff}.sidebar .sidebar-projectLink{margin:23px 30px 0}.sidebar .sidebar-projectDetails{display:inline-block;text-align:right;vertical-align:top;margin-top:6px}.sidebar .sidebar-projectImage{display:inline-block;max-width:64px;max-height:64px;margin-left:15px;vertical-align:bottom}.sidebar .sidebar-projectName{font-weight:700;font-size:24px;line-height:30px;color:#fff;margin:0;padding:0;max-width:155px}.sidebar .sidebar-projectVersion{margin:0;padding:0;font-weight:300;font-size:16px;line-height:20px;color:#fff}.sidebar .sidebar-listNav{padding:0 30px}.sidebar .sidebar-listNav li,.sidebar .sidebar-listNav li a{text-transform:uppercase;font-weight:300;font-size:13px}.sidebar .sidebar-listNav li{padding-left:17px;border-left:3px solid transparent;transition:all .3s linear;line-height:27px}.sidebar .sidebar-listNav li.selected,.sidebar .sidebar-listNav li.selected a,.sidebar .sidebar-listNav li:hover,.sidebar .sidebar-listNav li:hover a{border-color:#9768d1;color:#fff}.sidebar .sidebar-search{margin:23px 30px 18px;display:-webkit-flex;display:-ms-flexbox;display:-ms-flex;display:flex}.sidebar .sidebar-search i.icon-search{font-size:14px;color:#d5dae6}.sidebar #full-list li.clicked>a,.sidebar #full-list ul li.active a{color:#fff}.sidebar .sidebar-search .sidebar-searchInput{background-color:transparent;border:none;border-radius:0;border-bottom:1px solid #959595;margin-left:5px}.sidebar #full-list{margin:4px 0 0 30px;padding:0 20px;overflow-y:auto;-webkit-overflow-scrolling:touch;-webkit-flex:1 1 .01%;-moz-flex:1 1 .01%;-ms-flex:1 1 .01%;flex:1 1 .01%;-ms-flex-positive:1;-ms-flex-negative:1;-ms-flex-preferred-size:.01%}.sidebar #full-list ul{margin:0 20px;padding:9px 0 18px}.sidebar #full-list ul li{font-weight:300;line-height:18px}.sidebar #full-list ul li ul{display:none;padding:9px 0}.sidebar #full-list ul li ul li{border-left:1px solid #959595;padding:0 10px}.sidebar #full-list ul li ul li.active:before{font-family:icomoon;speak:none;font-style:normal;font-weight:400;font-variant:normal;text-transform:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;content:"\f105";margin-left:-10px;font-size:16px;margin-right:5px}.sidebar #full-list ul li.active{border-left:none}.sidebar #full-list ul li.active ul{display:block}.sidebar #full-list li{padding:0;line-height:27px}.sidebar #full-list li.collapsed ul{display:none}@media screen and (min-width:768px){.sidebar{position:relative;display:-webkit-flex;display:-ms-flexbox;display:-ms-flex;display:flex}}@media screen and (max-height:500px){.sidebar{overflow-y:auto}.sidebar #full-list{overflow:visible}}.content-inner{font-family:Merriweather,serif;font-size:1em;line-height:1.6875em}.content-inner h1,.content-inner h2,.content-inner h3,.content-inner h4,.content-inner h5,.content-inner h6{font-family:Lato,sans-serif;font-weight:800;line-height:1.5em;word-wrap:break-word}.content-inner h1{font-size:2em;margin:1em 0 .5em}.content-inner h1.section-heading{margin:1.5em 0 .5em}.content-inner h1 small{font-weight:300}.content-inner h1 a.view-source{font-size:1.2rem}.content-inner h2{font-size:1.625em;margin:1em 0 .5em;font-weight:400}.content-inner h3{font-size:1.375em;margin:1em 0 .5em;font-weight:600}.content-inner a{color:#000;text-decoration:none;text-shadow:.03em 0 #fff,-.03em 0 #fff,0 .03em #fff,0 -.03em #fff,.06em 0 #fff,-.06em 0 #fff,.09em 0 #fff,-.09em 0 #fff,.12em 0 #fff,-.12em 0 #fff,.15em 0 #fff,-.15em 0 #fff;background-image:linear-gradient(#fff,#fff),linear-gradient(#fff,#fff),linear-gradient(#000,#000);background-size:.05em 1px,.05em 1px,1px 1px;background-repeat:no-repeat,no-repeat,repeat-x;background-position:0 90%,100% 90%,0 90%}.content-inner a:selection{text-shadow:.03em 0 #b4d5fe,-.03em 0 #b4d5fe,0 .03em #b4d5fe,0 -.03em #b4d5fe,.06em 0 #b4d5fe,-.06em 0 #b4d5fe,.09em 0 #b4d5fe,-.09em 0 #b4d5fe,.12em 0 #b4d5fe,-.12em 0 #b4d5fe,.15em 0 #b4d5fe,-.15em 0 #b4d5fe;background:#b4d5fe}.content-inner a:-moz-selection{text-shadow:.03em 0 #b4d5fe,-.03em 0 #b4d5fe,0 .03em #b4d5fe,0 -.03em #b4d5fe,.06em 0 #b4d5fe,-.06em 0 #b4d5fe,.09em 0 #b4d5fe,-.09em 0 #b4d5fe,.12em 0 #b4d5fe,-.12em 0 #b4d5fe,.15em 0 #b4d5fe,-.15em 0 #b4d5fe;background:#b4d5fe}.content-inner a *,.content-inner a :after,.content-inner a :before,.content-inner a:after,.content-inner a:before{text-shadow:none}.content-inner a:visited{color:#000}.content-inner ul li{line-height:1.5em}.content-inner a.view-source{float:right;color:#959595;background:0 0;border:none;text-shadow:none;transition:color .3s ease-in-out}.content-inner a.view-source:hover{color:#373f52}.content-inner blockquote{font-style:italic;margin:.5em 0;padding:.25em 1.5em;border-left:3px solid #e1e1e1;display:inline-block}.content-inner blockquote :first-child{padding-top:0;margin-top:0}.content-inner blockquote :last-child{padding-bottom:0;margin-bottom:0}.content-inner table{margin:2em 0}.content-inner th{text-align:left;font-family:Lato,sans-serif;text-transform:uppercase;font-weight:600;padding-bottom:.5em}.content-inner tr{border-bottom:1px solid #d5dae6;vertical-align:bottom;height:2.5em}.content-inner .summary .summary-row .summary-signature a,.content-inner .summary h2 a{background:0 0;border:none;text-shadow:none}.content-inner td,.content-inner th{padding-left:1em;line-height:2em}.content-inner h1.section-heading:hover a.hover-link{opacity:1;text-decoration:none}.content-inner h1.section-heading a.hover-link{transition:opacity .3s ease-in-out;display:inline-block;opacity:0;padding:.3em .6em .6em;line-height:1em;margin-left:-2.7em;background:0 0;border:none;text-shadow:none;font-size:16px;vertical-align:middle}.content-inner .summary h2{font-weight:600}.content-inner .summary .summary-row .summary-signature{font-family:Inconsolata,Menlo,Courier,monospace;font-weight:600}.content-inner .summary .summary-row .summary-synopsis{font-family:Merriweather,serif;font-style:italic;padding:0 .5em;margin:0 0 .5em}.content-inner .detail-header,.content-inner code{font-family:Inconsolata,Menlo,Courier,monospace}.content-inner .summary .summary-row .summary-synopsis p{margin:0;padding:0}.content-inner .detail-header{margin:2.5em 0 .5em;padding:.5em 1em;background:#f7f7f7;border-left:3px solid #9768d1;font-size:1em;position:relative}.content-inner .detail-header .signature{font-size:1rem;font-weight:600}.content-inner .detail-header:hover a.detail-link{opacity:1;text-decoration:none}.content-inner .detail-header a.detail-link{transition:opacity .3s ease-in-out;position:absolute;top:0;left:0;display:block;opacity:0;padding:.6em;line-height:1.5em;margin-left:-2.5em;background:0 0;border:none;text-shadow:none}.content-inner .specs .specs-list pre code,.content-inner .types .types-list .type-detail pre code{padding:0 .5em;border:none}.content-inner .specs .specs-list{margin:0 0 2em}.content-inner .specs .specs-list pre{margin:.5em 0}.content-inner .types .types-list .type-detail{margin-bottom:2em}.content-inner .types .types-list .type-detail pre{margin:.5em 0}.content-inner .types .types-list .type-detail .typespec-doc{padding:0 1.5em}.content-inner a.no-underline,.content-inner code a{color:#9768d1;text-shadow:none;background-image:none}.content-inner a.no-underline:active,.content-inner a.no-underline:focus,.content-inner a.no-underline:hover,.content-inner a.no-underline:visited,.content-inner code a:active,.content-inner code a:focus,.content-inner code a:hover,.content-inner code a:visited{color:#9768d1}.content-inner code{font-size:15px;font-style:normal;line-height:24px;font-weight:400;background-color:#f7f9fc;border:1px solid #e1e1e1;vertical-align:middle;border-radius:2px;padding:0 .5em}.content-inner pre{margin:1.5em 0}.content-inner pre.spec{margin:0}.content-inner pre.spec code{padding:0}.content-inner pre code.hljs{white-space:inherit;padding:1em 1.5em;background-color:#f7f9fc}.content-inner .footer{margin:4em auto 1em;text-align:center;font-style:italic;font-size:14px;color:#959595}.content-inner .footer .line{display:inline-block}.content-inner .footer a{color:#959595;text-decoration:none;text-shadow:.03em 0 #fff,-.03em 0 #fff,0 .03em #fff,0 -.03em #fff,.06em 0 #fff,-.06em 0 #fff,.09em 0 #fff,-.09em 0 #fff,.12em 0 #fff,-.12em 0 #fff,.15em 0 #fff,-.15em 0 #fff;background-image:linear-gradient(#fff,#fff),linear-gradient(#fff,#fff),linear-gradient(#959595,#959595);background-size:.05em 1px,.05em 1px,1px 1px;background-repeat:no-repeat,no-repeat,repeat-x;background-position:0 90%,100% 90%,0 90%}.content-inner .footer a:selection{text-shadow:.03em 0 #b4d5fe,-.03em 0 #b4d5fe,0 .03em #b4d5fe,0 -.03em #b4d5fe,.06em 0 #b4d5fe,-.06em 0 #b4d5fe,.09em 0 #b4d5fe,-.09em 0 #b4d5fe,.12em 0 #b4d5fe,-.12em 0 #b4d5fe,.15em 0 #b4d5fe,-.15em 0 #b4d5fe;background:#b4d5fe}.content-inner .footer a:-moz-selection{text-shadow:.03em 0 #b4d5fe,-.03em 0 #b4d5fe,0 .03em #b4d5fe,0 -.03em #b4d5fe,.06em 0 #b4d5fe,-.06em 0 #b4d5fe,.09em 0 #b4d5fe,-.09em 0 #b4d5fe,.12em 0 #b4d5fe,-.12em 0 #b4d5fe,.15em 0 #b4d5fe,-.15em 0 #b4d5fe;background:#b4d5fe}.results .result-id a,a.close-search{text-shadow:none;background-image:none;transition:color .3s ease-in-out}.content-inner .footer a *,.content-inner .footer a :after,.content-inner .footer a :before,.content-inner .footer a:after,.content-inner .footer a:before{text-shadow:none}.content-inner .footer a:visited{color:#959595}a.close-search{margin-top:-3em;display:block;float:right}a.close-search:active,a.close-search:focus,a.close-search:visited{color:#000}a.close-search:hover{color:#9768d1}.results .result-id{font-size:1.2em}.results .result-id a:active,.results .result-id a:focus,.results .result-id a:visited{color:#000}.results .result-id a:hover{color:#9768d1}.results .result-elem em,.results .result-id em{font-style:normal;color:#9768d1}.results ul{margin:0;padding:0}@media print{#sidebar{display:none}} -------------------------------------------------------------------------------- /doc/dist/sidebar_items.js: -------------------------------------------------------------------------------- 1 | sidebarNodes={"exceptions":[],"extras":[{"id":"extra-api-reference","title":"API Reference","headers":[]}],"modules":[{"id":"ElixirFBP","title":"ElixirFBP"},{"id":"ElixirFBP.Behaviour","title":"ElixirFBP.Behaviour","callbacks":[{"id":"description/0","anchor":"c:description/0"},{"id":"inports/0","anchor":"c:inports/0"},{"id":"loop/2","anchor":"c:loop/2"},{"id":"outports/0","anchor":"c:outports/0"}]},{"id":"ElixirFBP.Component","title":"ElixirFBP.Component","functions":[{"id":"start/5","anchor":"start/5"},{"id":"stop/3","anchor":"stop/3"}]},{"id":"ElixirFBP.ComponentLoader","title":"ElixirFBP.ComponentLoader","functions":[{"id":"get_components/1","anchor":"get_components/1"},{"id":"is_component?/1","anchor":"is_component?/1"},{"id":"retrieve_components/1","anchor":"retrieve_components/1"}]},{"id":"ElixirFBP.Graph","title":"ElixirFBP.Graph","functions":[{"id":"add_edge/6","anchor":"add_edge/6"},{"id":"add_initial/5","anchor":"add_initial/5"},{"id":"add_node/4","anchor":"add_node/4"},{"id":"change_edge/7","anchor":"change_edge/7"},{"id":"change_node/4","anchor":"change_node/4"},{"id":"edges/1","anchor":"edges/1"},{"id":"edges/2","anchor":"edges/2"},{"id":"get/1","anchor":"get/1"},{"id":"get_node/2","anchor":"get_node/2"},{"id":"get_status/1","anchor":"get_status/1"},{"id":"get_subscription/5","anchor":"get_subscription/5"},{"id":"handle_call/3","anchor":"handle_call/3"},{"id":"init/1","anchor":"init/1"},{"id":"nodes/1","anchor":"nodes/1"},{"id":"remove_edge/5","anchor":"remove_edge/5"},{"id":"remove_initial/4","anchor":"remove_initial/4"},{"id":"remove_node/2","anchor":"remove_node/2"},{"id":"rename_node/4","anchor":"rename_node/4"},{"id":"set_parameters/2","anchor":"set_parameters/2"},{"id":"start/2","anchor":"start/2"},{"id":"start_link/2","anchor":"start_link/2"},{"id":"stop/1","anchor":"stop/1"},{"id":"stop_graph/1","anchor":"stop_graph/1"},{"id":"terminate/2","anchor":"terminate/2"},{"id":"verify_out_ports/1","anchor":"verify_out_ports/1"}],"types":[{"id":"t/0","anchor":"t:t/0"}]},{"id":"ElixirFBP.InitialInformationPacket","title":"ElixirFBP.InitialInformationPacket","functions":[{"id":"description/0","anchor":"description/0"},{"id":"inports/0","anchor":"inports/0"},{"id":"loop/2","anchor":"loop/2"},{"id":"outports/0","anchor":"outports/0"}]},{"id":"ElixirFBP.Network","title":"ElixirFBP.Network","functions":[{"id":"clear/2","anchor":"clear/2"},{"id":"data/5","anchor":"data/5"},{"id":"get_graph/1","anchor":"get_graph/1"},{"id":"get_status/2","anchor":"get_status/2"},{"id":"handle_call/3","anchor":"handle_call/3"},{"id":"handle_cast/2","anchor":"handle_cast/2"},{"id":"init/1","anchor":"init/1"},{"id":"remove_graph/1","anchor":"remove_graph/1"},{"id":"set_debug/1","anchor":"set_debug/1"},{"id":"start/2","anchor":"start/2"},{"id":"start_link/0","anchor":"start_link/0"},{"id":"stop/0","anchor":"stop/0"},{"id":"stop/1","anchor":"stop/1"},{"id":"terminate/2","anchor":"terminate/2"}]},{"id":"ElixirFBP.Subscription","title":"ElixirFBP.Subscription","functions":[{"id":"loop/2","anchor":"loop/2"},{"id":"new/2","anchor":"new/2"},{"id":"new/3","anchor":"new/3"},{"id":"start/2","anchor":"start/2"},{"id":"start/3","anchor":"start/3"}]}],"protocols":[]} -------------------------------------------------------------------------------- /doc/extra-api-reference.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | API Reference – ElixirFBP v0.0.1 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 19 | 57 | 58 |
59 |
60 | 61 |

API Reference

62 | 63 | 70 | 71 | 72 |
73 |

Modules

74 |
75 |
76 | 77 | 78 |

ElixirFBP is an implementation of the flow-based programming (FBP) technique. 79 | An FBP program is represented by a directed graph where the nodes are 80 | components that can receive Information Packets (IPs) via in ports and send 81 | IPs - typically after some computation - out the out ports

82 |
83 | 84 |
85 |
86 | 87 | 88 |
89 |
90 | 91 | 92 |

Component is used to start an FBP component. It takes the component’s name - 93 | an Elixir module name - and spawns a process. It is assumed that the component 94 | module supports a loop function which will be called once the component is 95 | spawned

96 |
97 | 98 |
99 |
100 | 101 | 102 |

This module is responsible for loading ElixirFBP components at run time. Given 103 | a list of file paths, it will examine all modules, select those which implement 104 | the ElixirFBP.ComponentBehaviour and develop a list of Component attributes 105 | such as description, inports, and outports

106 |
107 | 108 |
109 |
110 | 111 | 112 |

ElxirFBP.Graph is a GenServer that provides support for the creation and 113 | maintainance of an FBP Graph

114 |
115 | 116 |
117 |
118 | 119 | 120 |

A special component that can deliver a constant anytime it is sent 121 | a :value message

122 |
123 | 124 |
125 |
126 | 127 | 128 |

ElxirFBP.Network is a GenServer that provides support for starting and stopping a 129 | FBP network, and finding out about its state. The network keeps 130 | a dictionary of graphs ids, each of which points to an ElixirFBP.Graph structure. 131 | Graphs are also implemented as GenServers

132 |
133 | 134 |
135 |
136 | 137 | 138 |

A Subscription serves as a conduit and a control mechanism for the delivery of 139 | data from Publishers (Components) to Subscribers (Components). A Subscriber 140 | must specify how many IPs it is willing to receive via a {:request, n} message 141 | where n is some integer or the atom :infinity. The Subscription will ensure that 142 | no more IPs than have been asked for will be sent

143 |
144 | 145 |
146 | 147 |
148 |
149 | 150 | 151 | 152 | 153 | 154 | 166 |
167 |
168 |
169 | 170 | 171 | 172 | 173 | -------------------------------------------------------------------------------- /doc/fonts/icomoon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pcmarks/ElixirFBP/4a28b44a7a2bed446239c0a425ae3ca7c11458bb/doc/fonts/icomoon.eot -------------------------------------------------------------------------------- /doc/fonts/icomoon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by IcoMoon 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /doc/fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pcmarks/ElixirFBP/4a28b44a7a2bed446239c0a425ae3ca7c11458bb/doc/fonts/icomoon.ttf -------------------------------------------------------------------------------- /doc/fonts/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pcmarks/ElixirFBP/4a28b44a7a2bed446239c0a425ae3ca7c11458bb/doc/fonts/icomoon.woff -------------------------------------------------------------------------------- /doc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ElixirFBP v0.0.1 – Documentation 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /elixirFBP-0.0.1.tar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pcmarks/ElixirFBP/4a28b44a7a2bed446239c0a425ae3ca7c11458bb/elixirFBP-0.0.1.tar -------------------------------------------------------------------------------- /lib/elixirFBP.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirFBP do 2 | @moduledoc """ 3 | ElixirFBP is an implementation of the flow-based programming (FBP) technique. 4 | An FBP program is represented by a directed graph where the nodes are 5 | components that can receive Information Packets (IPs) via in ports and send 6 | IPs - typically after some computation - out the out ports. 7 | 8 | In ElixirFBP, each component is a module that must specify the names of its 9 | in and out ports and it must implement a loop function that is responsible for 10 | maintaining state and sending computed values to the out ports. 11 | 12 | An FBP program is created by adding components to an ElixrFBP.Graph (a gen_server). 13 | The graph, in turn, is kept inside and managed by an ElixirFBP.Network 14 | (a gen_server). 15 | 16 | """ 17 | end 18 | -------------------------------------------------------------------------------- /lib/elixirFBP/behaviour.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirFBP.Behaviour do 2 | @moduledoc """ 3 | An ElixirFBP component is expected to implement this Behaviour. 4 | 5 | inports is a (possibly empty) keyword list of the form [inport1: type, ...] 6 | 7 | outports is a (possibly empty) keyword list of the form [outport1: type, ...] 8 | 9 | The loop function takes two arguments. The first argument is a map of input 10 | values. It is used as a state variable - a place to store values that have 11 | been received but are not ready to be used in a computation and the 12 | result sent out the outports. 13 | 14 | The second loop argument is a map of outport pids. When a value is ready to 15 | be sent to an outport, the outport name (an atom) is used to get the pid. The 16 | pid is actually a Subscription pid. 17 | 18 | Runnin a component in push and/or pull mode. 19 | TODO: describe 20 | """ 21 | use Behaviour 22 | 23 | defcallback description :: String.t 24 | defcallback inports() :: [atom: atom] 25 | defcallback outports() :: [atom: atom] 26 | defcallback loop(%{}, %{}) :: any 27 | 28 | end 29 | -------------------------------------------------------------------------------- /lib/elixirFBP/component.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirFBP.Component do 2 | @moduledoc """ 3 | Component is used to start an FBP component. It takes the component's name - 4 | an Elixir module name - and spawns a process. It is assumed that the component 5 | module supports a loop function which will be called once the component is 6 | spawned. 7 | """ 8 | @doc """ 9 | Start the execution of a component in its own process(es). Spawn as many 10 | processes as are specified in the no_of_processes value (the default value is one). 11 | 12 | inports is a list of {port, value} tuples where value is an initial value or 13 | a Subscription pid. outports is a list of {port, pid} tuples where the pid is 14 | a Subscription. 15 | 16 | inports and outports are used to create the initial arguments sent to a 17 | Component's loop function. They are also used to create lists of component 18 | pids that must be sent to any Subscriptions that a component's in or out ports 19 | are connected to. 20 | """ 21 | def start(component, node_id, inports, outports, no_of_processes \\ 1) do 22 | # IO.puts("inports: #{inspect inports}") 23 | # IO.puts("outports: #{inspect outports}") 24 | # Remove pids as inport values for the spawning of the component process. 25 | # After the component is spawned, these pids will be used to update the 26 | # Subscriptions associated with the inports. 27 | inps = Enum.map(inports, fn 28 | {port, pid} when is_pid(pid) -> 29 | {port, nil} 30 | {port, value} -> 31 | {port, value} 32 | end) |> Enum.into(%{}) 33 | outports = Enum.map(outports, fn({port, subscription} = outport) -> 34 | outport 35 | end) |> Enum.into(%{}) 36 | module = Module.concat("Elixir", component) 37 | component_pids = Enum.map(1 .. no_of_processes, fn(_) -> 38 | spawn(module, :loop, [inps, outports]) 39 | end) 40 | Enum.each(outports, fn({port, subscription}) -> 41 | send(subscription, {:publisher_pids, component_pids}) 42 | end) 43 | Enum.each(inports, fn 44 | {port, subscription} when is_pid(subscription) -> 45 | send(subscription, {:subscriber_pids, List.to_tuple(component_pids)}) 46 | {port, initial_value} -> 47 | end) 48 | component_pids 49 | end 50 | 51 | @doc """ 52 | Stop a component. This means find the pid of all of the component's processes, 53 | unregister it and force an exit. 54 | """ 55 | def stop(graph_reg_name, node_id, label) do 56 | number_of_processes = label.metadata[:number_of_processes] 57 | process_name = Atom.to_string(graph_reg_name) <> "_" <> node_id 58 | Enum.each(Range.new(1, number_of_processes), fn(process_no) -> 59 | process_name_atom = String.to_atom(process_name <> "_#{process_no}") 60 | pid = Process.whereis(process_name_atom) 61 | if pid != nil do 62 | Process.unregister(process_name_atom) 63 | Process.exit(pid, :kill) # Not really a normal exit?? 64 | end 65 | end) 66 | end 67 | 68 | end 69 | -------------------------------------------------------------------------------- /lib/elixirFBP/component_loader.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirFBP.ComponentLoader do 2 | @moduledoc """ 3 | This module is responsible for loading ElixirFBP components at run time. Given 4 | a list of file paths, it will examine all modules, select those which implement 5 | the ElixirFBP.ComponentBehaviour and develop a list of Component attributes 6 | such as description, inports, and outports. 7 | 8 | This code is based on the [answer](https://groups.google.com/d/msg/elixir-lang-core/wEi95gzibLE/qFa12TLCPrMJ) 9 | to a question about loading Elixir modules. The code is from the hex package 10 | manager. 11 | """ 12 | 13 | def retrieve_components(paths) do 14 | Enum.reduce(paths, [], fn(path, matches) -> 15 | {:ok, files } = :erl_prim_loader.list_dir(path |> to_char_list) 16 | Enum.reduce(files, matches, &match_component/2) 17 | end) 18 | end 19 | 20 | def is_component?(module) do 21 | attributes = module.__info__(:attributes) 22 | case attributes[:behaviour] do 23 | nil -> false 24 | behaviour -> ElixirFBP.Behaviour in behaviour 25 | end 26 | end 27 | 28 | def get_components(module_list) do 29 | Enum.map(module_list, fn(module) -> 30 | inports = module.inports() 31 | outports = module.outports() 32 | description = module.description() 33 | name = to_string(module) 34 | ips = Enum.map(inports, fn({id, type} = _inport) -> 35 | %{"id" => to_string(id), "type" => to_string(type)} 36 | end) 37 | ops = Enum.map(outports, fn({id, type} = _outport) -> 38 | %{"id" => to_string(id), "type" => to_string(type)} 39 | end) 40 | %{"name" => name, "description" => description, 41 | "inPorts" => ips, "outPorts" => ops} 42 | end) 43 | end 44 | 45 | @re_pattern Regex.re_pattern(~r/.*\.ex$/) 46 | 47 | defp match_component(filename, modules) do 48 | if :re.run(filename, @re_pattern, [capture: :none]) == :match do 49 | mod = Path.rootname(filename) |> List.to_atom 50 | if Code.ensure_compiled?(mod), do: [mod | modules], else: modules 51 | else 52 | modules 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/elixirFBP/graph.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirFBP.Graph do 2 | @moduledoc """ 3 | ElxirFBP.Graph is a GenServer that provides support for the creation and 4 | maintainance of an FBP Graph. 5 | 6 | An FBP graph contains Nodes connected by Edges. Facilities are provided for 7 | the creation, deletion, and modification of nodes and edges. Initial Information 8 | Packets (IIP's) can also be specified. 9 | 10 | Graphs are implemented using Erlang's digraph library. 11 | 12 | The digraph Label associated with a node (digraph vertex) is 13 | [component, inports, inport_types, outports, outport_types, metadata] where 14 | component is the string name of a component e.g., "Math.Add". inports 15 | and outports are lists of atomic name, initial value pairs, e.g., {:augend, 2} and 16 | inport_types and outport_types are lists of atomic name, type, e.g., {:augend, :integer}. 17 | 18 | Initial values can be set using the add_initial graph command. 19 | 20 | The digraph Label associated with an edge is [src.port,, tgt.port, metadata] where src.port 21 | and tgt.port are atom values for the component's ports. 22 | 23 | Functions supported by this module are based on NoFlo's FBP Network Protocol, 24 | specifically the graph sub-protocol. See http://noflojs.org/documentation/protocol/ 25 | for the details. 26 | 27 | TODO: Provide support for Port and Group maintenance. 28 | TODO: Use secret parameter 29 | TODO: Handle :digraph errors 30 | TODO: Metadata needs to be stored somewhere in add_initial() 31 | 32 | """ 33 | defstruct [ 34 | id: "", 35 | name: "", 36 | library: nil, 37 | main: false, 38 | icon: nil, 39 | description: "", 40 | registered_name: nil, 41 | running: false, 42 | started: false, 43 | graph: nil 44 | ] 45 | @type t :: %__MODULE__{ 46 | id: String.t, 47 | name: String.t, 48 | library: module, 49 | main: boolean, 50 | icon: String.t, 51 | description: String.t, 52 | registered_name: atom, 53 | running: boolean, 54 | started: boolean, 55 | graph: atom} 56 | 57 | use GenServer 58 | 59 | ######################################################################## 60 | # The External API 61 | 62 | @doc """ 63 | Starts things off with the creation of the state. Register it with the name 64 | graph_id - converted to an atom. 65 | """ 66 | def start_link(graph_id, parameters \\ %{}) do 67 | registered_name = String.to_atom(graph_id) 68 | {:ok, _pid} = GenServer.start_link(__MODULE__, 69 | [graph_id, registered_name, parameters], 70 | name: registered_name) 71 | {:ok, registered_name} 72 | end 73 | 74 | @doc """ 75 | Set the parameters associated with this graph 76 | """ 77 | def set_parameters(fbp_graph_reg_name, parameters \\ %{}) do 78 | GenServer.call(fbp_graph_reg_name, {:set_parameters, parameters}) 79 | end 80 | 81 | @doc """ 82 | Start the execution of the components in this graph. Optionally supplying 83 | a run mode of :pull or :push (default) 84 | """ 85 | def start(fbp_graph_reg_name, run_mode \\ :push) do 86 | GenServer.call(fbp_graph_reg_name, {:start, run_mode}) 87 | end 88 | 89 | @doc """ 90 | Stop the execution of the components in this graph. 91 | This should normally be called via the Network.stop(graph_id) function. 92 | """ 93 | def stop_graph(fbp_graph_reg_name) do 94 | GenServer.call(fbp_graph_reg_name, :stop_graph) 95 | end 96 | 97 | @doc """ 98 | Retreive the FBP Graph structure - primarily used for testing/debugging 99 | """ 100 | def get(fbp_graph_reg_name) do 101 | GenServer.call(fbp_graph_reg_name, :get) 102 | end 103 | 104 | @doc """ 105 | Return the status variables for this graph 106 | """ 107 | def get_status(fbp_graph_reg_name) do 108 | GenServer.call(fbp_graph_reg_name, :get_status) 109 | end 110 | 111 | @doc """ 112 | Return the current list of nodes 113 | """ 114 | def nodes(fbp_graph_reg_name) do 115 | GenServer.call(fbp_graph_reg_name, :nodes) 116 | end 117 | 118 | @doc """ 119 | Return info about a node. 120 | """ 121 | def get_node(fbp_graph_reg_name, node_id) do 122 | GenServer.call(fbp_graph_reg_name, {:get_node, node_id}) 123 | end 124 | 125 | @doc """ 126 | Return the current list of edges - primarily used for testing/debugging. 127 | """ 128 | def edges(fbp_graph_reg_name) do 129 | GenServer.call(fbp_graph_reg_name, :edges) 130 | end 131 | 132 | @doc """ 133 | Return the edges attached to a node 134 | """ 135 | def edges(fbp_graph_reg_name, node_id) do 136 | GenServer.call(fbp_graph_reg_name, {:edges, node_id}) 137 | end 138 | 139 | @doc """ 140 | Add a node to the FBP Graph. Note the number of default processes is 1. 141 | """ 142 | def add_node(fbp_graph_reg_name, node_id, component, metadata \\ %{}) do 143 | if ! Map.has_key?(metadata, :number_of_processes) do 144 | metadata = Map.merge(metadata, %{:number_of_processes => 1}) 145 | end 146 | GenServer.call(fbp_graph_reg_name, {:add_node, node_id, component, metadata}) 147 | end 148 | 149 | @doc """ 150 | Remove a node from an FBP Graph 151 | """ 152 | def remove_node(fbp_graph_reg_name, node_id) do 153 | GenServer.call(fbp_graph_reg_name, {:remove_node, node_id}) 154 | end 155 | 156 | @doc """ 157 | Rename a node in an FBP Graph 158 | """ 159 | def rename_node(fbp_graph_reg_name, 160 | from, 161 | to, 162 | secret) do 163 | GenServer.call(fbp_graph_reg_name, 164 | {:rename_node, from, to, secret}) 165 | end 166 | 167 | @doc """ 168 | Change the metadata associated with a node in a graph 169 | """ 170 | def change_node(fbp_graph_reg_name, 171 | node_id, 172 | metadata, 173 | secret) do 174 | GenServer.call(fbp_graph_reg_name, {:change_node, node_id, metadata, secret}) 175 | end 176 | 177 | @doc """ 178 | Add an edge to the FBP Graph 179 | """ 180 | def add_edge(fbp_graph_reg_name, 181 | src_node_id, src_port, 182 | tgt_node_id, tgt_port, 183 | metadata \\ %{}) do 184 | GenServer.call(fbp_graph_reg_name, 185 | {:add_edge, src_node_id, src_port, tgt_node_id, tgt_port, metadata}) 186 | end 187 | 188 | @doc """ 189 | Remove the edge between the two given node/ports in an FBP Graph 190 | """ 191 | def remove_edge(fbp_graph_reg_name, 192 | src_node_id, src_port, 193 | tgt_node_id, tgt_port) do 194 | GenServer.call(fbp_graph_reg_name, 195 | {:remove_edge, src_node_id, src_port, tgt_node_id, tgt_port}) 196 | end 197 | 198 | @doc """ 199 | Change an edge's metadata in an FBP Graph 200 | """ 201 | def change_edge(fbp_graph_reg_name, 202 | src_node_id, src_port, 203 | tgt_node_id, tgt_port, 204 | metadata, 205 | secret) do 206 | GenServer.call(fbp_graph_reg_name, 207 | {:change_edge, src_node_id, src_port, tgt_node_id, tgt_port, metadata, secret}) 208 | end 209 | 210 | @doc """ 211 | Get the subscription pid for this edge. 212 | """ 213 | def get_subscription(fbp_graph_reg_name, 214 | src_node_id, src_port, 215 | tgt_node_id, tgt_port) do 216 | GenServer.call(fbp_graph_reg_name, 217 | {:get_subscription,src_node_id, src_port, tgt_node_id, tgt_port}) 218 | end 219 | 220 | @doc """ 221 | Place an initial value at the port of a node in and FBP Graph 222 | """ 223 | def add_initial(fbp_graph_reg_name, data, node_id, port, metadata \\ %{}) do 224 | GenServer.call(fbp_graph_reg_name, {:add_initial, data, node_id, port, metadata}) 225 | end 226 | 227 | @doc """ 228 | Remove an initial value at the port of a node in the FBP Graph. It is set to 229 | the value nil. 230 | """ 231 | def remove_initial(fbp_graph_reg_name, node_id, port, secret) do 232 | GenServer.call(fbp_graph_reg_name, {:remove_initial, node_id, port, secret}) 233 | end 234 | 235 | @doc """ 236 | Stop this GenServer 237 | """ 238 | def stop(fbp_graph_reg_name) do 239 | GenServer.call(fbp_graph_reg_name, :stop) 240 | end 241 | 242 | ############################################################################## 243 | # The GenServer implementations 244 | 245 | @doc """ 246 | Callback implementation for ElixirFBP.Graph.clear() 247 | Create and initialize the FBP Graph Structure which becomes the State 248 | """ 249 | def init([graph_id, registered_name, parameters]) do 250 | # The digraph is where all the nodes and edges are stored 251 | graph = :digraph.new([:protected]) 252 | fbp_graph = %ElixirFBP.Graph{id: graph_id, 253 | name: parameters[:name], 254 | library: parameters[:library], 255 | main: parameters[:main], 256 | icon: parameters[:icon], 257 | description: parameters[:description], 258 | registered_name: registered_name, 259 | graph: graph} 260 | {:ok, fbp_graph} 261 | end 262 | 263 | @doc """ 264 | Callback implementation of ElixirFBP.Graph.set_parameters() 265 | Set the parameters associated with this registered graph 266 | """ 267 | def handle_call({:set_parameters, parameters}, _req, fbp_graph) do 268 | new_fbp_graph = %ElixirFBP.Graph{fbp_graph | 269 | name: parameters[:name], 270 | library: parameters[:library], 271 | main: parameters[:main], 272 | icon: parameters[:icon], 273 | description: parameters[:description]} 274 | {:reply, :ok, new_fbp_graph} 275 | end 276 | 277 | @doc """ 278 | Callback implementation of ElixirFBP.Graph.start() 279 | Starting a graph involves the following steps: 280 | 1. Verify that all outports are connected to something 281 | 2. For every edge in the graph, create and spawn a Subscription 282 | 3. For every node in the graph, assemble the inports and outport values 283 | for a Component and then spawn the Component's process(es) 284 | 4. Choose the method to begin execution based on the run_mode - either 285 | :push or :pull mode. 286 | 287 | """ 288 | def handle_call({:start, run_mode}, _req, fbp_graph) do 289 | nodes = :digraph.vertices(fbp_graph.graph) 290 | edges = :digraph.edges(fbp_graph.graph) 291 | # Verify that all out ports on all the nodes are connected to something 292 | problems = verify_out_ports(fbp_graph.graph) 293 | case problems do 294 | [] -> 295 | # Start a subscription for each edge; save the subscription's pid in a 296 | # Map for later lookup. The key values are the tuples {node_in, src_port} 297 | # and {node_out, tgt_ports} 298 | subscription_map = Enum.map(edges, fn(edge) -> 299 | {_edge_id, node_in, node_out, label} = :digraph.edge(fbp_graph.graph, edge) 300 | %{src_port: src_port, tgt_port: tgt_port} = label 301 | subscription_pid = ElixirFBP.Subscription.start(src_port, tgt_port) 302 | # Process.register(subscription_pid, :sub1) 303 | new_label = %{label | :subscription_pid => subscription_pid} 304 | :digraph.add_edge( 305 | fbp_graph.graph, 306 | edge, 307 | node_in, 308 | node_out, 309 | new_label) 310 | [{{node_in, src_port}, subscription_pid}, 311 | {{node_out, tgt_port}, subscription_pid}] 312 | end) |> List.flatten() |> Enum.into(%{}) 313 | # Create the initial inport and outport values for a Component. 314 | # Spawn 1 or more processes for a Component 315 | Enum.each(nodes, fn(node) -> 316 | {node_id, label} = :digraph.vertex(fbp_graph.graph, node) 317 | %{component: component, inports: inports, outports: outports} = label 318 | # inports will have either a Subscription pid or an initial value 319 | inps = Enum.map(inports, fn 320 | {inport, nil} -> 321 | subscription_pid = Map.get(subscription_map, {node_id, inport}) 322 | {inport, subscription_pid} 323 | {inport, value} -> 324 | {inport, value} 325 | end) |> Enum.into(%{}) 326 | # outports will have a Subscription pid as a value 327 | outps = Enum.map(outports, fn({outport, _}) -> 328 | subscription_pid = Map.get(subscription_map, {node_id, outport}) 329 | {outport, subscription_pid} 330 | end) |> Enum.into(%{}) 331 | # IO.puts("Component.start: #{component},#{node_id},#{inspect inps},#{inspect outps}") 332 | component_pids = ElixirFBP.Component.start(component, node_id, inps, outps) 333 | new_label = %{label | :component_pids => component_pids} 334 | :digraph.add_vertex(fbp_graph.graph, node_id, new_label) 335 | end) 336 | # Get things started conditioned on whether this graph is to run 337 | # in a push or pull mode 338 | case run_mode do 339 | :push -> 340 | # Find all of the initial information packet components and make 341 | # each of them send an information packet to their respective subscribers 342 | Enum.each(nodes, fn(node) -> 343 | {_vertex, 344 | %{component: component, 345 | component_pids: component_pids} = label} = :digraph.vertex(fbp_graph.graph, node) 346 | if component == "ElixirFBP.InitialInformationPacket" do 347 | Enum.each(component_pids, fn(component_pid) -> 348 | send(component_pid, :value) 349 | end) 350 | end 351 | end) 352 | :pull -> 353 | # For all of the subscriptions, send them a request message. request 354 | # capacity values from the subscription's publisher. 355 | Enum.each(edges, fn(edge) -> 356 | {_edge_id, _node_in, _node_out, label} = 357 | :digraph.edge(fbp_graph.graph, edge) 358 | %{subscription_pid: subscription_pid} = label 359 | send(subscription_pid, :request) 360 | end) 361 | _ -> 362 | nil 363 | end 364 | new_fbp_graph = %ElixirFBP.Graph{fbp_graph | started: true, running: true} 365 | {:reply, :ok, new_fbp_graph} 366 | _ -> 367 | # There were problems whilst checking the out ports 368 | {:reply, problems, fbp_graph} 369 | end 370 | end 371 | 372 | @doc """ 373 | Callback implementation of ElxirFBP.Graph.get() 374 | Return the FBP Graph structure 375 | """ 376 | def handle_call(:get, _req, fbp_graph) do 377 | {:reply, fbp_graph, fbp_graph} 378 | end 379 | 380 | @doc """ 381 | Callback implementation of ElixirFBP.Graph.get_status() 382 | Return the status variables as a tuple. 383 | """ 384 | def handle_call(:get_status, _req, fbp_graph) do 385 | {:reply, {fbp_graph.running, fbp_graph.started}, fbp_graph} 386 | end 387 | 388 | @doc """ 389 | Callback implementation of ElixirFBP.Graph.nodes() 390 | Return the current list of nodes. 391 | """ 392 | def handle_call(:nodes, _req, fbp_graph) do 393 | {:reply, :digraph.vertices(fbp_graph.graph), fbp_graph} 394 | end 395 | 396 | @doc """ 397 | Callback implementation of ElixirFBP.Graph.get_node() 398 | Returns {vertex, label} 399 | """ 400 | def handle_call({:get_node, node_id}, _req, fbp_graph) do 401 | graph = fbp_graph.graph 402 | {:reply, :digraph.vertex(graph, node_id), fbp_graph} 403 | end 404 | 405 | @doc """ 406 | Callback implementation of ElixirFBP.Graph.edges() 407 | Return the current list of edges. 408 | """ 409 | def handle_call(:edges, _req, fbp_graph) do 410 | {:reply, :digraph.edges(fbp_graph.graph), fbp_graph} 411 | end 412 | 413 | @doc """ 414 | Callback implementation for ElixirFBP.Graph.edges(node_id) 415 | Return the edges for a given node 416 | """ 417 | def handle_call({:edges, node_id}, _req, fbp_graph) do 418 | out_edges = :digraph.out_edges(fbp_graph.graph, node_id) 419 | in_edges = :digraph.in_edges(fbp_graph.graph, node_id) 420 | edges = Enum.map(out_edges ++ in_edges, fn(edge) -> 421 | {_edge_id, _node_in, _node_out, label} = 422 | :digraph.edge(fbp_graph.graph, edge) 423 | %{subscription_pid: subscription_pid} = label 424 | end) 425 | {:reply, :ok, fbp_graph} 426 | end 427 | 428 | @doc """ 429 | Callback implementation for ElixirFBP.Graph.add_node() 430 | """ 431 | def handle_call({:add_node, node_id, component, metadata}, _req, fbp_graph) do 432 | component_inports = elem(Code.eval_string(component <> ".inports"), 0) 433 | component_outports = elem(Code.eval_string(component <> ".outports"), 0) 434 | # Construct a list of port name, value pairs that will be used to hold 435 | # initial values. 436 | inports = Enum.map(component_inports, fn(inport) -> 437 | {elem(inport,0), nil} 438 | end) 439 | outports = Enum.map(component_outports, fn(outport) -> 440 | {elem(outport,0), nil} 441 | end) 442 | label = %{component: component, 443 | component_pids: [], 444 | inports: inports, inport_types: component_inports, 445 | outports: outports, outport_types: component_outports, 446 | metadata: metadata} 447 | new_vertex = :digraph.add_vertex(fbp_graph.graph, node_id, label) 448 | {:reply, new_vertex, fbp_graph} 449 | end 450 | 451 | @doc """ 452 | Callback implementation for ElixirFBP.Graph.remove_node() 453 | """ 454 | def handle_call({:remove_node, node_id}, _req, fbp_graph) do 455 | result = :digraph.del_vertex(fbp_graph.graph, node_id) 456 | {:reply, result, fbp_graph} 457 | end 458 | 459 | @doc """ 460 | Callback implementation for ElixirFBP.Graph.rename_node() 461 | """ 462 | def handle_call({:rename_node, from, to, secret}, _req, fbp_graph) do 463 | {_vertex, label} = :digraph.vertex(fbp_graph.graph, from) 464 | new_vertex = :digraph.add_vertex(fbp_graph.graph, to, label) 465 | :digraph.del_vertex(fbp_graph.graph, from) 466 | {:reply, new_vertex, fbp_graph} 467 | end 468 | 469 | @doc """ 470 | Callback implementation for ElixirFBP.Graph.change_node() 471 | """ 472 | def handle_call({:change_node, id, metadata, secret}, _req, fbp_graph) do 473 | {_vertex, label} = :digraph.vertex(fbp_graph.graph, id) 474 | current_metadata = label.metadata 475 | new_metadata = Map.merge(current_metadata, metadata) 476 | new_label = %{label | :metadata => new_metadata} 477 | new_vertex = :digraph.add_vertex(fbp_graph.graph, id, new_label) 478 | {:reply, new_vertex, fbp_graph} 479 | end 480 | 481 | @doc """ 482 | Callback implementation for ElixirFBP.Graph.add_edge() 483 | """ 484 | def handle_call({:add_edge, 485 | src_node_id, src_port, 486 | tgt_node_id, tgt_port, 487 | metadata}, _req, fbp_graph) do 488 | label = %{src_port: src_port, tgt_port: tgt_port, metadata: metadata, subscription_pid: nil} 489 | new_edge = :digraph.add_edge( 490 | fbp_graph.graph, 491 | src_node_id, 492 | tgt_node_id, 493 | label) 494 | {:reply, new_edge, fbp_graph} 495 | end 496 | 497 | @doc """ 498 | Callback implementation for ElixirFBP.Graph.remove_edge() 499 | """ 500 | def handle_call({:remove_edge, 501 | src_node_id, _src_port, 502 | tgt_node_id, _tgt_port}, 503 | _req, fbp_graph) do 504 | result = :digraph.del_path(fbp_graph.graph, src_node_id, tgt_node_id) 505 | {:reply, result, fbp_graph} 506 | end 507 | 508 | @doc """ 509 | Callback implementation for ElixirFBP.Graph.change_edge() 510 | """ 511 | def handle_call({:change_edge, 512 | src_node_id, _src_port, 513 | tgt_node_id, _tgt_port, 514 | metadata, secret}, 515 | _req, fbp_graph) do 516 | edges = :digraph.out_edges(fbp_graph.graph, src_node_id) 517 | Enum.each(edges, fn(edge) -> 518 | {_, _, node, label} = :digraph.edge(fbp_graph.graph, edge) 519 | if node == tgt_node_id do 520 | current_metadata = label.metadata 521 | new_metadata = Map.merge(current_metadata, metadata) 522 | new_label = %{label | :metadata => new_metadata} 523 | :digraph.add_edge( 524 | fbp_graph.graph, 525 | edge, 526 | src_node_id, 527 | tgt_node_id, 528 | new_label) 529 | end 530 | end) 531 | {:reply, nil, fbp_graph} 532 | end 533 | 534 | @doc """ 535 | Callback implementation for ElixirFBP.Graph.get_subscription() 536 | """ 537 | def handle_call({:get_subscription, 538 | src_node_id, _src_port, 539 | tgt_node_id, _tgt_port}, _req, fbp_graph) do 540 | edges = :digraph.out_edges(fbp_graph.graph, src_node_id) 541 | subscription_pids = Enum.map(edges, 542 | fn(edge) -> 543 | {_, _, node, label} = :digraph.edge(fbp_graph.graph, edge) 544 | if node == tgt_node_id do 545 | %{:subscription_pid => subscription_pid} = label 546 | subscription_pid 547 | end 548 | end) 549 | {:reply, subscription_pids, fbp_graph} 550 | end 551 | 552 | @doc """ 553 | Callback implementation for ElixirFBP.Graph.add_initial(). We create a node 554 | using the distinguished component named InitialInformationPacket. This node 555 | is preloaded with the initial value. We also add an edge from the given node 556 | (node_id) to this initial component node. The edge will later be the basis 557 | for spawning a Subscription. 558 | """ 559 | def handle_call({:add_initial, data, node_id, port, _metadata}, _req, fbp_graph) do 560 | inports = [constant: data] 561 | outports = [value: :any] 562 | label = %{component: "ElixirFBP.InitialInformationPacket", 563 | component_pids: [], 564 | inports: inports, inport_types: [:any], metadata: %{:number_of_processes => 1}, 565 | outports: outports, outport_types: []} 566 | iip_node_id = node_id <> "_" <> Atom.to_string(port) 567 | iip_node = :digraph.add_vertex(fbp_graph.graph, iip_node_id, label) 568 | # Now construct an edge between this IIP node and the node that will be 569 | # initialized - designated by node_id 570 | edge_label = %{src_port: :value, 571 | tgt_port: port, 572 | subscription_pid: nil} 573 | new_edge = :digraph.add_edge( 574 | fbp_graph.graph, 575 | iip_node_id, 576 | node_id, 577 | edge_label) 578 | {:reply, iip_node, fbp_graph} 579 | end 580 | 581 | @doc """ 582 | Callback implementation for ElixirFBP.Graph.remove_initial() 583 | """ 584 | def handle_call({:remove_initial, node_id, port, secret}, _req, fbp_graph) do 585 | {node_id, label} = :digraph.vertex(fbp_graph.graph, node_id) 586 | inports = label.inports 587 | new_inports = Keyword.put(inports, port, nil) 588 | new_label = %{label | :inports => new_inports} 589 | :digraph.add_vertex(fbp_graph.graph, node_id, new_label) 590 | {:reply, nil, fbp_graph} 591 | end 592 | 593 | @doc """ 594 | Callback implementation for ElixirFBP.Graph.stop() 595 | Stop the execution of a graph. 596 | Unregister and kill all of the node processes. 597 | """ 598 | def handle_call(:stop_graph, _req, fbp_graph) do 599 | reg_name = fbp_graph.registered_name 600 | nodes = :digraph.vertices(fbp_graph.graph) 601 | Enum.each(nodes, fn(node) -> 602 | {node_id, label} = :digraph.vertex(fbp_graph.graph, node) 603 | ElixirFBP.Component.stop(reg_name, node_id, label) end) 604 | new_fbp_graph = %ElixirFBP.Graph{fbp_graph | running: false, started: false} 605 | {:reply, :ok, new_fbp_graph} 606 | end 607 | 608 | @doc """ 609 | Callback implementation for stopping this GenServer. 610 | """ 611 | def handle_call(:stop, _req, fbp_graph) do 612 | # Graph.stop(fbp_graph.registered_name) 613 | {:stop, :normal, :ok, fbp_graph} 614 | end 615 | 616 | @doc """ 617 | Callback implementation triggered by asking the GenServer to :stop 618 | """ 619 | def terminate(_reason, _fbp_graph) do 620 | :ok 621 | end 622 | 623 | def verify_out_ports(graph) do 624 | nodes = :digraph.vertices(graph) 625 | Enum.reduce(nodes, [], fn(node, node_results) -> 626 | {_,%{outports: outports} = label} = :digraph.vertex(graph, node) 627 | if length(outports) > 0 do 628 | out_ports = :digraph.out_edges(graph, node) 629 | case out_ports do 630 | [] -> 631 | [{:error, "no outports are connected"} | node_results] 632 | _ -> 633 | o_p_results = Enum.reduce(out_ports, [], fn(out_port, out_port_results) -> 634 | case :digraph.edge(graph, out_port) do 635 | {_, _, nil, %{src_port: src_port} = label} -> 636 | [{:error, src_port} | out_port_results] 637 | _ -> 638 | out_port_results 639 | end 640 | end) 641 | case o_p_results do 642 | [] -> node_results 643 | _ -> [o_p_results | node_results] 644 | end 645 | end 646 | else 647 | node_results 648 | end 649 | end) 650 | end 651 | 652 | defp convert_to_type(:integer, data) when is_bitstring(data) do 653 | case Integer.parse(data) do 654 | {value, _} -> value 655 | :error -> {:error, "Invalid integer"} 656 | end 657 | end 658 | 659 | defp convert_to_type(:integer, data) when is_integer(data) do 660 | data 661 | end 662 | defp convert_to_type(:string, data) when is_bitstring(data) do 663 | data 664 | end 665 | defp convert_to_type(:string, data) do 666 | inspect data 667 | end 668 | defp convert_to_type(:pid, data) when is_pid(data) do 669 | data 670 | end 671 | end 672 | -------------------------------------------------------------------------------- /lib/elixirFBP/initial_information_packet.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirFBP.InitialInformationPacket do 2 | @moduledoc """ 3 | A special component that can deliver a constant anytime it is sent 4 | a :value message. 5 | """ 6 | @behaviour ElixirFBP.Behaviour 7 | 8 | def description, do: "An Initial Information Packet producer" 9 | def inports, do: [constant: :any] 10 | def outports, do: [value: :any] 11 | 12 | def loop(inports, outports) do 13 | %{:constant => value} = inports 14 | receive do 15 | :value -> 16 | send(outports[:value], {:value, value}) 17 | loop(inports, outports) 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/elixirFBP/network.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirFBP.Network do 2 | @moduledoc """ 3 | ElxirFBP.Network is a GenServer that provides support for starting and stopping a 4 | FBP network, and finding out about its state. The network keeps 5 | a dictionary of graphs ids, each of which points to an ElixirFBP.Graph structure. 6 | Graphs are also implemented as GenServers 7 | 8 | Functions supported by this module are based on NoFlo's FBP Network Protocol, 9 | specifically the network sub-protocol. See http://noflojs.org/documentation/protocol/ 10 | for the details. There is one exception: the clear graph command is implemented here. 11 | 12 | There is a function - remove_graph - that is not part of the Network Protocol. 13 | 14 | This module is registered with its module name. 15 | 16 | TODO: Finish implementation of data function 17 | 18 | """ 19 | defstruct [ 20 | graph_reg_names: HashDict.new, # graph id => registered name 21 | debug: false 22 | ] 23 | 24 | #This module's behaviour 25 | use GenServer 26 | 27 | alias ElixirFBP.Graph 28 | 29 | ######################################################################## 30 | # The External API 31 | 32 | @doc """ 33 | Starts things off with the creation of the empty state. 34 | """ 35 | def start_link do 36 | case GenServer.start_link(__MODULE__, [], name: __MODULE__) do 37 | {:ok, pid} -> pid 38 | {:error, {:already_started, pid}} -> pid 39 | end 40 | end 41 | 42 | @doc """ 43 | Clear adds a new graph in the network. This command is part of the Graph protocol 44 | but here it is implemented as a network command because it seems like a better fit. 45 | """ 46 | def clear(graph_id, parameters \\ %{}) do 47 | GenServer.call(__MODULE__, {:clear, graph_id, parameters}) 48 | end 49 | 50 | @doc """ 51 | Retrieve the registered graph name if it exists. A call to clear will have created 52 | and registered it. 53 | """ 54 | def get_graph(graph_id) do 55 | GenServer.call(__MODULE__, {:get_graph, graph_id}) 56 | end 57 | 58 | @doc """ 59 | Set the network debug switch on or off 60 | """ 61 | def set_debug(value) do 62 | GenServer.call(__MODULE__, {:set_debug, value}) 63 | end 64 | 65 | @doc """ 66 | Remove a graph from this network. 67 | """ 68 | def remove_graph(graph_id) do 69 | GenServer.call(__MODULE__, {:remove_graph, graph_id}) 70 | end 71 | 72 | @doc """ 73 | Start execution of a graph, optionally specifing whether it is to 74 | run in either :pull or :push (default) mode. 75 | """ 76 | def start(graph_id, run_mode \\ :push) do 77 | GenServer.call(__MODULE__, {:start, graph_id, run_mode}) 78 | end 79 | 80 | @doc """ 81 | Stop the execution of the graph 82 | """ 83 | def stop(graph_id) do 84 | GenServer.call(__MODULE__, {:stop, graph_id}) 85 | end 86 | 87 | @doc """ 88 | Get the current status of a graph 89 | """ 90 | def get_status(graph_id, secret \\ nil) do 91 | GenServer.call(__MODULE__, {:get_status, graph_id, secret}) 92 | end 93 | 94 | @doc """ 95 | Data transmission on an edge. 96 | """ 97 | def data(graph_id, edge_id, src, tgt, subgraph \\ nil) do 98 | GenServer.cast(__MODULE__, {:data, graph_id, edge_id, src, tgt, subgraph}) 99 | end 100 | 101 | @doc """ 102 | Stop the Network GenServer process 103 | """ 104 | def stop do 105 | GenServer.call(__MODULE__, :stop) 106 | end 107 | 108 | ######################################################################## 109 | # The GenServer implementations 110 | 111 | @doc """ 112 | Callback implementation for ElixirFBP.Network.start_link() 113 | Initialize the state - no graphs. There are no initialization args. 114 | """ 115 | def init(_args) do 116 | {:ok, %ElixirFBP.Network{}} 117 | end 118 | 119 | @doc """ 120 | Clear (initialize) a graph in this network. If the graph's GenServer has 121 | not been started and registered, do so. 122 | """ 123 | def handle_call({:clear, graph_id, parameters}, _req, network) do 124 | HashDict.get(network.graph_reg_names, graph_id) 125 | |> handle_call_clear(graph_id, parameters, network) 126 | end 127 | 128 | @doc """ 129 | Callback implementation of ElxirFBP.Network.get_graph() 130 | """ 131 | def handle_call({:get_graph, graph_id}, _req, network) do 132 | registered_name = HashDict.get(network.graph_reg_names, graph_id) 133 | {:reply, {:ok, registered_name}, network} 134 | end 135 | 136 | @doc """ 137 | Callback implementation of ElixirFBP.Network.set_debug() 138 | """ 139 | def handle_call({:set_debug, value}, _req, network) when is_boolean(value) do 140 | {:reply, :ok, %ElixirFBP.Network{network | debug: value}} 141 | end 142 | 143 | @doc """ 144 | Remove a graph (%ElixirFBP.Graph structure) from the networks dictionary of graphs. 145 | """ 146 | def handle_call({:remove_graph, graph_id}, _req, network) do 147 | new_graph_reg_names = HashDict.delete(network.graph_reg_names, graph_id) 148 | {:reply, :ok, 149 | %ElixirFBP.Network{network | graph_reg_names: new_graph_reg_names}} 150 | end 151 | 152 | @doc """ 153 | Callback implementation for ElixirFBP.Network.start() 154 | """ 155 | def handle_call({:start, graph_id, run_mode}, _req, network) do 156 | reg_name = HashDict.get(network.graph_reg_names, graph_id) 157 | if ! reg_name do 158 | {:reply, {:error, "Graph does not exist"}, network} 159 | else 160 | case Graph.start(reg_name, run_mode) do 161 | {:ok} -> 162 | {:reply, :ok, network} 163 | error -> 164 | {:reply, error, network} 165 | end 166 | end 167 | end 168 | 169 | @doc """ 170 | Callback implementation for ElixirFBP.Network.get_status 171 | """ 172 | def handle_call({:get_status, graph_id, _secret}, _req, network) do 173 | reg_name = HashDict.get(network.graph_reg_names, graph_id) 174 | status = Graph.get_status(reg_name) 175 | {:reply, status, network} 176 | end 177 | 178 | @doc """ 179 | Callback implementation for stopping the Network - Note that this is different 180 | than the stop function. 181 | """ 182 | def handle_call(:stop, _req, network) do 183 | {:stop, :normal, :ok, network} 184 | end 185 | 186 | @doc """ 187 | Callback implementation for ElixirFBP.Network.stop() 188 | Stop the execution of a graph identified by its id (string). 189 | """ 190 | def handle_call({:stop, graph_id}, _req, network) do 191 | reg_name = HashDict.get(network.graph_reg_names, graph_id) 192 | Graph.stop_graph(reg_name) 193 | {:reply, :ok, network} 194 | end 195 | 196 | @doc """ 197 | Callback implementation for ElixirFBP.Network.data 198 | """ 199 | def handle_cast({:data, _graph_id, _edge_id, _src, _tgt, _subgraph}, network) do 200 | {:noreply, network} 201 | end 202 | 203 | @doc """ 204 | Callback implmentation for having asked the GenServer to stop processing 205 | """ 206 | def terminate(_reason, network) do 207 | # Stop the Graph GenServers 208 | Enum.each(HashDict.values(network.graph_reg_names), fn(reg_name) -> 209 | Graph.stop(reg_name) 210 | end) 211 | :ok 212 | end 213 | 214 | defp handle_call_clear(nil, graph_id, parameters, network) do 215 | {:ok, registered_name} = Graph.start_link(graph_id, parameters) 216 | new_graph_reg_names = 217 | HashDict.put(network.graph_reg_names, graph_id, registered_name) 218 | {:reply, {:ok, registered_name}, 219 | %ElixirFBP.Network{network | graph_reg_names: new_graph_reg_names}} 220 | end 221 | 222 | defp handle_call_clear(registered_name, _graph_id, parameters, network) do 223 | Graph.set_parameters(registered_name, parameters) 224 | {:reply, {:ok, registered_name}, network} 225 | end 226 | 227 | end 228 | -------------------------------------------------------------------------------- /lib/elixirFBP/subscription.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirFBP.Subscription do 2 | @moduledoc """ 3 | A Subscription serves as a conduit and a control mechanism for the delivery of 4 | data from Publishers (Components) to Subscribers (Components). A Subscriber 5 | must specify how many IPs it is willing to receive via a {:request, n} message 6 | where n is some integer or the atom :infinity. The Subscription will ensure that 7 | no more IPs than have been asked for will be sent. 8 | 9 | When a subscriber asks for an infinite number of values via a {:request, :infinity} 10 | message, the Subscription effectively becoming a push flow without any back pressure. 11 | 12 | A Subscription is able to deal with multiple Publisher and/or Subscriber 13 | Component processes. Values are devlivered to multiple Subscriber processes 14 | in a round-robin manner. 15 | 16 | The design of this module borrows ideas and terminology from the Reactive Stream 17 | project: http://www.reactive-streams.org/ 18 | """ 19 | require Logger 20 | 21 | defstruct [ 22 | publisher_pids: [], publisher_port: nil, 23 | subscriber_pids: {}, subscriber_pids_length: 0, subscriber_port: nil, 24 | capacity: :infinity, count: 0 25 | ] 26 | @doc """ 27 | The new function doesn't do much except initialize a Subscription structure 28 | with values for the names of the publisher and subscriber ports that this 29 | subscription will connect to and manage. Also see the start() function below. 30 | The initial value for a subscription's capacity is set to :infinity. 31 | """ 32 | def new(publisher_port, subscriber_port) do 33 | %ElixirFBP.Subscription{ 34 | publisher_pids: [], 35 | publisher_port: publisher_port, 36 | subscriber_pids: {}, 37 | subscriber_pids_length: 0, 38 | subscriber_port: subscriber_port, 39 | capacity: :infinity, 40 | count: 0 41 | } 42 | end 43 | 44 | @doc """ 45 | The new function doesn't do much except initialize a Subscription structure 46 | with values for the names of the publisher and subscriber ports that this 47 | subscription will connect to and manage. Also see the start() function below. 48 | An initial value for the subscription's capacity is supplied. 49 | """ 50 | def new(publisher_port, subscriber_port, capacity) do 51 | %ElixirFBP.Subscription{ 52 | publisher_pids: [], 53 | publisher_port: publisher_port, 54 | subscriber_pids: {}, 55 | subscriber_pids_length: 0, 56 | subscriber_port: subscriber_port, 57 | capacity: capacity, 58 | count: 0 59 | } 60 | end 61 | 62 | @doc """ 63 | The start function does nothing more than spawn a Subscription process. The other 64 | values in the Subscription structure are initialized after the Components that 65 | are connected to this subscription have been started. See Component.start(); 66 | it is then that we know how many subscriber and publisher processes are 67 | attached to this subscription. 68 | """ 69 | def start(inport, outport) do 70 | subscription = ElixirFBP.Subscription.new(inport, outport) 71 | spawn(fn -> loop(subscription, 0) end) 72 | end 73 | 74 | def start(inport, outport, capacity) do 75 | subscription = ElixirFBP.Subscription.new(inport, outport, capacity) 76 | spawn(fn -> loop(subscription, 0) end) 77 | end 78 | 79 | @doc """ 80 | This function serves as a Subscriptions's main computational loop, dealing 81 | with requests for data from Subscribers and responses from Publishers. The 82 | subscriber_index points to the next subscriber that is to receive data. 83 | 84 | The subscriber and publisher processes are started with Component.start. after 85 | starting a component's process(es), the function sends lists of process pids 86 | as messages to this subscription process. 87 | """ 88 | def loop(%ElixirFBP.Subscription{ 89 | publisher_pids: publisher_pids, 90 | publisher_port: publisher_port, 91 | subscriber_pids: subscriber_pids, 92 | subscriber_pids_length: subscriber_pids_length, 93 | subscriber_port: subscriber_port, 94 | capacity: capacity, 95 | count: count} = subscription, 96 | subscriber_index) do 97 | receive do 98 | # The next two messages serve to update the processor pids for 99 | # the publisher and subscriber components. Notice that the publisher pids 100 | # are in a List while the subscriber pids are in a tuple. We use a tuple 101 | # so that we can efficiently send a value to the nth subscriber process 102 | {:publisher_pids, publisher_pids} -> 103 | new_subscription = %{subscription | :publisher_pids => publisher_pids} 104 | loop(new_subscription, subscriber_index) 105 | {:subscriber_pids, subscriber_pids} -> 106 | new_subscription = %{subscription | :subscriber_pids => subscriber_pids, 107 | :subscriber_pids_length => tuple_size(subscriber_pids)} 108 | loop(new_subscription, subscriber_index) 109 | # Ask the publisher for capacity many values. Used during the starting 110 | # of a graph in the pull run mode. 111 | :request when is_integer(capacity) -> 112 | new_subscription = %{subscription | 113 | :count => 0} 114 | Stream.cycle(publisher_pids) # Cycle limited by capacity 115 | |> Stream.take(capacity) 116 | |> Enum.each(fn(publisher_pid) -> 117 | send(publisher_pid, publisher_port) 118 | end) 119 | loop(new_subscription, subscriber_index) 120 | :request when capacity == :infinity-> 121 | new_subscription = %{subscription | :count => 0} 122 | Enum.each(publisher_pids, fn(publisher_pid) -> 123 | # IO.puts("sending publisher_port #{inspect publisher_port} publisher_pid #{inspect publisher_pid}") 124 | send(publisher_pid, publisher_port) 125 | end) 126 | loop(new_subscription, subscriber_index) 127 | # Change the capacity and reset the count to zero. And finally ask The 128 | # publisher for data 129 | {:request, capacity} -> 130 | new_subscription = %{subscription | :capacity => capacity, :count => 0} 131 | Stream.cycle(publisher_pids) # Cycle limited by capacity 132 | |> Stream.take(capacity) 133 | |> Enum.each(fn(publisher_pid) -> 134 | send(publisher_pid, publisher_port) 135 | end) 136 | loop(new_subscription, subscriber_index) 137 | # Publisher has sent data - pass it on to the next subscriber 138 | {^publisher_port, value} when capacity == :infinity -> 139 | subscriber_pid = elem(subscriber_pids, subscriber_index) 140 | message = {subscriber_port, value} 141 | send(subscriber_pid, message) 142 | loop(subscription, rem(subscriber_index + 1, subscriber_pids_length)) 143 | # Publisher has sent data - pass it on to the next subscriber and count 144 | # up to the current capacity. 145 | {^publisher_port, value} when capacity < count -> 146 | send(elem(subscriber_pids, subscriber_index), {subscriber_port, value}) 147 | new_subscription = %{subscription | :count => count + 1} 148 | loop(new_subscription, rem(subscriber_index + 1, subscriber_pids_length)) 149 | # Publisher has sent data - pass it on to the subscriber, but we have 150 | # reached capacity - ask for more 151 | {^publisher_port, value} -> 152 | send(elem(subscriber_pids, subscriber_index), {subscriber_port, value}) 153 | Stream.cycle(publisher_pids) # cycle limited by capacity 154 | |> Stream.take(capacity) 155 | |> Enum.each(fn(publisher_pid) -> 156 | send(publisher_pid, publisher_port) 157 | end) 158 | new_subscription = %{subscription | :count => 0} 159 | loop(new_subscription, rem(subscriber_index + 1, subscriber_pids_length)) 160 | # Cancel this subscription - break out of the receiving loop. 161 | :cancel -> 162 | nil 163 | # Some unknown message has been received. 164 | message -> 165 | Logger.info("Received unknown message: #{inspect message}") 166 | loop(subscription, subscriber_index) 167 | end 168 | end 169 | end 170 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirFBP.Mixfile do 2 | # alias ElixirFBP.Network 3 | # alias ElixirFBP.Graph 4 | 5 | use Mix.Project 6 | 7 | def project do 8 | [app: :elixirFBP, 9 | version: "0.0.1", 10 | elixir: "~> 1.1", 11 | name: "ElixirFBP", 12 | source_url: "https://github.com/pcmarks/ElixirFBP", 13 | deps: deps] 14 | end 15 | 16 | # Configuration for the OTP application 17 | # 18 | # Type `mix help compile.app` for more information 19 | def application do 20 | [applications: [:logger], 21 | registered: [ElixirFBP.Network, ElixirFBP.Graph]] 22 | end 23 | 24 | # Dependencies can be Hex packages: 25 | # 26 | # {:mydep, "~> 0.3.0"} 27 | # 28 | # Or git/path repositories: 29 | # 30 | # {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"} 31 | # 32 | # Type `mix help deps` for more examples and options 33 | defp deps do 34 | [{:earmark, "~> 0.1"}, 35 | {:ex_doc, github: "elixir-lang/ex_doc"}] 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{"cowboy": {:hex, :cowboy, "1.0.0"}, 2 | "cowlib": {:hex, :cowlib, "1.0.1"}, 3 | "earmark": {:hex, :earmark, "0.1.17"}, 4 | "ex_doc": {:git, "https://github.com/elixir-lang/ex_doc.git", "63342194e8a572485b37b4a58668f9b43ab7ab26", []}, 5 | "hackney": {:hex, :hackney, "1.1.0"}, 6 | "httpoison": {:hex, :httpoison, "0.6.2"}, 7 | "idna": {:hex, :idna, "1.0.2"}, 8 | "poison": {:hex, :poison, "1.4.0"}, 9 | "ranch": {:hex, :ranch, "1.0.0"}, 10 | "ssl_verify_hostname": {:hex, :ssl_verify_hostname, "1.0.4"}, 11 | "timex": {:hex, :timex, "0.13.4"}} 12 | -------------------------------------------------------------------------------- /test/ElixirFBP_network_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirFBPNetworkTest do 2 | 3 | use ExUnit.Case, async: false 4 | 5 | alias ElixirFBP.Graph 6 | alias ElixirFBP.Network 7 | 8 | import ExUnit.CaptureLog 9 | 10 | @graph_1 "graph_1" 11 | @node_1 "node_1" 12 | @node_2 "node_2" 13 | 14 | test "Start and stop the FBP Network" do 15 | Network.start_link 16 | assert :ok == Network.stop 17 | end 18 | 19 | test "Create an ElixirFBP Graph" do 20 | Network.start_link 21 | {:ok, _fbp_graph = Network.clear(@graph_1)} 22 | status = Network.get_status(@graph_1) 23 | assert status == {false, false} 24 | # Make sure the graph and network are stopped 25 | Network.stop(@graph_1) 26 | assert :ok == Network.stop 27 | end 28 | 29 | test "Create and start an ElxirFBP Graph with defaults (push mode)" do 30 | Network.start_link 31 | {:ok, fbp_graph} = Network.clear(@graph_1) 32 | Graph.add_node(fbp_graph, @node_1, "Math.Add") 33 | Graph.add_node(fbp_graph, @node_2, "Core.Log") 34 | _edge = Graph.add_edge( 35 | fbp_graph, 36 | @node_1, :sum, 37 | @node_2, :in_port) 38 | 39 | Graph.add_initial(fbp_graph, 42, @node_1, :addend) 40 | Graph.add_initial(fbp_graph, 24, @node_1, :augend) 41 | assert capture_log(fn -> 42 | Network.start(@graph_1) 43 | # Need a slight delay to allow for the log message to shou up 44 | :timer.sleep(5) 45 | end) =~ "66" 46 | {true, true} = Network.get_status(@graph_1) 47 | # Make sure the graph and network are stopped 48 | Network.stop(@graph_1) 49 | end 50 | 51 | test "Create and start an ElxirFBP Graph in push mode" do 52 | Network.start_link 53 | {:ok, fbp_graph} = Network.clear(@graph_1) 54 | Graph.add_node(fbp_graph, @node_1, "Math.Add") 55 | Graph.add_node(fbp_graph, @node_2, "Core.Log") 56 | _edge = Graph.add_edge( 57 | fbp_graph, 58 | @node_1, :sum, 59 | @node_2, :in_port) 60 | 61 | Graph.add_initial(fbp_graph, 53, @node_1, :addend) 62 | Graph.add_initial(fbp_graph, 35, @node_1, :augend) 63 | assert capture_log(fn -> 64 | Network.start(@graph_1, :push) 65 | # Need a slight delay to allow for the log message to shou up 66 | :timer.sleep(5) 67 | end) =~ "88" 68 | {true, true} = Network.get_status(@graph_1) 69 | # Make sure the graph and network are stopped 70 | Network.stop(@graph_1) 71 | end 72 | 73 | test "Create and start an ElxirFBP Graph in pull mode" do 74 | Network.start_link 75 | {:ok, fbp_graph} = Network.clear(@graph_1) 76 | Graph.add_node(fbp_graph, @node_1, "Math.Add") 77 | Graph.add_node(fbp_graph, @node_2, "Core.Log") 78 | _edge = Graph.add_edge( 79 | fbp_graph, 80 | @node_1, :sum, 81 | @node_2, :in_port) 82 | 83 | Graph.add_initial(fbp_graph, 32, @node_1, :addend) 84 | Graph.add_initial(fbp_graph, 23, @node_1, :augend) 85 | assert capture_log(fn -> 86 | Network.start(@graph_1, :pull) 87 | # Need a slight delay to allow for the log message to shou up 88 | :timer.sleep(5) 89 | end) =~ "55" 90 | {true, true} = Network.get_status(@graph_1) 91 | # Make sure the graph and network are stopped 92 | Network.stop(@graph_1) 93 | end 94 | 95 | end 96 | -------------------------------------------------------------------------------- /test/elixirFBP_component_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirFBPComponentTest do 2 | use ExUnit.Case, async: false 3 | 4 | alias ElixirFBP.Graph 5 | alias ElixirFBP.Component 6 | alias ElixirFBP.Network 7 | 8 | # Testing parameters 9 | @graph_1 "graph_c1" 10 | @graph_2 "graph_c2" 11 | @node_1 "node_1" 12 | @node_2 "node_2" 13 | 14 | test "Start a component and see if it's alive!" do 15 | Network.start_link 16 | {:ok, fbp_graph_reg_name} = Network.clear(@graph_1) 17 | Graph.add_node(fbp_graph_reg_name, @node_1, "Math.Add") 18 | Graph.add_node(fbp_graph_reg_name, @node_2, "Math.Add") 19 | _edge = Graph.add_edge( 20 | fbp_graph_reg_name, 21 | @node_1, :sum, 22 | @node_2, :addend) 23 | {_node_id, label} = Graph.get_node(fbp_graph_reg_name, @node_1) 24 | Graph.get(fbp_graph_reg_name) 25 | Component.stop(fbp_graph_reg_name, @node_1, label) 26 | Network.stop(@graph_1) 27 | Network.stop 28 | end 29 | 30 | test "Start a component with initial values" do 31 | Network.start_link 32 | {:ok, fbp_graph_reg_name} = Network.clear(@graph_2) 33 | Graph.add_node(fbp_graph_reg_name, @node_1, "Math.Add") 34 | Graph.add_node(fbp_graph_reg_name, @node_2, "Math.Add") 35 | _edge = Graph.add_edge( 36 | fbp_graph_reg_name, 37 | @node_1, :sum, 38 | @node_2, :addend) 39 | Graph.add_initial(fbp_graph_reg_name, 42, @node_1, :augend) 40 | Graph.add_initial(fbp_graph_reg_name, 42, @node_1, :addend) 41 | Network.stop(@graph_2) 42 | Network.stop 43 | end 44 | 45 | end 46 | -------------------------------------------------------------------------------- /test/elixirFBP_graph_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirFBPGraphTest do 2 | use ExUnit.Case, async: false 3 | 4 | alias ElixirFBP.Graph 5 | alias ElixirFBP.Network 6 | 7 | # Testing parameters 8 | @graph_1 "graph_g1" 9 | @node_1 "node_1" 10 | @node_2 "node_2" 11 | 12 | test "Create and persist a graph with default metadata" do 13 | {:ok, fbp_graph_reg_name} = Graph.start_link(@graph_1) 14 | fbp_graph = Graph.get(fbp_graph_reg_name) 15 | assert :digraph == elem(fbp_graph.graph, 0) 16 | end 17 | 18 | test "Clear an FBP graph" do 19 | Network.start_link 20 | {:ok, fbp_graph_reg_name} = Network.clear(@graph_1) 21 | fbp_graph = Graph.get(fbp_graph_reg_name) 22 | assert :digraph.no_edges(fbp_graph.graph) == 0 23 | assert :digraph.no_vertices(fbp_graph.graph) == 0 24 | Network.stop(@graph_1) 25 | assert :ok = Network.stop 26 | end 27 | 28 | test "Clear an FBP graph and change its description" do 29 | Network.start_link 30 | {:ok, fbp_graph_reg_name} =Network.clear(@graph_1) 31 | fbp_graph = Graph.get(fbp_graph_reg_name) 32 | assert nil == fbp_graph.description 33 | {:ok, ^fbp_graph_reg_name} = 34 | Network.clear(@graph_1, %{description: "this is a test"}) 35 | fbp_graph = Graph.get(fbp_graph_reg_name) 36 | assert "this is a test" == fbp_graph.description 37 | Network.stop(@graph_1) 38 | assert :ok = Network.stop 39 | end 40 | 41 | test "Add a node" do 42 | {:ok, fbp_graph_reg_name} = Graph.start_link(@graph_1) 43 | node_id = Graph.add_node(fbp_graph_reg_name, @node_1, "Math.Add") 44 | nodes = Graph.nodes(fbp_graph_reg_name) 45 | assert node_id in nodes == true 46 | fbp_graph = Graph.get(fbp_graph_reg_name) 47 | {_node_id, label} = :digraph.vertex(fbp_graph.graph, node_id) 48 | assert label.inports == [{:addend, nil}, {:augend, nil}] 49 | end 50 | 51 | test "Add a node; check default metadata" do 52 | {:ok, fbp_graph_reg_name} = Graph.start_link(@graph_1) 53 | node_id = Graph.add_node(fbp_graph_reg_name, 54 | @node_1, 55 | "Math.Add") 56 | nodes = Graph.nodes(fbp_graph_reg_name) 57 | assert node_id in nodes == true 58 | fbp_graph = Graph.get(fbp_graph_reg_name) 59 | {_node_id, label} = :digraph.vertex(fbp_graph.graph, node_id) 60 | assert label.inports == [{:addend, nil}, {:augend, nil}] 61 | assert Map.fetch!(label.metadata, :number_of_processes) == 1 62 | end 63 | 64 | test "Add a node with metadata" do 65 | {:ok, fbp_graph_reg_name} = Graph.start_link(@graph_1) 66 | node_id = Graph.add_node(fbp_graph_reg_name, 67 | @node_1, 68 | "Math.Add", 69 | %{number_of_processes: 4}) 70 | nodes = Graph.nodes(fbp_graph_reg_name) 71 | assert node_id in nodes == true 72 | fbp_graph = Graph.get(fbp_graph_reg_name) 73 | {_node_id, label} = :digraph.vertex(fbp_graph.graph, node_id) 74 | assert label.inports == [{:addend, nil}, {:augend, nil}] 75 | assert Map.fetch!(label.metadata, :number_of_processes) == 4 76 | end 77 | 78 | test "Get the info associated with a node" do 79 | {:ok, fbp_graph_reg_name} = Graph.start_link(@graph_1) 80 | node_id = Graph.add_node(fbp_graph_reg_name, @node_1, "Math.Add") 81 | {_node_id, label} = Graph.get_node(fbp_graph_reg_name, node_id) 82 | assert label.component == "Math.Add" 83 | end 84 | 85 | test "Remove a node" do 86 | {:ok, fbp_graph_reg_name} = Graph.start_link(@graph_1) 87 | Graph.add_node(fbp_graph_reg_name, @node_1, "Math.Add") 88 | result = Graph.remove_node(fbp_graph_reg_name, @node_1) 89 | assert result == true 90 | nodes = Graph.nodes(fbp_graph_reg_name) 91 | assert @node_1 in nodes == false 92 | end 93 | 94 | test "Rename a node" do 95 | {:ok, fbp_graph_reg_name} = Graph.start_link(@graph_1) 96 | Graph.add_node(fbp_graph_reg_name, @node_1, "Math.Add") 97 | Graph.rename_node(fbp_graph_reg_name, @node_1, "new_name", "secret") 98 | fbp_graph = Graph.get(fbp_graph_reg_name) 99 | {node_id, _label} = :digraph.vertex(fbp_graph.graph, "new_name") 100 | assert node_id == "new_name" 101 | end 102 | 103 | test "Change the metadata in a node" do 104 | {:ok, fbp_graph_reg_name} = Graph.start_link(@graph_1) 105 | Graph.add_node(fbp_graph_reg_name, @node_1, "Math.Add", %{foobar: 33}) 106 | Graph.change_node(fbp_graph_reg_name, @node_1, %{foobar: 42}, "secret") 107 | fbp_graph = Graph.get(fbp_graph_reg_name) 108 | {_node_id, label} = :digraph.vertex(fbp_graph.graph, @node_1) 109 | assert Map.has_key?(label.metadata, :foobar) == true 110 | assert Map.get(label.metadata, :foobar) == 42 111 | end 112 | 113 | test "Add an edge between two nodes" do 114 | {:ok, fbp_graph_reg_name} = Graph.start_link(@graph_1) 115 | Graph.add_node(fbp_graph_reg_name, @node_1, "Math.Add") 116 | Graph.add_node(fbp_graph_reg_name, @node_2, "Math.Add") 117 | edge = Graph.add_edge( 118 | fbp_graph_reg_name, 119 | @node_1, :sum, 120 | @node_2, :addend) 121 | fbp_graph = Graph.get(fbp_graph_reg_name) 122 | {_edge_id, node1, node2, label} = :digraph.edge(fbp_graph.graph, edge) 123 | assert node1 == @node_1 124 | assert node2 == @node_2 125 | assert label.src_port == :sum 126 | assert label.tgt_port == :addend 127 | end 128 | 129 | test "Remove an edge between two nodes" do 130 | {:ok, fbp_graph_reg_name} = Graph.start_link(@graph_1) 131 | # First add an edge 132 | Graph.add_node(fbp_graph_reg_name, @node_1, "Math.Add") 133 | Graph.add_node(fbp_graph_reg_name, @node_2, "Math.Add") 134 | _edge = Graph.add_edge( 135 | fbp_graph_reg_name, 136 | @node_1, :sum, 137 | @node_2, :addend) 138 | # Now remove it 139 | result = Graph.remove_edge( 140 | fbp_graph_reg_name, 141 | @node_1, :sum, 142 | @node_2, :addend) 143 | assert result == true 144 | end 145 | 146 | test "Add an initial value to a node port without conversion" do 147 | {:ok, fbp_graph_reg_name} = Graph.start_link(@graph_1) 148 | Graph.add_node(fbp_graph_reg_name, @node_1, "Math.Add") 149 | result = Graph.add_initial(fbp_graph_reg_name, 27, @node_1, :addend) 150 | assert result == @node_1 <> "_" <> "addend" 151 | end 152 | 153 | test "Add an initial value to a node port with conversion" do 154 | {:ok, fbp_graph_reg_name} = Graph.start_link(@graph_1) 155 | Graph.add_node(fbp_graph_reg_name, @node_1, "Math.Add") 156 | result = Graph.add_initial(fbp_graph_reg_name, "27", @node_1, :addend) 157 | assert result == @node_1 <> "_" <> "addend" 158 | end 159 | 160 | test "Remove an initial value from a node port" do 161 | {:ok, fbp_graph_reg_name} = Graph.start_link(@graph_1) 162 | Graph.add_node(fbp_graph_reg_name, @node_1, "Math.Add") 163 | Graph.add_initial(fbp_graph_reg_name, 27, @node_1, :addend) 164 | result = Graph.remove_initial(fbp_graph_reg_name, @node_1, :addend, "none") 165 | assert result == nil 166 | # Double-check that the value is = nil 167 | fbp_graph = Graph.get(fbp_graph_reg_name) 168 | {_node_id, label} = :digraph.vertex(fbp_graph.graph, @node_1) 169 | inports = label.inports 170 | port_value = inports[:addend] 171 | assert port_value == nil 172 | end 173 | end 174 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start(trace: true) 2 | 3 | # Define some basic ElixirFBP Components that will be used in the examples 4 | 5 | defmodule Core.Log do 6 | 7 | @behaviour ElixirFBP.Behaviour 8 | 9 | require Logger 10 | 11 | def description, do: "Log the IP" 12 | def inports, do: [in_port: :string] 13 | def outports, do: [] 14 | 15 | def loop(inports, outports) do 16 | receive do 17 | {:in_port, value} when value != nil -> 18 | Logger.info("#{inspect value}") 19 | loop(inports, outports) 20 | end 21 | end 22 | end 23 | 24 | defmodule Core.Output do 25 | 26 | @behaviour ElixirFBP.Behaviour 27 | 28 | def description, do: "Show the IP on the console" 29 | def inports, do: [in_port: :string] 30 | def outports, do: [] 31 | 32 | def loop(inports, outports) do 33 | receive do 34 | {:in_port, value} when value != nil -> 35 | IO.puts("#{inspect value}") 36 | loop(inports, outports) 37 | end 38 | end 39 | end 40 | 41 | defmodule Math.Add do 42 | @moduledoc """ 43 | This module describes an FBP Component: Math.Add 44 | """ 45 | @behaviour ElixirFBP.Behaviour 46 | 47 | def description, do: "Add two integers" 48 | def inports, do: [addend: :integer, augend: :integer] 49 | def outports, do: [sum: :integer] 50 | 51 | def loop(inports, outports) do 52 | %{:augend => augend, :addend => addend} = inports 53 | receive do 54 | {:addend, value} when is_number(augend)-> 55 | send(outports[:sum], {:sum, value + augend}) 56 | inports = %{inports | :addend => nil, :augend => nil} 57 | loop(inports, outports) 58 | {:addend, value} -> 59 | inports = %{inports | :addend => value} 60 | loop(inports, outports) 61 | {:augend, value} when is_number(addend) -> 62 | send(outports[:sum], {:sum, addend + value}) 63 | inports = %{inports | :addend => nil, :augend => nil} 64 | loop(inports, outports) 65 | {:augend, value} -> 66 | inports = %{inports | :augend => value} 67 | loop(inports, outports) 68 | :sum when is_number(addend) and is_number(augend) -> 69 | send(outports[:sum], {:sum, addend + augend}) 70 | inports = %{inports | :addend => nil, :augend => nil} 71 | loop(inports, outports) 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /ttb_last_config: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pcmarks/ElixirFBP/4a28b44a7a2bed446239c0a425ae3ca7c11458bb/ttb_last_config --------------------------------------------------------------------------------