Skip to main content

Module node

Module node 

Source
Expand description

The dispatcher — node registration, subscription, wave engine.

Mirrors ~/src/graphrefly-ts/src/__experiments__/handle-core/core.ts (the Phase 13.6 brainstorm prototype, ~370 lines, 22 invariant tests).

§Scope (M1 dispatcher + Slice A+B parity, closed 2026-05-05)

  • State + derived + dynamic node registration.
  • Subscribe / unsubscribe with push-on-subscribe (R1.2.3).
  • RAII [Subscription] with Drop-based deregister (§10.12).
  • DIRTY → DATA / RESOLVED ordering (R1.3.1.b two-phase push).
  • Equals-substitution (R1.3.2): identity is zero-FFI; custom crosses boundary.
  • First-run gate (R2.5.3) — fn does not fire until every dep has a handle.
  • Diamond resolution — one fn fire per wave even with shared upstream.
  • set_deps() atomic dep mutation with cycle detection + Phase 13.8 Q1 terminal-rejection policy (R3.3.1).
  • PAUSE / RESUME with lockId set + replay buffer (R1.2.6, R2.6, §10.2).
  • INVALIDATE broadcast + cascade with R1.4 idempotency.
  • COMPLETE / ERROR cascade + Lock 2.B auto-cascade gating (ERROR dominates COMPLETE; first error wins).
  • TEARDOWN auto-precedes COMPLETE (R2.6.4 / Lock 6.F) + has_received_teardown idempotency.
  • Meta TEARDOWN ordering (R1.3.9.d) — companions tear down before parent.
  • Resubscribable terminal lifecycle (R2.2.7, R2.5.3) — late subscribe to a resubscribable terminal node resets lifecycle, except after TEARDOWN (per F3 audit guard: TEARDOWN is permanent).

§Module split (Slice C-1, 2026-05-05)

Wave-engine internals (drain loop, fire selection, emission commit, sink dispatch) live in [crate::batch]. The split is purely organizational — the methods are still on Core. See batch.rs for the wave-engine entry points (run_wave, drain_and_flush, commit_emission, queue_notify, deliver_data_to_consumer).

§Out of scope (later slices / milestones)

  • Deactivation cleanup (RAM nodes clear cache when sink count → 0) — M2.

See migration-status.md for the milestone tracker and porting-deferred.md for surfaced concerns deferred to evidence-driven slices.

§Re-entrance discipline (Slice A close, M1: fully lock-released)

  • Wave-end sink fires drop the state lock first. A subscriber’s sink that calls back into Core::emit / pause / resume / invalidate / complete / error / teardown re-acquires the lock cleanly and runs a nested wave (the one-Core-per-OS-thread in_tick ownership slot is cleared by the owning BatchGuard::drop before the deferred-fire phase).
  • BindingBoundary::invoke_fn fires lock-released. The wave engine acquires + drops the state lock per fn-fire iteration around the invoke_fn callback. User fns may re-enter Core::emit / pause / etc. and run a nested wave.
  • BindingBoundary::custom_equals fires lock-released. commit_emission brackets the equals check around a lock release; custom equals oracles may re-enter Core safely.
  • Subscribe-time handshake also fires lock-released. Core::subscribe installs the sink under the state lock, drops it, then fires the per-tier handshake ([Start] / [Data(cache)]? / [Complete]? / [Error(h)]? / [Teardown]? per R1.3.5.a) lock-released. A handshake-time sink callback may re-enter Core via the owner-side mailbox/DeferQueue seam (emit / complete / error / subscribe); the owner drain applies it as a nested wave. Post-S4 Core is single-owner !Send + !Sync — there is no wave_owner mutex and no cross-thread block; R1.3.5.a happens-after ordering holds because the one owner thread installs the sink (state lock) before any subsequent emit’s flush observes it (subscribers_revision freeze, Slice X4/D2).

Structs§

