alien-signals
Advanced tools
Comparing version
{ | ||
"name": "alien-signals", | ||
"version": "0.5.0", | ||
"version": "0.6.0", | ||
"sideEffects": false, | ||
"license": "MIT", | ||
"description": "The fastest and lightest signal library.", | ||
"description": "The lightest signal library.", | ||
"packageManager": "pnpm@9.12.0", | ||
@@ -40,3 +41,4 @@ "types": "./types/index.d.ts", | ||
"test": "vitest run", | ||
"bench": "npm run build:esm && node --jitless --expose-gc benchs/propagate.mjs" | ||
"bench": "npm run build:esm && node --jitless --expose-gc benchs/propagate.mjs && node --jitless --expose-gc benchs/complex.mjs", | ||
"bench:memory": "npm run build:esm && node --expose-gc benchs/memoryUsage.mjs" | ||
}, | ||
@@ -47,5 +49,4 @@ "devDependencies": { | ||
"typescript": "latest", | ||
"vite": "latest", | ||
"vitest": "latest" | ||
} | ||
} |
117
README.md
@@ -9,10 +9,14 @@ <p align="center"> | ||
<h3 align="center"> | ||
<p>[<a href="https://github.com/YanqingXu/alien-signals-in-lua">Alien Signals in Lua</a>]</p> | ||
<p>[<a href="https://github.com/medz/alien-signals-dart">Alien Signals in Dart</a>]</p> | ||
<p>[<a href="https://github.com/Rajaniraiyn/react-alien-signals">React Binding</a>]</p> | ||
</h3> | ||
# alien-signals | ||
Project Status: **Preview** | ||
The goal of `alien-signals` is to create a ~~push-pull~~ [push-pull-push model](https://github.com/stackblitz/alien-signals/pull/19) based signal library with the lowest overhead. | ||
The goal of `alien-signals` is to create a push-pull model based signal library with the lowest overhead. | ||
We have set the following constraints in scheduling logic: | ||
We have set the following scheduling logic constraints: | ||
1. No dynamic object fields | ||
@@ -83,2 +87,107 @@ 2. No use of Array/Set/Map | ||
## About `propagate` and `checkDirty` functions | ||
In order to eliminate recursive calls and improve performance, we record the last link node of the previous loop in `propagate` and `checkDirty` functions, and implement the rollback logic to return to this node. | ||
This results in code that is difficult to understand, and you don't necessarily get the same performance improvements in other languages, so we record the original implementation without eliminating recursive calls here for reference. | ||
#### `propagate` | ||
```ts | ||
export function propagate(link: Link, targetFlag: SubscriberFlags = SubscriberFlags.Dirty): void { | ||
do { | ||
const sub = link.sub; | ||
const subFlags = sub.flags; | ||
if ( | ||
( | ||
!(subFlags & (SubscriberFlags.Tracking | SubscriberFlags.Recursed | SubscriberFlags.InnerEffectsPending | SubscriberFlags.ToCheckDirty | SubscriberFlags.Dirty)) | ||
&& (sub.flags = subFlags | targetFlag, true) | ||
) | ||
|| ( | ||
(subFlags & (SubscriberFlags.Tracking | SubscriberFlags.Recursed)) === SubscriberFlags.Recursed | ||
&& (sub.flags = (subFlags & ~SubscriberFlags.Recursed) | targetFlag, true) | ||
) | ||
|| ( | ||
!(subFlags & (SubscriberFlags.InnerEffectsPending | SubscriberFlags.ToCheckDirty | SubscriberFlags.Dirty)) | ||
&& isValidLink(link, sub) | ||
&& ( | ||
sub.flags = subFlags | SubscriberFlags.Recursed | targetFlag, | ||
(sub as Dependency).subs !== undefined | ||
) | ||
) | ||
) { | ||
const subSubs = (sub as Dependency).subs; | ||
if (subSubs !== undefined) { | ||
propagate( | ||
subSubs, | ||
'notify' in sub | ||
? SubscriberFlags.InnerEffectsPending | ||
: SubscriberFlags.ToCheckDirty | ||
); | ||
} else if ('notify' in sub) { | ||
if (queuedEffectsTail !== undefined) { | ||
queuedEffectsTail.nextNotify = sub; | ||
} else { | ||
queuedEffects = sub; | ||
} | ||
queuedEffectsTail = sub; | ||
} | ||
} else if ( | ||
!(subFlags & (SubscriberFlags.Tracking | targetFlag)) | ||
|| ( | ||
!(subFlags & targetFlag) | ||
&& (subFlags & (SubscriberFlags.InnerEffectsPending | SubscriberFlags.ToCheckDirty | SubscriberFlags.Dirty)) | ||
&& isValidLink(link, sub) | ||
) | ||
) { | ||
sub.flags = subFlags | targetFlag; | ||
} | ||
link = link.nextSub!; | ||
} while (link !== undefined); | ||
if (targetFlag === SubscriberFlags.Dirty && !batchDepth) { | ||
drainQueuedEffects(); | ||
} | ||
} | ||
``` | ||
#### `checkDirty` | ||
```ts | ||
export function checkDirty(link: Link): boolean { | ||
do { | ||
const dep = link.dep; | ||
if ('update' in dep) { | ||
const depFlags = dep.flags; | ||
if (depFlags & SubscriberFlags.Dirty) { | ||
if (dep.update()) { | ||
const subs = dep.subs!; | ||
if (subs.nextSub !== undefined) { | ||
shallowPropagate(subs); | ||
} | ||
return true; | ||
} | ||
} else if (depFlags & SubscriberFlags.ToCheckDirty) { | ||
if (checkDirty(dep.deps!)) { | ||
if (dep.update()) { | ||
const subs = dep.subs!; | ||
if (subs.nextSub !== undefined) { | ||
shallowPropagate(subs); | ||
} | ||
return true; | ||
} | ||
} else { | ||
dep.flags = depFlags & ~SubscriberFlags.ToCheckDirty; | ||
} | ||
} | ||
} | ||
link = link.nextDep!; | ||
} while (link !== undefined); | ||
return false; | ||
} | ||
``` | ||
## Roadmap | ||
@@ -85,0 +194,0 @@ |
@@ -1,2 +0,2 @@ | ||
import { IComputed, Link, SubscriberFlags } from './system.js'; | ||
import { IComputed, ILink, SubscriberFlags } from './system.js'; | ||
import type { ISignal } from './types.js'; | ||
@@ -6,9 +6,7 @@ export declare function computed<T>(getter: (cachedValue?: T) => T): Computed<T>; | ||
getter: (cachedValue?: T) => T; | ||
cachedValue: T | undefined; | ||
version: number; | ||
subs: Link | undefined; | ||
subsTail: Link | undefined; | ||
lastTrackedId: number; | ||
deps: Link | undefined; | ||
depsTail: Link | undefined; | ||
currentValue: T | undefined; | ||
subs: ILink | undefined; | ||
subsTail: ILink | undefined; | ||
deps: ILink | undefined; | ||
depsTail: ILink | undefined; | ||
flags: SubscriberFlags; | ||
@@ -15,0 +13,0 @@ constructor(getter: (cachedValue?: T) => T); |
@@ -1,15 +0,13 @@ | ||
import { Dependency, IEffect, Link, Subscriber, SubscriberFlags } from './system.js'; | ||
export declare let activeSub: Subscriber | undefined; | ||
export declare let activeTrackId: number; | ||
export declare let lastTrackId: number; | ||
export declare function setActiveSub(sub: Subscriber | undefined, trackId: number): void; | ||
export declare function nextTrackId(): number; | ||
import { IDependency, IEffect, ILink, ISubscriber, SubscriberFlags } from './system.js'; | ||
export declare let activeSub: ISubscriber | undefined; | ||
export declare function untrack<T>(fn: () => T): T; | ||
export declare function setActiveSub(sub: ISubscriber | undefined): void; | ||
export declare function effect<T>(fn: () => T): Effect<T>; | ||
export declare class Effect<T = any> implements IEffect, Dependency { | ||
export declare class Effect<T = any> implements IEffect, IDependency { | ||
fn: () => T; | ||
nextNotify: IEffect | undefined; | ||
subs: Link | undefined; | ||
subsTail: Link | undefined; | ||
deps: Link | undefined; | ||
depsTail: Link | undefined; | ||
subs: ILink | undefined; | ||
subsTail: ILink | undefined; | ||
deps: ILink | undefined; | ||
depsTail: ILink | undefined; | ||
flags: SubscriberFlags; | ||
@@ -16,0 +14,0 @@ constructor(fn: () => T); |
@@ -1,7 +0,9 @@ | ||
import { Link, Subscriber, SubscriberFlags } from './system.js'; | ||
import { ILink, ISubscriber, SubscriberFlags } from './system.js'; | ||
export declare let activeEffectScope: EffectScope | undefined; | ||
export declare function untrackScope<T>(fn: () => T): T; | ||
export declare function setActiveScope(sub: EffectScope | undefined): void; | ||
export declare function effectScope(): EffectScope; | ||
export declare class EffectScope implements Subscriber { | ||
deps: Link | undefined; | ||
depsTail: Link | undefined; | ||
export declare class EffectScope implements ISubscriber { | ||
deps: ILink | undefined; | ||
depsTail: ILink | undefined; | ||
flags: SubscriberFlags; | ||
@@ -8,0 +10,0 @@ notify(): void; |
@@ -0,1 +1,2 @@ | ||
export * from './batch.js'; | ||
export * from './computed.js'; | ||
@@ -7,2 +8,2 @@ export * from './effect.js'; | ||
export * from './types.js'; | ||
export * as Unstable from './unstable/index.js'; | ||
export * from './unstable/index.js'; |
@@ -1,13 +0,12 @@ | ||
import { Dependency, Link } from './system.js'; | ||
import { IDependency, ILink } from './system.js'; | ||
import type { IWritableSignal } from './types.js'; | ||
export declare function signal<T>(): Signal<T | undefined>; | ||
export declare function signal<T>(oldValue: T): Signal<T>; | ||
export declare class Signal<T = any> implements Dependency, IWritableSignal<T> { | ||
export declare class Signal<T = any> implements IDependency, IWritableSignal<T> { | ||
currentValue: T; | ||
subs: Link | undefined; | ||
subsTail: Link | undefined; | ||
lastTrackedId: number; | ||
subs: ILink | undefined; | ||
subsTail: ILink | undefined; | ||
constructor(currentValue: T); | ||
get(): NonNullable<T>; | ||
get(): T; | ||
set(value: T): void; | ||
} |
@@ -1,26 +0,23 @@ | ||
export interface IEffect extends Subscriber { | ||
export interface IEffect extends ISubscriber { | ||
nextNotify: IEffect | undefined; | ||
notify(): void; | ||
} | ||
export interface IComputed extends Dependency, Subscriber { | ||
version: number; | ||
export interface IComputed extends IDependency, ISubscriber { | ||
update(): boolean; | ||
} | ||
export interface Dependency { | ||
subs: Link | undefined; | ||
subsTail: Link | undefined; | ||
lastTrackedId?: number; | ||
export interface IDependency { | ||
subs: ILink | undefined; | ||
subsTail: ILink | undefined; | ||
} | ||
export interface Subscriber { | ||
export interface ISubscriber { | ||
flags: SubscriberFlags; | ||
deps: Link | undefined; | ||
depsTail: Link | undefined; | ||
deps: ILink | undefined; | ||
depsTail: ILink | undefined; | ||
} | ||
export interface Link { | ||
dep: Dependency | IComputed | (Dependency & IEffect); | ||
sub: Subscriber | IComputed | (Dependency & IEffect) | IEffect; | ||
version: number; | ||
prevSub: Link | undefined; | ||
nextSub: Link | undefined; | ||
nextDep: Link | undefined; | ||
export interface ILink { | ||
dep: IDependency | IComputed | (IDependency & IEffect); | ||
sub: ISubscriber | IComputed | (IDependency & IEffect) | IEffect; | ||
prevSub: ILink | undefined; | ||
nextSub: ILink | undefined; | ||
nextDep: ILink | undefined; | ||
} | ||
@@ -30,14 +27,16 @@ export declare const enum SubscriberFlags { | ||
Tracking = 1, | ||
CanPropagate = 2, | ||
RunInnerEffects = 4, | ||
Recursed = 2, | ||
InnerEffectsPending = 4, | ||
ToCheckDirty = 8, | ||
Dirty = 16, | ||
Dirtys = 24 | ||
Notified = 28 | ||
} | ||
export declare function startBatch(): void; | ||
export declare function endBatch(): void; | ||
export declare function link(dep: Dependency, sub: Subscriber): Link; | ||
export declare function propagate(subs: Link): void; | ||
export declare function checkDirty(deps: Link, singleDep?: boolean): boolean; | ||
export declare function startTrack(sub: Subscriber): void; | ||
export declare function endTrack(sub: Subscriber): void; | ||
export declare function drainQueuedEffects(): void; | ||
export declare function link(dep: IDependency, sub: ISubscriber): void; | ||
export declare function propagate(link: ILink): void; | ||
export declare function shallowPropagate(link: ILink): void; | ||
export declare function checkDirty(link: ILink): boolean; | ||
export declare function startTrack(sub: ISubscriber): void; | ||
export declare function endTrack(sub: ISubscriber): void; | ||
export declare function isDirty(sub: ISubscriber, flags: SubscriberFlags): boolean; | ||
export declare function runInnerEffects(link: ILink): void; |
import { ISignal } from '../index.js'; | ||
export declare function computedArray<I, O>(arr: ISignal<I[]>, getGetter: (item: ISignal<I>, index: number) => () => O): readonly Readonly<O>[]; | ||
export declare function computedArray<I, O>(arr: ISignal<I[]>, getter: (item: I, index: number) => O): readonly Readonly<O>[]; |
@@ -1,3 +0,17 @@ | ||
export * from './computedArray.js'; | ||
export * from './computedSet.js'; | ||
export * from './equalityComputed.js'; | ||
import { AsyncComputed, asyncComputed } from './asyncComputed.js'; | ||
import { AsyncEffect, asyncEffect } from './asyncEffect.js'; | ||
import { asyncCheckDirty } from './asyncSystem.js'; | ||
import { computedArray } from './computedArray.js'; | ||
import { computedSet } from './computedSet.js'; | ||
import { EqualityComputed, equalityComputed } from './equalityComputed.js'; | ||
export declare const unstable: { | ||
AsyncComputed: typeof AsyncComputed; | ||
asyncComputed: typeof asyncComputed; | ||
AsyncEffect: typeof AsyncEffect; | ||
asyncEffect: typeof asyncEffect; | ||
asyncCheckDirty: typeof asyncCheckDirty; | ||
computedArray: typeof computedArray; | ||
computedSet: typeof computedSet; | ||
EqualityComputed: typeof EqualityComputed; | ||
equalityComputed: typeof equalityComputed; | ||
}; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
63693
25.75%4
-20%33
22.22%2023
20.63%0
-100%199
121.11%