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 per-(Core, 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 acquires the [Core::wave_owner] re-entrant mutex first (cross-thread serialization), installs the sink under the state lock, drops the state lock, 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 (emit / complete / error / subscribe); same-thread re-entry passes through wave_owner transparently. Cross-thread emits block on wave_owner until the subscribe path drops it, preserving R1.3.5.a happens-after ordering.

Structs§

Core
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.
Subscription
RAII subscription handle.
WeakCore
Weak handle to a Core — does not contribute to strong refcount.

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).

Functions§

held_snapshot_for_tests
Test-only re-exports for integration tests under crates/graphrefly-core/tests/. Gated #[cfg(any(test, debug_assertions))] so they don’t leak into release builds. Public visibility is required because integration tests live outside the crate. Test-only: read the current thread’s held-partitions snapshot. Used by post-panic regression assertions to verify the thread-local stays clean even when the H+ check unwinds the stack (so cargo’s thread-reuse doesn’t propagate corrupted state to subsequent tests). pub (gated by cfg(any(test, debug_assertions))) so integration tests outside the crate can read it.

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.