moongraph/
tutorial_impl.rs

1#[cfg_attr(doc, aquamarine::aquamarine)]
2/// # Intro to Moongraph 🌙📈
3///
4/// `moongraph` is a library for defining, scheduling and running directed acyclic graphs
5/// with shared resources.
6///
7/// ## DAG
8/// A "DAG" is a directed, acyclic graph.
9/// Within the `moongraph` library, graph nodes are Rust functions and graph edges are the
10/// function's input parameters and output parameters. For example, in the following diagram
11/// we represent nodes as circles and edges as rectangles:
12///
13/// ```mermaid
14/// flowchart LR
15///     A[Input Types] --> B((Rust Function)) --> C[Output Types]
16/// ```
17///
18/// In this way our graphs are "directed" because the output of one function becomes the
19/// input of another function, which leads to a required ordering when executing the graph.
20///
21/// ```mermaid
22/// flowchart LR
23///     A[Input Types] --> B((Function A)) --> C[Output Types] -- Become Input Types --> D((Function B))
24/// ```
25///
26/// "Acyclic" means that the graph cannot contain cycles between nodes.
27/// What this means in `moongraph` is that the result of one function **cannot** be the input to another
28/// function whose result is required input of the first function. For example the following graph would
29/// fail to schedule:
30///
31/// ```mermaid
32/// flowchart LR
33///     A[A Input Types / B Output Types] --> B((Function A)) --> C[A Output Types] --> D((Function B))
34///     D --> A
35/// ```
36///
37/// Obviously this graph cannot be scheduled because it's not possible to determine which function to run
38/// first!
39/// Attempting to run or schedule a graph with cycles will produce an error.
40///
41/// ### The benefit of a DAG
42/// Once a DAG has been defined, it does not change.
43/// For this reason we can create a schedule that runs all nodes in the DAG in an optimal way, running as many nodes as possible in parallel, safely accessing shared resources **without locks** and in a synchronous context.
44/// This makes using DAGs an alternative to async runtimes and an alternative to message passing systems.
45/// DAGs as a main loop are often found in games, simulations and other contexts where reliable, predictable performance is a top concern.
46///
47/// So if you're still interested, let's learn about creating DAGs with `moongraph`.
48///
49/// ## Nodes and Edges
50/// Graph nodes are Rust functions.
51/// There are two requirements for a Rust function to be used as a graph node.
52/// 1. The input parameters must implement [`Edges`](crate::Edges) `+ Any + Send + Sync`.
53/// 2. The output must implement [`NodeResults`](crate::NodeResults) `+ Any + Send + Sync`.
54///
55/// [`Edges`](crate::Edges) is a trait that tells `moongraph` how to construct your node's input
56/// from its internal resources (we'll explain more about that when we get talking about the `Graph` type). For now we just need to know about the three ways to access an input parameter:
57/// 1. By reference
58/// 2. By mutable reference
59/// 3. By **move**
60///
61/// The first two should be familiar from Rust in general and the last is somewhat novel. That is - an input parameter to a node function can be borrowed from the graph, borrowed mutably from the graph, or **moved** out of the graph into the function. To represent each type of access we have three wrappers:
62/// 1. [`View`](crate::View) - allows a node function to borrow from the graph.
63/// 2. [`ViewMut`](crate::ViewMut) - allows a node function to borrow mutably from the graph.
64/// 3. [`Move`](crate::Move) - allows a node function to move a type from the graph into itself.
65///
66/// With these we can construct our input parameters by wrapping them in a tuple.
67/// Here we define a node function that uses all three and possibly returns an error:
68///
69/// ```rust
70/// use moongraph::{View, ViewMut, Move, GraphError};
71///
72/// fn node_b((s, mut u, f): (View<&'static str>, ViewMut<usize>, Move<f32>)) -> Result<(), GraphError> {
73///     *u += f.floor() as usize;
74///     println!("s={s}, u={u}, f={f}");
75///     Ok(())
76/// }
77/// ```
78///
79/// And here we'll define another node that creates `node_b`'s `f32` as a result:
80///
81/// ```rust
82/// # use moongraph::GraphError;
83/// fn node_a(_:()) -> Result<(f32,), GraphError> {
84///     Ok((42.0,))
85/// }
86/// ```
87///
88/// Notice how even though `node_a` doesn't use any input we still have to pass `()`.
89/// Also notice how the result is a tuple even though there's only one element `(f32,)`.
90/// These are both warts due to constraints in Rust's type system.
91/// That's the worst of it though, and it's easy enough to live with.
92///
93/// ## Graph
94/// The top level type you'll interact with is called [`Graph`](crate::Graph).
95/// Now that we have our nodes we can start constructing our graph.
96/// A [`Graph`](crate::Graph) is made up of two main concepts - [`Execution`](crate::Execution) and [`TypeMap`](crate::TypeMap).
97///
98/// [`Execution`](crate::Execution) is a collection of node functions and a schedule that determines their running order.
99///
100/// [`TypeMap`](crate::TypeMap) is a collection of resources (edge types).
101///
102/// For the most part we won't have to interact with either of these.
103/// Instead we'll be adding functions and resources to the graph using `Graph`'s API, which will
104/// do this interaction for us, but it's good to understand the concepts.
105///
106/// So - let's construct a graph using our previously created nodes.
107/// For this we'll use the [`graph!`](crate::graph) macro, but you can use the API directly if you like.
108///
109/// ```rust
110/// use moongraph::{View, ViewMut, Move, GraphError, Graph, graph};
111///
112/// fn node_b((s, mut u, f): (View<&'static str>, ViewMut<usize>, Move<f32>)) -> Result<(), GraphError> {
113///     *u += f.floor() as usize;
114///     println!("s={s}, u={u}, f={f}");
115///     Ok(())
116/// }
117///
118/// fn node_a(_:()) -> Result<(f32,), GraphError> {
119///     Ok((42.0,))
120/// }
121///
122/// let mut graph = graph!(node_b, node_a)
123///     .with_resource("a big blue crystal") // add the &'static str resource
124///     .with_resource(0usize); // add the usize resource
125/// // we don't have to add the f32 resource because it comes as the result of node_a
126/// ```
127///
128/// ### Resources
129/// Input parameters don't always have to come from the results of functions, in fact they
130/// more often come from a set of resources that live inside the graph.
131/// Notice how we added the `&'static str` and `usize` resources to the graph explicitly using [`Graph::with_resource`](crate::Graph::with_resource).
132/// So to recap - edge types are also known as resources and can come from the results of node functions or can be inserted into the graph manually before executing it.
133///
134/// ### Execution
135/// After the graph is built it can be executed.
136/// The order of execution of node functions is determined by the edge types of the functions themselves.
137/// For example, since `node_b` takes as input the results of `node_a`, `node_b` must run _after_ `node_a`.
138/// [`Graph`](crate::Graph) is smart enough to figure out these types of orderings by itself.
139/// It can also determine which node functions access resources _mutably_, and therefore which node functions can be run in parallel.
140/// But there are certain scenarios where type information is not enough to determine ordering.
141/// For example if we were building a game we might need the physics step to run before rendering, but the types may not describe this and so we would have to state that ordering explicitly.
142/// Luckily we can do that using [`graph!`](crate::graph) and the `>` and `<` operators:
143/// ```rust
144/// use moongraph::{Graph, GraphError, graph};
145///
146/// fn physics_step(_:()) -> Result<(), GraphError> {
147///     println!("physics step goes here");
148///     Ok(())
149/// }
150///
151/// fn render(_:()) -> Result<(), GraphError> {
152///     println!("rendering goes here");
153///     Ok(())
154/// }
155///
156/// let graph = graph!(physics_step < render);
157/// ```
158/// Alternatively we can use the [`Graph`](crate::Graph) and [`Node`](crate::Node) APIs to do the same thing:
159/// ```rust
160/// # use moongraph::{IsGraphNode, Graph, GraphError, graph};
161/// #
162/// # fn physics_step(_:()) -> Result<(), GraphError> {
163/// #     println!("physics step goes here");
164/// #     Ok(())
165/// # }
166/// #
167/// # fn render(_:()) -> Result<(), GraphError> {
168/// #     println!("rendering goes here");
169/// #     Ok(())
170/// # }
171/// #
172/// let graph = Graph::default()
173///    .with_node(
174///        physics_step
175///            .into_node()
176///            .with_name("physics_step")
177///            .run_before("render")
178///    )
179///    .with_node(render.into_node().with_name("render"));
180/// ```
181/// You can see that the [`graph!`](crate::graph) macro is doing a lot of heavy lifting.
182///
183/// Now that we have our graph, we can run it:
184/// ```rust
185/// # use moongraph::{IsGraphNode, Graph, GraphError, graph};
186/// #
187/// # fn physics_step(_:()) -> Result<(), GraphError> {
188/// #     println!("physics step goes here");
189/// #     Ok(())
190/// # }
191/// #
192/// # fn render(_:()) -> Result<(), GraphError> {
193/// #     println!("rendering goes here");
194/// #     Ok(())
195/// # }
196/// #
197/// let mut graph = graph!(physics_step < render);
198/// graph.run().unwrap();
199/// ```
200///
201/// ## Full example
202/// Here's another example where we'll implement a number of physics steps followed by an unrelated banking step.
203/// After constructing the graph, we'll run the graph and also print out the schedule.
204/// ```rust
205/// use moongraph::*;
206///
207/// #[derive(Default)]
208/// pub struct Position(f32, f32);
209///
210/// #[derive(Default)]
211/// pub struct Velocity(f32, f32);
212///
213/// #[derive(Default)]
214/// pub struct Acceleration(f32, f32);
215///
216/// #[derive(Default)]
217/// pub struct BankAccount {
218///     interest_rate: f32,
219///     balance: f32,
220/// }
221///
222/// pub fn add_acceleration((mut v, a): (ViewMut<Velocity>, View<Acceleration>)) -> Result<(), GraphError> {
223///     v.0 += a.0;
224///     v.1 += a.1;
225///     Ok(())
226/// }
227///
228/// pub fn add_velocity((mut p, v): (ViewMut<Position>, View<Velocity>)) -> Result<(), GraphError> {
229///     p.0 += v.0;
230///     p.1 += v.1;
231///     Ok(())
232/// }
233///
234/// pub fn compound_interest(mut acct: ViewMut<BankAccount>) -> Result<(), GraphError> {
235///     let increment = acct.interest_rate * acct.balance;
236///     acct.balance += increment;
237///     Ok(())
238/// }
239///
240/// let mut graph = graph!(
241///     add_acceleration,
242///     add_velocity,
243///     compound_interest
244/// )
245/// .with_resource(Position(0.0, 0.0))
246/// .with_resource(Velocity(0.0, 0.0))
247/// .with_resource(Acceleration(1.0, 1.0))
248/// .with_resource(BankAccount {
249///     interest_rate: 0.02,
250///     balance: 1337.0,
251/// });
252///
253/// graph.run().unwrap();
254/// let schedule = graph.get_schedule();
255/// assert_eq!(
256///     vec![
257///         vec!["add_velocity", "compound_interest"],
258///         vec!["add_acceleration"]
259///     ],
260///     schedule,
261/// );
262///
263/// graph.run().unwrap();
264/// let position = graph.get_resource::<Position>().unwrap().unwrap();
265/// assert_eq!((1.0, 1.0), (position.0, position.1));
266/// ```
267///
268/// Notice how the returned schedule shows that `add_velocity` and `compound_interest` can run together in parallel. We call this a "batch". It's possible to run all nodes in a batch at the same time because none of their borrows conflict and there are no explicit ordering constraints between them.
269///
270///
271///
272/// ## Conclusion
273/// Hopefully by this point you have a better idea what `moongraph` is about and how to use it.
274/// For more info please look at the module and type level documentation.
275/// If you have any questions please reach out at [the moongraph GitHub repository](https://github.com/schell/moongraph).
276///
277/// Happy hacking! ☕☕☕
278pub mod tutorial {}