Skip to content

Commit 4a86bda

Browse files
committed
docs: update readme for 1.0
1 parent ee1ac43 commit 4a86bda

1 file changed

Lines changed: 72 additions & 74 deletions

File tree

README.md

Lines changed: 72 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -6,83 +6,86 @@
66
<a href="https://npmjs.com/package/alien-signals"><img src="https://badgen.net/npm/v/alien-signals" alt="npm package"></a>
77
</p>
88

9-
<h3 align="center">
10-
<p>[<a href="https://github.com/YanqingXu/alien-signals-in-lua">Alien Signals in Lua</a>]</p>
11-
<p>[<a href="https://github.com/medz/alien-signals-dart">Alien Signals in Dart</a>]</p>
12-
<p>[<a href="https://github.com/Rajaniraiyn/react-alien-signals">React Binding</a>]</p>
13-
</h3>
9+
## Derived Projects
1410

15-
# alien-signals
16-
17-
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.
18-
19-
We have set the following constraints in scheduling logic:
11+
- [YanqingXu/alien-signals-in-lua](https://github.com/YanqingXu/alien-signals-in-lua): Lua implementation of alien-signals
12+
- [medz/alien-signals-dart](https://github.com/medz/alien-signals-dart): alien-signals Dart implementation of alien-signals
13+
- [Rajaniraiyn/react-alien-signals](https://github.com/Rajaniraiyn/react-alien-signals): React bindings for the alien-signals API
14+
- [CCherry07/alien-deepsignals](https://github.com/CCherry07/alien-deepsignals): Use alien-signals with the interface of a plain JavaScript object
2015

21-
1. No dynamic object fields
22-
2. No use of Array/Set/Map
23-
3. No recursion calls
24-
4. Class properties must be fewer than 10 (https://v8.dev/blog/fast-properties)
16+
# alien-signals
2517

26-
Experimental results have shown that with these constraints, it is possible to achieve excellent performance for a Signal library without using sophisticated scheduling strategies. The overall performance of `alien-signals` is approximately 400% that of Vue 3.4's reactivity system.
18+
This project explores a push-pull based signal algorithm. Its current implementation is similar to or related to certain other frontend projects:
2719

28-
For more detailed performance comparisons, please visit: https://github.com/transitive-bullshit/js-reactivity-benchmark
20+
- Propagation algorithm of Vue 3
21+
- Preact’s double-linked-list approach (https://preactjs.com/blog/signal-boosting/)
22+
- Inner effects scheduling of Svelte
23+
- Graph-coloring approach of Reactively (https://milomg.dev/2022-12-01/reactivity)
2924

30-
## Motivation
25+
We impose some constraints (such as not using Array/Set/Map and disallowing function recursion) to ensure performance. We found that under these conditions, maintaining algorithmic simplicity offers more significant improvements than complex scheduling strategies.
3126

32-
To achieve high-performance code generation in https://github.com/vuejs/language-tools, I needed to write some on-demand computed logic using Signals, but I couldn't find a low-cost Signal library that satisfied me.
27+
Even though Vue 3.4 is already optimized, alien-signals is still noticeably faster. (I wrote code for both, and since they share similar algorithms, they’re quite comparable.)
3328

34-
In the past, I accumulated some knowledge of reactivity systems in https://github.com/vuejs/core/pull/5912, so I attempted to develop `alien-signals` with the goal of creating a Signal library with minimal memory usage and excellent performance.
29+
<img width="1210" alt="Image" src="https://github.com/user-attachments/assets/88448f6d-4034-4389-89aa-9edf3da77254" />
3530

36-
Since Vue 3.5 switched to a Pull reactivity system in https://github.com/vuejs/core/pull/10397, I continued to research the Push-Pull reactivity system here. It is worth mentioning that I was inspired by the doubly-linked concept, but `alien-signals` does not use a similar implementation.
31+
## Background
3732

38-
## Adoptions
33+
I spent considerable time [optimizing Vue 3.4’s reactivity system](https://github.com/vuejs/core/pull/5912), gaining experience along the way. Since Vue 3.5 [switched to a pull-based algorithm similar to Preact](https://github.com/vuejs/core/pull/10397), I decided to continue researching a push-pull based implementation in a separate project. Our end goal is to implement fully incremental AST parsing and virtual code generation in Vue language tools, based on alien-signals.
3934

40-
- Used in Vue language tools (https://github.com/vuejs/language-tools) for virtual code generation.
35+
## Adoption
4136

42-
- The core reactivity system code was ported to Vue 3.6 and later. (https://github.com/vuejs/core/pull/12349)
37+
- [vuejs/core](https://github.com/vuejs/core): The core algorithm has been ported to 3.6 or higher (PR:https://github.com/vuejs/core/pull/12349)
38+
- [vuejs/language-tools](https://github.com/vuejs/language-tools): Used in the language-core package for virtual code generation
4339

4440
## Usage
4541

46-
### Basic
42+
#### Basic APIs
4743

4844
```ts
4945
import { signal, computed, effect } from 'alien-signals';
5046

5147
const count = signal(1);
52-
const doubleCount = computed(() => count.get() * 2);
48+
const doubleCount = computed(() => count() * 2);
5349

5450
effect(() => {
55-
console.log(`Count is: ${count.get()}`);
51+
console.log(`Count is: ${count()}`);
5652
}); // Console: Count is: 1
5753

58-
console.log(doubleCount.get()); // 2
54+
console.log(doubleCount()); // 2
5955

60-
count.set(2); // Console: Count is: 2
56+
count(2); // Console: Count is: 2
6157

62-
console.log(doubleCount.get()); // 4
58+
console.log(doubleCount()); // 4
6359
```
6460

65-
### Effect Scope
61+
#### Effect Scope
6662

6763
```ts
6864
import { signal, effectScope } from 'alien-signals';
6965

7066
const count = signal(1);
71-
const scope = effectScope();
7267

73-
scope.run(() => {
68+
const stopScope = effectScope(() => {
7469
effect(() => {
75-
console.log(`Count in scope: ${count.get()}`);
70+
console.log(`Count in scope: ${count()}`);
7671
}); // Console: Count in scope: 1
7772

78-
count.set(2); // Console: Count in scope: 2
73+
count(2); // Console: Count in scope: 2
7974
});
8075

81-
scope.stop();
76+
stopScope();
8277

83-
count.set(3); // No console output
78+
count(3); // No console output
8479
```
8580

81+
#### Creating Your Own Public API
82+
83+
You can reuse alien-signals’ core algorithm via `createReactiveSystem()` to build your own signal API. For implementation examples, see:
84+
85+
- https://github.com/stackblitz/alien-signals/blob/master/src/index.ts
86+
- https://github.com/proposal-signals/signal-polyfill/pull/44
87+
88+
8689
## About `propagate` and `checkDirty` functions
8790

8891
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.
@@ -92,25 +95,26 @@ This results in code that is difficult to understand, and you don't necessarily
9295
#### `propagate`
9396

9497
```ts
95-
export function propagate(link: Link, targetFlag: SubscriberFlags = SubscriberFlags.Dirty): void {
98+
function propagate(link: Link, targetFlag = SubscriberFlags.Dirty): void {
9699
do {
97100
const sub = link.sub;
98101
const subFlags = sub.flags;
99102

100103
if (
101104
(
102-
!(subFlags & (SubscriberFlags.Tracking | SubscriberFlags.Recursed | SubscriberFlags.InnerEffectsPending | SubscriberFlags.ToCheckDirty | SubscriberFlags.Dirty))
103-
&& (sub.flags = subFlags | targetFlag, true)
105+
!(subFlags & (SubscriberFlags.Tracking | SubscriberFlags.Recursed | SubscriberFlags.Propagated))
106+
&& (sub.flags = subFlags | targetFlag | SubscriberFlags.Notified, true)
104107
)
105108
|| (
106-
(subFlags & (SubscriberFlags.Tracking | SubscriberFlags.Recursed)) === SubscriberFlags.Recursed
107-
&& (sub.flags = (subFlags & ~SubscriberFlags.Recursed) | targetFlag, true)
109+
(subFlags & SubscriberFlags.Recursed)
110+
&& !(subFlags & SubscriberFlags.Tracking)
111+
&& (sub.flags = (subFlags & ~SubscriberFlags.Recursed) | targetFlag | SubscriberFlags.Notified, true)
108112
)
109113
|| (
110-
!(subFlags & (SubscriberFlags.InnerEffectsPending | SubscriberFlags.ToCheckDirty | SubscriberFlags.Dirty))
114+
!(subFlags & SubscriberFlags.Propagated)
111115
&& isValidLink(link, sub)
112116
&& (
113-
sub.flags = subFlags | SubscriberFlags.Recursed | targetFlag,
117+
sub.flags = subFlags | SubscriberFlags.Recursed | targetFlag | SubscriberFlags.Notified,
114118
(sub as Dependency).subs !== undefined
115119
)
116120
)
@@ -119,65 +123,68 @@ export function propagate(link: Link, targetFlag: SubscriberFlags = SubscriberFl
119123
if (subSubs !== undefined) {
120124
propagate(
121125
subSubs,
122-
isEffect(sub)
123-
? SubscriberFlags.InnerEffectsPending
124-
: SubscriberFlags.ToCheckDirty
126+
subFlags & SubscriberFlags.Effect
127+
? SubscriberFlags.PendingEffect
128+
: SubscriberFlags.PendingComputed
125129
);
126-
} else if (isEffect(sub)) {
130+
} else if (subFlags & SubscriberFlags.Effect) {
131+
if (queuedEffectsTail !== undefined) {
132+
queuedEffectsTail.depsTail!.nextDep = sub.deps;
133+
} else {
134+
queuedEffects = sub;
135+
}
136+
queuedEffectsTail = sub;
137+
}
138+
} else if (!(subFlags & (SubscriberFlags.Tracking | targetFlag))) {
139+
sub.flags = subFlags | targetFlag | SubscriberFlags.Notified;
140+
if ((subFlags & (SubscriberFlags.Effect | SubscriberFlags.Notified)) === SubscriberFlags.Effect) {
127141
if (queuedEffectsTail !== undefined) {
128-
queuedEffectsTail.nextNotify = sub;
142+
queuedEffectsTail.depsTail!.nextDep = sub.deps;
129143
} else {
130144
queuedEffects = sub;
131145
}
132146
queuedEffectsTail = sub;
133147
}
134148
} else if (
135-
!(subFlags & (SubscriberFlags.Tracking | targetFlag))
136-
|| (
137-
!(subFlags & targetFlag)
138-
&& (subFlags & (SubscriberFlags.InnerEffectsPending | SubscriberFlags.ToCheckDirty | SubscriberFlags.Dirty))
139-
&& isValidLink(link, sub)
140-
)
149+
!(subFlags & targetFlag)
150+
&& (subFlags & SubscriberFlags.Propagated)
151+
&& isValidLink(link, sub)
141152
) {
142153
sub.flags = subFlags | targetFlag;
143154
}
144155

145156
link = link.nextSub!;
146157
} while (link !== undefined);
147-
148-
if (targetFlag === SubscriberFlags.Dirty && !batchDepth) {
149-
drainQueuedEffects();
150-
}
151158
}
152159
```
153160

154161
#### `checkDirty`
155162

156163
```ts
157-
export function checkDirty(link: Link): boolean {
164+
function checkDirty(link: Link): boolean {
158165
do {
159166
const dep = link.dep;
160167
if ('flags' in dep) {
161168
const depFlags = dep.flags;
162-
if (depFlags & SubscriberFlags.Dirty) {
163-
if (isComputed(dep) && updateComputed(dep)) {
169+
if ((depFlags & (SubscriberFlags.Computed | SubscriberFlags.Dirty)) === (SubscriberFlags.Computed | SubscriberFlags.Dirty)) {
170+
if (updateComputed(dep)) {
164171
const subs = dep.subs!;
165172
if (subs.nextSub !== undefined) {
166173
shallowPropagate(subs);
167174
}
168175
return true;
169176
}
170-
} else if (depFlags & SubscriberFlags.ToCheckDirty) {
171-
if (isComputed(dep) && checkDirty(dep.deps!)) {
172-
if (dep.update()) {
177+
} else if ((depFlags & (SubscriberFlags.Computed | SubscriberFlags.PendingComputed)) === (SubscriberFlags.Computed | SubscriberFlags.PendingComputed)) {
178+
if (checkDirty(dep.deps!)) {
179+
if (updateComputed(dep)) {
173180
const subs = dep.subs!;
174181
if (subs.nextSub !== undefined) {
175182
shallowPropagate(subs);
176183
}
177184
return true;
178185
}
179186
} else {
180-
dep.flags = depFlags & ~SubscriberFlags.ToCheckDirty;
187+
dep.flags = depFlags & ~SubscriberFlags.PendingComputed;
181188
}
182189
}
183190
}
@@ -187,12 +194,3 @@ export function checkDirty(link: Link): boolean {
187194
return false;
188195
}
189196
```
190-
191-
## Roadmap
192-
193-
| Version | Savings |
194-
|---------|-----------------------------------------------------------------------------------------------|
195-
| 0.3 | Satisfy all 4 constraints |
196-
| 0.2 | Correctly schedule computed side effects |
197-
| 0.1 | Correctly schedule inner effect callbacks |
198-
| 0.0 | Add APIs: `signal()`, `computed()`, `effect()`, `effectScope()`, `startBatch()`, `endBatch()` |

0 commit comments

Comments
 (0)