Differences
This shows you the differences between two versions of the page.
| Next revision | Previous revision | ||
|
basic_data_flow [2026/07/20 03:32] hermann created |
basic_data_flow [2026/07/21 22:38] (current) hermann |
||
|---|---|---|---|
| Line 3: | Line 3: | ||
| ===== Description ===== | ===== Description ===== | ||
| - | A Dinamica EGO model is, at its core, a graph: **functors** connected through their input and output ports. This is true no matter how the model was authored — built visually in the GUI, or written by hand in [[ego_script|EGO Script]]. Both are just different notations for describing the same underlying graph, and the execution engine schedules functors from that graph, not from the notation used to write it. This page describes how that scheduling works, independently of any particular notation. | + | A Dinamica EGO model is, at its core, a graph: **functors** connected through their input and output ports. This is true no matter how the model was authored — built visually in the GUI, saved as [[xml_script|XML Script]], or written by hand in [[ego_script|EGO Script]]. Either notation just describes the same underlying graph, and the execution engine schedules functors from that graph, not from the notation used to write it. This page describes how that scheduling works, independently of any particular notation. |
| ===== Functors and connections ===== | ===== Functors and connections ===== | ||
| Line 19: | Line 19: | ||
| ===== Containers: the second ordering source ===== | ===== Containers: the second ordering source ===== | ||
| - | **Container functors** group a nested set of functors so that they execute as a single unit — the container starts, its contents run, then it finishes. This has a consequence beyond simple grouping: a dependency on anything produced *inside* a container is really a dependency on the *whole container*. If functor B consumes a value from something inside container A, B will not start until every functor inside A has finished — not just the specific one that produced the value. See [[ego_script#container_functors|Container functors]] for how this is expressed in EGO Script. | + | **Container functors** group a nested set of functors so that they execute as a single unit — the container starts, its contents run, then it finishes. This has a consequence beyond simple grouping: a dependency on anything produced //inside// a container is really a dependency on the //whole container//. If functor B consumes a value from something inside container A, B will not start until every functor inside A has finished — not just the specific one that produced the value. See [[ego_script#container_functors|Container functors]] for how this is expressed in EGO Script. |
| This makes containers useful even when nothing inside them shares data with anything outside: wrapping two otherwise-independent functors in a container guarantees they run together, in the order the container defines — an ordering that a plain data dependency could never enforce on its own. | This makes containers useful even when nothing inside them shares data with anything outside: wrapping two otherwise-independent functors in a container guarantees they run together, in the order the container defines — an ordering that a plain data dependency could never enforce on its own. | ||
| + | |||
| + | ===== Containers control whether and how their content runs ===== | ||
| + | |||
| + | Grouping and ordering are only part of what a container does. A container has full control over whether its contents run at all, and how many times. [[Group]] runs its contents exactly once, unconditionally — it exists purely for organization and ordering. [[If Then]] and [[If Not Then]] run their contents only when a condition holds. Loop containers — [[Repeat]], [[For]], [[For Each]], [[For Each Category]], [[For Each Region]], [[While]], [[Do While]], and others — run their contents repeatedly, each according to its own rule: a fixed count, a numeric range, the rows of a table, the categories of a map, or a condition re-evaluated before every pass. From inside the block, a functor has no say in any of this — it simply runs, or doesn't, or runs again, whenever the container decides to. | ||
| + | |||
| + | A container that branches or repeats generally needs to exchange a small amount of state with its contents beyond ordinary connections — a loop's current element going in, or its repeat condition coming back out. This happens through dedicated **internal ports**, a separate channel between the container and the functors nested inside it. See [[ego_script#internal_output_ports|Internal output ports]] and [[ego_script#internal_input_ports|Internal input ports]] for how this is expressed in EGO Script. | ||
| ===== Ordering without data: sequence connections ===== | ===== Ordering without data: sequence connections ===== | ||
| Line 29: | Line 35: | ||
| ===== The one exception: feedback in loops ===== | ===== The one exception: feedback in loops ===== | ||
| - | A loop's running state is usually carried by a **mux** — a functor with two inputs, an initial value and a feedback value, and one output. The feedback input is deliberately exempt from the rule above: the mux does not wait for the functor that produces the feedback value to run first. Instead, it emits whatever that functor produced on the *previous* iteration. See [[ego_script#carrying_and_selecting_values_across_iterations|Carrying and selecting values across iterations]] for a worked example. | + | A loop's running state is usually carried by a **mux** — a functor with two inputs, an initial value and a feedback value, and one output. The feedback input is deliberately exempt from the rule above: the mux does not wait for the functor that produces the feedback value to run first. Instead, it emits whatever that functor produced on the //previous// iteration. See [[ego_script#carrying_and_selecting_values_across_iterations|Carrying and selecting values across iterations]] for a worked example. |
| This exemption is what makes loops possible at all. Without it, the functor that computes a loop's next state and the mux that carries it forward would depend on each other in a cycle, and neither could ever be scheduled. By treating the feedback connection as "last iteration's value" rather than "this iteration's value," the engine breaks the cycle — the same trick a register or flip-flop uses in a clocked circuit to let a signal feed back into its own computation. | This exemption is what makes loops possible at all. Without it, the functor that computes a loop's next state and the mux that carries it forward would depend on each other in a cycle, and neither could ever be scheduled. By treating the feedback connection as "last iteration's value" rather than "this iteration's value," the engine breaks the cycle — the same trick a register or flip-flop uses in a clocked circuit to let a signal feed back into its own computation. | ||
| + | The converse also holds. A loop's iterations can run simultaneously as long as three conditions are met: no mux is used directly inside the loop, nothing produced inside the loop is consumed by anything outside it, and no member of the loop is used as a submodel's output port. Without a mux, an iteration carries no state forward to the next one — so, exactly as with any other pair of functors, iterations that share no connection have no dependency on each other, and the engine is free to run several of them at once. | ||
| + | This also means a mux can be used deliberately to force sequential execution, even when a loop would otherwise qualify for parallel execution — any mux inside the loop disqualifies it, since it's the presence of a mux itself, not what it carries, that creates the dependency between iterations. ''MuxValue 0 0'' is simply the simplest way to add one: a mux whose initial and feedback inputs are both just the constant ''0'', carrying no real state, added purely to force the ordering. | ||