Skip to main content

Internals: Linked-List Subscriber System

SoulState manages subscribers using a Doubly Linked List to achieve $O(1)$ addition and removal complexity. This is critical for high-churn user interfaces where components frequently mount and unmount.

The Subscription Node

Every subscription (granular or global) is represented by a node in the list.

// src/core/subscriptions.ts (extends ReactiveNode from graph.ts)
export interface SubscriptionNode<S> extends ReactiveNode {
selector: (state: any) => S;
listener: (curr: S, prev: S) => void;
equalityFn: (a: S, b: S) => boolean;
lastSelectedState: S;
fastKey: DependencyKey | null;
next: SubscriptionNode<any> | null;
prev: SubscriptionNode<any> | null;
}

O(1) Teardown Optimization

Unlike libraries that use arrays (which require $O(N)$ scanning to remove an item), SoulState's teardown is a direct pointer manipulation.

// Simplified teardown logic
const { prev, next } = node;
if (prev) prev.next = next;
else this.head = next;

if (next) next.prev = prev;
else this.tail = prev;

This ensures that even with 100,000 subscribers, unmounting a component is an O(1) pointer operation.


Listener Iteration Strategy

SoulState maintains two separate lists:

  1. Global Subscribers: Listeners that subscribe to the entire state.
  2. Granular Subscribers: Listeners that are managed by the Invalidation Graph.

Surgical Dispatch

When an update occurs:

  1. The Invalidation Graph directly identifies only the granular nodes affected by the change.
  2. The SubscriptionManager iterates through the global list and notifies them.

This separation ensures that granular updates don't pay the cost of iterating over unrelated listeners, maintaining the $O(M)$ propagation complexity that defines SoulState.

High-Churn Performance

The combination of linked-list management and graph-based dispatch makes SoulState the ideal runtime for dynamic, data-heavy applications where component lifecycles are frequent and unpredictable.