import { createReactiveSystem, Dependency, Link, Subscriber, SubscriberFlags } from 'alien-signals'; import { ReactiveFlags } from "./contents" import { isFunction } from './utils'; const { link, propagate, endTracking, startTracking, updateDirtyFlag, processComputedUpdate, processEffectNotifications, } = createReactiveSystem({ updateComputed(computed: Computed) { return computed.update(); }, notifyEffect(effect: Effect) { effect.notify(); return true; }, }); let activeSub: Subscriber | undefined = undefined; let batchDepth = 0; export function startBatch(): void { ++batchDepth; } export function endBatch(): void { if (!--batchDepth) { processEffectNotifications(); } } export function signal(): Signal; export function signal(oldValue: T): Signal; export function signal(oldValue?: T): Signal { return new Signal(oldValue); } export class Signal implements Dependency { public readonly [ReactiveFlags.IS_SIGNAL] = true public readonly [ReactiveFlags.SKIP] = true // Dependency fields subs: Link | undefined = undefined; subsTail: Link | undefined = undefined; constructor( public currentValue: T ) { } get(): T { if (activeSub !== undefined) { link(this, activeSub); } return this.currentValue; } set(value: T): void { if (this.currentValue !== value) { this.currentValue = value; const subs = this.subs; if (subs !== undefined) { propagate(subs); if (!batchDepth) { processEffectNotifications(); } } } } get value(): T { return this.get(); } set value(value: T) { this.set(value); } peek(): T { return this.currentValue; } } export function computed(getter: () => T): Computed { return new Computed(getter); } export class Computed implements Subscriber, Dependency { readonly [ReactiveFlags.IS_SIGNAL] = true currentValue: T | undefined = undefined; // Dependency fields subs: Link | undefined = undefined; subsTail: Link | undefined = undefined; // Subscriber fields deps: Link | undefined = undefined; depsTail: Link | undefined = undefined; flags: SubscriberFlags = SubscriberFlags.Computed | SubscriberFlags.Dirty; constructor( public getter: () => T ) { } get(): T { const flags = this.flags; if (flags & (SubscriberFlags.PendingComputed | SubscriberFlags.Dirty)) { processComputedUpdate(this, flags); } if (activeSub !== undefined) { link(this, activeSub); } return this.currentValue!; } update(): boolean { const prevSub = activeSub; activeSub = this; startTracking(this); try { const oldValue = this.currentValue; const newValue = this.getter(); if (oldValue !== newValue) { this.currentValue = newValue; return true; } return false; } finally { activeSub = prevSub; endTracking(this); } } get value(): Readonly { return this.get(); } peek(): T { return this.currentValue!; } } export function effect(fn: () => T): Effect { const e = new Effect(fn); e.run(); return e; } export enum EffectFlags { /** * ReactiveEffect only */ ALLOW_RECURSE = 1 << 7, PAUSED = 1 << 8, NOTIFIED = 1 << 9, STOP = 1 << 10, } export class Effect implements Subscriber { readonly [ReactiveFlags.IS_SIGNAL] = true // Subscriber fields deps: Link | undefined = undefined; depsTail: Link | undefined = undefined; flags: SubscriberFlags = SubscriberFlags.Effect; constructor( public fn: () => T ) { } notify(): void { const flags = this.flags; if ( flags & SubscriberFlags.Dirty || (flags & SubscriberFlags.PendingComputed && updateDirtyFlag(this, flags)) ) { this.scheduler(); } } scheduler(): void { if (this.dirty) { this.run() } } get active(): boolean { return !(this.flags & EffectFlags.STOP) } get dirty(): boolean { const flags = this.flags if ( flags & SubscriberFlags.Dirty || (flags & SubscriberFlags.PendingComputed && updateDirtyFlag(this, flags)) ) { return true } return false } run(): T { const prevSub = activeSub; activeSub = this; startTracking(this); try { return this.fn(); } finally { activeSub = prevSub; endTracking(this); } } stop(): void { startTracking(this); endTracking(this); } } export function batch(fn: () => T): T { startBatch(); try { return fn(); } finally { endBatch(); } } export function isSignal(r: Signal | unknown): r is Signal export function isSignal(s: any): s is Signal { return s ? s[ReactiveFlags.IS_SIGNAL] === true : false } export type MaybeSignal = | T | Signal export type MaybeSignalOrGetter = MaybeSignal | Computed | (() => T) export function unSignal(signal: MaybeSignal | Computed): T { return (isSignal(signal) ? signal.value : signal) as T; } export function toValue(source: MaybeSignalOrGetter): T { return isFunction(source) ? source() : unSignal(source) }