Run Loop Concept
The run-loop is central to the HGraph package. The run-loop is the mechanism used to dispatch events (or ticks) into the graph for evaluation. The run-loop is a similar to an operating systems process scheduler.
The run loop has two key modes of operation, namely:
SIMULATION- A mode used to run through scheduled events as fast as possibleREAL_TIME- The mode to use when scheduling events to occur at roughly true time.
Typically SIMULATION is used to run testing of the graph’s behaviour. Whereas REAL_TIME
is used to run the production system. The REAL_TIME mode of operation can be set to
start in the past, this allows for a form of recover / warm up prior to running the graph.
When the time is in the past, the graph will advance time in the same mannor as with
SIMULATION.
Nodes in the graph are scheduled in topologically ranked order. The topology is defined by the connection of nodes (since the connections are directed, this forms a Directed Acyclic Graph) the rank is the max count of connections from source to current node.
Sink nodes are placed at the max rank (although this is not guaranteed, the only guarantee is that the nodes are evaluated in order to ensure that nodes with a smaller rank are evalauted prior to nodes of a higher ranking).
The operations of the run loop can be observed using an instance of EvaluationLifeCycleObserver.
This has callback methods that can be implemented to be notified of each key step the graph makes
as it evaluates itself.
Life-Cycle
The life-cycle of the graph is as below:
The graph as well as each node in the graph are taken through this basic life-cycle.
The master graph is started when the run-loop is started. Nested graphs (such as those used
to implement map_, are started when they are brought into existence, this is data driven.
The observer will be notified prior to the graph starting or stopping calling the methods:
on_before_start_graph(self, graph: Graph)on_after_start_graph(self, graph: Graph)
and
on_before_start_node(self, node: Node)on_after_start_node(self, node: Node)
for start and the counterpart method for stop.
Once the graph has been taken though the start life-cycle. The evaluation loop will run, in all modes, the core loop is roughly as below:
current_time = start_time
while current_time <= end_time:
self.evaluate_graph()
next_time = next_scheduled_time()
self.wait_until(next_time)
current_time = next_time
In the real-time engine the wait will wait until the time is reached and in the simulation mode this will return immediately.
Note
This is not the actual code, just the conceptual view of how it works.
Evaluation Loop
The master evaluation loop uses a vector of datetime aligned with the nodes to indicate
when a node is to be scheduled. The scheduler runs down the list of time until it finds
an entry that is set to be evaluated at the current_time. The node is then evaluated.
The code takes the form of:
for i in range(len(self.nodes)):
if self.schedule[i] == current_time
self.nodes[i].eval()
The pre and post evaluation life-cycle observer methods are called just before and after evaluation.
A node is scheduled by setting the appropriate entry in the schedule.
Nested Graphs
A nested graph lives within a node. It runs it’s own scheduler. Depending on the implementation the nested graph engine, it can implement alternative scheduling algorithms. However, by default, each nested graph runs a distinct scheduler with it’s own list of nodes and schedule.