Reactive Signals

How formulas know when to update

When you write Ball.x = mouse.x, the formula system creates a dependency: Ball.x depends on mouse.x. When the mouse moves, Ball.x re-evaluates. This page explains the signal graph that makes it work.


The signal primitive

Every reactive value is a signal. Signals have:

  • A current value
  • A version number (incremented on change)
  • A list of dependents (signals that read this signal)

When you read a signal during formula evaluation, the system records the dependency automatically. No manual subscription needed.

title: "Chain reaction"
description: "Three shapes linked by formulas — move one, all update"
nodes:
  - type: ELLIPSE, name: "Leader", x: 200, y: 200, w: 60, h: 60, fill: "#E74C3C"
  - type: ELLIPSE, name: "Follower1", w: 50, h: 50, fill: "#3498DB"
  - type: ELLIPSE, name: "Follower2", w: 40, h: 40, fill: "#2ECC71"
formulas:
  - node: "Follower1", prop: "x", value: "Leader.x + 80"
  - node: "Follower1", prop: "y", value: "Leader.y"
  - node: "Follower2", prop: "x", value: "Follower1.x + 70"
  - node: "Follower2", prop: "y", value: "Follower1.y"
tools_visible: [select, play]
ui_visible: [properties, formulas]

Evaluation order

Formulas form a directed acyclic graph (DAG). The system uses Kahn's algorithm (topological sort) to determine evaluation order. If A depends on B, B evaluates first.

mouse.x → Ball.x → Shadow.x → Score.text
↘ Ball.y → Shadow.y

Every formula evaluates exactly once per frame, in the correct order.

Cycle detection

If Ball.x depends on Wall.x and Wall.x depends on Ball.x, that's a cycle. The system uses depth-first search to detect cycles and reports them before evaluation. Circular dependencies are caught at compile time, not at runtime.

Dirty propagation

When a signal changes, its dependents are marked dirty. Only dirty signals re-evaluate. If mouse.x changes but mouse.y doesn't, formulas depending only on mouse.y stay clean.

Batch evaluation

Multiple signals can change in one frame (mouse.x and mouse.y move together). The system batches all changes and evaluates the entire dirty set once — no redundant intermediate states.

Version counting

Each signal has a version number. Dependencies track which version they last read. If the version hasn't changed, the dependent skips re-evaluation. This prevents unnecessary work when a formula evaluates to the same result.


Performance

The signal system is designed for the hot path:

  • Zero allocation — preallocated scratch buffers, no GC pressure
  • Prototype-inherited context — formula context uses JS prototype chain instead of object spread
  • Scalar accessors — VN data accessed via typed array indices, not object properties
  • CSS paint cachingrgbaToCSS results cached to avoid string allocation

Back to formulasFormula compiler

← НазадTagsДальше →Events & Methods