Core
The handle-protocol Core dispatcher.
CoreShared
Core-global state with NO per-scheduling-group partition (Slice B-2 Step 1, D220-EXEC). Hoisted out of CoreState so that when Step 2 shards nodes/children per ShardKey, THESE remain singular: monotonic id counters, the topology-sink registry, the cross-shard-visible currently_firing reentrancy stack (P13 / /qa F2 — the load-bearing cross-thread-visibility property), the two Core-global caps, and the Core-shutdown scratch-release queue.
CoreState
Per-shard mutable Core state (Step 2a, D220-EXEC: still exactly ONE shard — behaviour-identical with the pre-B-2 single CoreState; Step 2b keys a per-[crate::state_cell::ShardKey] map of these). All mutable Core state, behind one parking_lot::Mutex.
NodeOpts
Per-kind opts for Core::register. Cross-kind config knobs live here; per-kind specifics (deps, fn_or_op) live on NodeRegistration.
NodeRegistration
Unified node-registration descriptor (D030, Slice D).
OperatorOpts
Registration options for Core::register_operator.
PartitionOrderViolation
Error returned when a same-thread partition acquire violates ascending order. Phase H+ STRICT variant (D115).
ResumeReport
Reported back from Core::resume when the final lock releases.
SubscriptionId
Public identifier for a single subscription (S2b / D225: promoted from pub(crate)). Returned by Core::subscribe / Core::try_subscribe; pair it with the node_id you subscribed and pass both to Core::unsubscribe to deregister.

Enums§

DeferredProducerOp
A producer-pattern operation deferred because it would have violated the ascending partition-order protocol (Phase H+ STRICT, D115). Drained by BatchGuard::drop after wave_guards are released (no partitions held → safe to acquire any partition).
EqualsMode
Equality mode for a node’s outgoing emissions.
NodeFnOrOp
Closure-form fn id OR typed operator discriminant — the two dispatch paths a node can use. State / passthrough nodes pass None to Core::register (no fn at all).
NodeKind
Node kind discriminant — derived metadata computed from [NodeRecord]’s field shape (D030 unification, Slice D).
OperatorOp
Built-in operator discriminant. Selects the per-operator dispatch path in fire_operator (crates/graphrefly-core/src/batch.rs). Each variant carries the binding-side closure ids (and seed handle for stateful folders) needed for the wave-execution path; Core stores no user values itself per the handle-protocol cleaving plane.
PausableMode
Pause behavior mode (canonical-spec §2.6 — three modes shipped in TS; Slice F audit, 2026-05-07 — closed the Rust port gap).
PauseError
Errors returnable by Core::pause and Core::resume.
RegisterError
Errors returnable by Core::register and its sugar wrappers (Core::register_state, Core::register_producer, Core::register_derived, Core::register_dynamic, Core::register_operator).
SetDepsError
Errors returnable by Core::set_deps.
SetPausableModeError
Errors returnable by Core::set_pausable_mode.
SubscribeError
Errors returnable by Core::try_subscribe.
TerminalKind
Terminal-lifecycle state — once set on a node, the node will not emit further DATA; per-dep slots on consumers also use this to track which upstreams have terminated (R1.3.4 / Lock 2.B).
UpError
Errors returnable by Core::up (canonical R1.4.1).

Traits§

CoreFull
Object-safe full-Core re-entry surface (S2b / D233) — the methods a producer sink’s owner-side crate::mailbox::MailboxOp::Defer closure needs, by NodeId/HandleId/Sink/id only (no C/T), blanket-impl’d for every Core. Lets windowing / higher-order-operator sinks perform value-returning topology mutation (register_*/subscribe) in-wave without naming the cell type: the BatchGuard drain-to-quiescence loop calls f(self as &dyn CoreFull) while it holds the owner &Core.

Type Aliases§

Sink
A subscriber callback. Send + Sync so the Core can fire it from any thread; Fn (not FnMut) so multiple references coexist — capture mutable state in Mutex<T> or atomics on the binding side.