graphity/lib.rs
1//! Graphity offers tools to model signal flow within directed graphs.
2//!
3//! There are two main parts playing a role here. First are nodes implemented by
4//! the user. These nodes can have arbitrary number of inputs and outputs, and
5//! an optional method performing operations over provided data. The second part
6//! is then a signal graph structure generated by this library. It allows the
7//! user to register previosly defined nodes, wire them up and let signal flow
8//! between them the user to register previosly defined nodes, wire them up and
9//! let signal flow between them.
10//!
11//! All the core concepts are illustrated by the following example. Read through
12//! the documentation of individual structs and traits to learn more.
13//!
14//! # Example
15//!
16//! This example will work with a signal graph containing 3 types of nodes:
17//!
18//! * `Generator` – pushing given value throught its output.
19//! * `Sum` – accepting two inputs and returning the sum of them.
20//! * `Echo` – accepting one input and printing it on the standard output as a
21//! side-effect.
22//!
23//! They will be wired up as following:
24//!
25//! ```text
26//! [Echo]
27//! Λ \ __
28//! | \ / | |
29//! | [Sum] | |
30//! | / \__| V
31//! /
32//! [Generator(1)]
33//! ```
34//!
35//! Generator sends value `1` to the first input of `Sum`. `Sum` sends the
36//! calculated result to its own second input, forming a loop. It will also send
37//! a copy of the result to `Echo`, so we can observe the current value.
38//!
39//! ## Defining nodes
40//!
41//! First, let's define all the modules. We'll start with `Sum` since it covers
42//! most of the important parts:
43//!
44//! ```
45//! # use graphity::Node;
46//! #[derive(Default)]
47//! pub struct Sum {
48//! input1: i32,
49//! input2: i32,
50//! output: i32,
51//! }
52//!
53//! #[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
54//! pub enum SumConsumer {
55//! In1,
56//! In2,
57//! }
58//!
59//! #[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
60//! pub struct SumProducer;
61//!
62//! impl Node<i32> for Sum {
63//! type Consumer = SumConsumer;
64//! type Producer = SumProducer;
65//!
66//! fn write(&mut self, consumer: Self::Consumer, input: i32) {
67//! match consumer {
68//! Self::Consumer::In1 => self.input1 = input,
69//! Self::Consumer::In2 => self.input2 = input,
70//! }
71//! }
72//!
73//! fn read(&self, _producer: Self::Producer) -> i32 {
74//! self.output
75//! }
76//!
77//! fn tick(&mut self) {
78//! self.output = self.input1 + self.input2;
79//! }
80//! }
81//! ```
82//!
83//! There is lot to discuss here, let's split it into parts.
84//!
85//! The first part simply defines the structure of the node. In this case, it
86//! has one field per each input/output to work with:
87//!
88//! ```
89//! #[derive(Default)]
90//! pub struct Sum {
91//! input1: i32,
92//! input2: i32,
93//! output: i32,
94//! }
95//! ```
96//!
97//! Next up, there is the definition of node's consumers. A consumer is simply
98//! an input pin of the node to which we can attach an edge. In this case it is
99//! an enum that can be one of two invariants, one for each input:
100//!
101//! ```
102//! #[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
103//! pub enum SumConsumer {
104//! In1,
105//! In2,
106//! }
107//! ```
108//!
109//! Similarly, node's producer is an output pin. In the case of this node, there
110//! is only one possible output, so we define a primitive struct as the type:
111//!
112//! ```
113//! #[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
114//! pub struct SumProducer;
115//! ```
116//!
117//! Each node provided by the user must implement the [Node
118//! trait](trait.Node.html). You can see that this is where we link previously
119//! defined consumers and producers. Note that the node is implemented for type
120//! `i32`. That is the payload type that will be used for data flowing between
121//! nodes. All consumers and producers within a graph must work with this type:
122//!
123//! ```ignore
124//! impl Node<i32> for Sum {
125//! type Consumer = SumConsumer;
126//! type Producer = SumProducer;
127//! // ...
128//! }
129//! ```
130//!
131//! The `write` method will be used to provide data to the node. It passes the
132//! assigned `Consumer` to specify to which consumer is the data meant for. In
133//! this case, our `Sum` node has two inputs, one for each number to be summed:
134//!
135//! ```ignore
136//! fn write(&mut self, consumer: Self::Consumer, input: i32) {
137//! match consumer {
138//! Self::Consumer::In1 => self.input1 = input,
139//! Self::Consumer::In2 => self.input2 = input,
140//! }
141//! }
142//! ```
143//!
144//! Its counterpart is the `read` method which will be called from the outside
145//! to read data produced by the node. Since our node has only one producer
146//! available, we can safely ignore the given `producer` value:
147//!
148//! ```ignore
149//! fn read(&self, _producer: Self::Producer) -> i32 {
150//! self.output
151//! }
152//! ```
153//!
154//! Finally, we need to define the `tick` method which will process all the data
155//! set on the input and save the result on the output:
156//!
157//! ```ignore
158//! fn tick(&mut self) {
159//! self.output = self.input1 + self.input2;
160//! }
161//! ```
162//!
163//! The remaining two nodes for completeness:
164//!
165//! ```
166//! # use graphity::Node;
167//! pub struct Generator(i32);
168//!
169//! #[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
170//! pub enum GeneratorConsumer {}
171//!
172//! #[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
173//! pub struct GeneratorProducer;
174//!
175//! impl Node<i32> for Generator {
176//! type Consumer = GeneratorConsumer;
177//! type Producer = GeneratorProducer;
178//!
179//! fn read(&self, _producer: Self::Producer) -> i32 {
180//! self.0
181//! }
182//! }
183//!
184//! #[derive(Default)]
185//! pub struct Echo {
186//! input: i32,
187//! }
188//!
189//! #[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
190//! pub struct EchoConsumer;
191//!
192//! #[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
193//! pub enum EchoProducer {}
194//!
195//! impl Node<i32> for Echo {
196//! type Consumer = EchoConsumer;
197//! type Producer = EchoProducer;
198//!
199//! fn write(&mut self, _consumer: Self::Consumer, input: i32) {
200//! self.input = input;
201//! }
202//!
203//! fn tick(&mut self) {
204//! println!("Echo: {}", self.input);
205//! }
206//! }
207//! ```
208//!
209//! ## Defining the graph
210//!
211//! The nodes do not bring much value on their own. The goal of this library is
212//! to make it easy to model signal flow between these nodes within a graph.
213//!
214//! To build such a graph, all you need to do is to call `graphity`, defining
215//! the name of the generated structure, the type of the payload passed between
216//! nodes and list of the previously defined nodes:
217//!
218//! ```
219//! # use graphity_nodes::*;
220//! # #[macro_use]
221//! # extern crate graphity;
222//! # fn main() {
223//! graphity!(
224//! Graph<i32>;
225//! Generator = {Generator, GeneratorConsumer, GeneratorProducer},
226//! Echo = {Echo, EchoConsumer, EchoProducer},
227//! );
228//! # }
229//! ```
230//!
231//! Note that the macro is called within its own module to prevent conflicts
232//! with the rest of you code.
233//!
234//! ## Wiring it all up
235//!
236//! Finally, let's instantiate such a graph, add nodes, connect them a and let
237//! the signal flow.
238//!
239//! First, let's create an instance of the previously generated graph:
240//!
241//! ```ignore
242//! let mut graph = Graph::new();
243//! ```
244//!
245//! Then add nodes to it. The returned value is actually an index of the stored
246//! node and can be later used to access consumers and producers or remove the
247//! node:
248//!
249//! ```ignore
250//! let generator = graph.add_node(Generator(1));
251//! let sum = graph.add_node(Sum::default());
252//! let echo = graph.add_node(Echo::default());
253//! ```
254//!
255//! As the next step, we can create edges between producers and consumers of
256//! available nodes to form the topology described above:
257//!
258//! ```ignore
259//! graph.add_edge(
260//! generator.producer(GeneratorProducer),
261//! sum.consumer(SumConsumer::In1),
262//! );
263//! graph.add_edge(
264//! sum.producer(SumProducer),
265//! sum.consumer(SumConsumer::In2),
266//! );
267//! graph.add_edge(
268//! sum.producer(SumProducer),
269//! echo.consumer(EchoConsumer),
270//! );
271//! ```
272//!
273//! Once all is wired up, we can trigger tick of the graph. When this happens,
274//! the graph is traversed, all individual nodes ticket and their output passed
275//! on to the input of their connected nodes. In this example, you can see that
276//! the `Echo` node keeps reporting increasing number due to the cycle feeding
277//! back the output of the `Sum` to its input.
278//!
279//! ```ignore
280//! graph.tick();
281//! graph.tick();
282//! graph.tick();
283//! ```
284//!
285//! ```text
286//! Echo: 1
287//! Echo: 2
288//! Echo: 3
289//! ```
290//!
291//! You can find the full executable version of this example under the [project
292//! sources](https://github.com/zlosynth/graphity/tree/main/examples).
293//!
294//! Learn more about individual types in this documentation. Reading about the
295//! generated [`SignalGraph`](signal/struct.SignalGraph.html) would be a good
296//! start.
297
298#![no_std]
299
300extern crate alloc;
301
302pub mod error;
303mod feedback;
304mod graph;
305mod graphity;
306mod internal;
307pub mod node;
308pub mod signal;
309mod sort;
310
311pub use error::Error;
312pub use node::{Node, NodeIndex, NodeWrapper};