Skip to content
View source

Graph & state

The Graph runtime (entelechy-graph) composes Kernels by KernelID with cached, dirty-tracked evaluation. Kernel owns computation; Graph owns relationships.

An evaluation (a frame) runs only what something reads. A node runs again when its Kernel, its values, its extent, the clock or input it reads, or a source it reads changed.

A node that gives what it gave last, word for word, is unchanged to what reads it — so a cheap node that settles spares a costly one after it, and state at a fixed point does not advance. (Words are compared in Host memory always; on a device only when the node ran for nothing but the state it reads, as comparing there waits.)

When a consumer samples a producer only at its own element, the two fuse into one Kernel and the intermediate is never written. entelechy graph prints the flattened graph, the published Kernels and generations, the order state advances in, and the plan:

// plan: 2 group(s)
// gray + source: fused.gray@1 vec2(128, 128)
// blocks: image.pixelate@1
// gray runs apart: `blocks` reads it neither at its own element (`sample`) nor at one offset of a row

Each producer that runs apart says why (it sleeps, its work is budgeted, it is read by address). --no-fusion turns fusion off for comparison; the protocol’s pin keeps a node materialized for read.

What docs/DOCUMENTS.md says is never fused: reductions, scans, scatters, native operations, and a narrow Field between two nodes.

node life: life.step@1 {
cells <~ life.result from seed.result; // what the last frame committed; seed at first
}
  • port <~ source from initial reads what the previous successful evaluation committed, not a dependency; initial until something commits.
  • A node a port reads <~ holds state. A node reading it <- sees the state this evaluation made; one reading it <~ the state the last left. So a moment several nodes must agree on (a battle beginning) is decided by one node, and the others read its new state <-. entelechy graph says, under each node of its plan, whose new and whose committed state it reads.
  • State commits only when the whole evaluation succeeds; a failed frame changes nothing.

When a republished Kernel changes a state port’s layout:

cells <~ life.result from seed.result migrate life.widen@1;

migrate names a map Kernel over one input in the old layout giving the new, run once before the nodes; the evaluation reports it migrated. Without one, the state restarts from initial and the evaluation reports the reset. A migration that is unpublished, not such a map, of the wrong layout, or failing, fails the evaluation and leaves the state as it was.

Form Effect
node name: graph "path.esn" { … } places another graph: flattened, its nodes join under name.; its inputs take the parent’s wires, its params the parent’s values
times = N; + carry port <- output; the node or graph placed N times (name/1…name/N), carry feeding each the last one’s output; name.port means the last. N is a literal or a graph param — changing it reshapes a running scene
node zeros: Rows<Float> { domain = 65; } a fill node: a Field, not a Kernel

examples/fluid places its Jacobi pressure solve with times; examples/mipmap builds its levels with carry.

  • async = true; runs a node off the frame on its own thread. Until it lands, readers take its last output and the state it feeds through <~ stays; the evaluation that finds it landed reads the new output, re-runs the readers and commits. One run at a time; inputs copied when it starts. A replay’s settle and the protocol’s evaluate with settle: true wait for every run.
  • awake = value(source, row); sleeps a node while that row is 0; asleep it is not run, and what only it reads does not run either. A game whose title, character maker and world are one scene puts the two not shown to sleep.
  • A param is a graph parameter the caller overrides (--param name=value, a replay’s param, the protocol’s param). A node’s own value is the document’s, changed by compose.
  • require name: a <= b; says what the params must hold (two counts compared), checked at load and before every evaluation; one that fails stops the evaluation before anything runs, saying what each side came to. The physics package: require cells: columns * rows <= 1 << bits;.

The Graph computes structure, never values. (docs/DECISIONS.md)

A node’s values are written; domain and times may follow from structure, but any arithmetic on values belongs in a Kernel. The graph stays readable and editable, and an agent changing structure never brings in computation by accident.