Skip to content

Commit e6f28d6

Browse files
committed
Rewrite in alien-signals 1.0.0-alpha.1
1 parent d8b49bb commit e6f28d6

3 files changed

Lines changed: 109 additions & 78 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
"webdriverio": "^8.36.1"
4242
},
4343
"dependencies": {
44-
"alien-signals": "^0.4.14"
44+
"alien-signals": "1.0.0-alpha.1"
4545
},
4646
"volta": {
4747
"node": "22.0.0",

pnpm-lock.yaml

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/alien.ts

Lines changed: 103 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,45 @@
11
import * as alien from 'alien-signals';
22

3-
const WATCHER_TRACK_ID = -1;
3+
const WATCHER_PLACEHOLDER = Symbol('watcher') as any;
44

55
export namespace Signal {
6-
export const untrack = alien.untrack;
7-
8-
export function batch<T>(fn: () => T): T {
9-
alien.startBatch();
6+
const {drainQueuedEffects, endTrack, isDirty, link, propagate, shallowPropagate, startTrack} =
7+
alien.createSystem({
8+
isComputed(sub): sub is Computed {
9+
return sub instanceof Computed;
10+
},
11+
isEffect(sub): sub is subtle.Watcher {
12+
return sub instanceof subtle.Watcher;
13+
},
14+
notifyEffect(watcher) {
15+
watcher.notify();
16+
},
17+
updateComputed(computed) {
18+
return computed.update();
19+
},
20+
});
21+
22+
let activeSub: alien.Subscriber | undefined;
23+
24+
export function untrack<T>(fn: () => T) {
25+
const prevSub = activeSub;
26+
activeSub = undefined;
1027
try {
1128
return fn();
1229
} finally {
13-
alien.endBatch();
30+
activeSub = prevSub;
1431
}
1532
}
1633

17-
export class State<T = any> extends alien.Signal<T> {
34+
export class State<T = any> implements alien.Dependency {
35+
subs: alien.Link | undefined = undefined;
36+
subsTail: alien.Link | undefined = undefined;
1837
watchCount = 0;
1938

2039
constructor(
21-
value: T,
40+
private currentValue: T,
2241
private options?: Options<T>,
2342
) {
24-
super(value);
2543
if (options && options.equals) {
2644
this.equals = options.equals;
2745
}
@@ -44,41 +62,47 @@ export namespace Signal {
4462
}
4563

4664
get() {
47-
if (alien.activeTrackId === WATCHER_TRACK_ID) {
65+
if (activeSub === WATCHER_PLACEHOLDER) {
4866
throw new Error('Cannot read from state inside watcher');
4967
}
50-
const lastSub = this.subsTail;
51-
const value = super.get();
52-
const newSub = this.subsTail;
53-
if (lastSub !== newSub && newSub instanceof Computed && newSub.watchCount) {
54-
this.onWatched();
68+
if (activeSub !== undefined && link(this, activeSub)) {
69+
const newSub = this.subsTail!;
70+
if (newSub instanceof Computed && newSub.watchCount) {
71+
this.onWatched();
72+
}
5573
}
56-
return value;
74+
return this.currentValue;
5775
}
5876

5977
set(value: T): void {
60-
if (alien.activeTrackId === WATCHER_TRACK_ID) {
78+
if (activeSub === WATCHER_PLACEHOLDER) {
6179
throw new Error('Cannot write to state inside watcher');
6280
}
6381
if (!this.equals(this.currentValue, value)) {
6482
this.currentValue = value;
6583
const subs = this.subs;
6684
if (subs !== undefined) {
67-
alien.propagate(subs);
85+
propagate(subs);
86+
drainQueuedEffects();
6887
}
6988
}
7089
}
7190
}
7291

73-
export class Computed<T = any> extends alien.Computed<T> {
92+
export class Computed<T = any> implements alien.Dependency, alien.Subscriber {
93+
subs: alien.Link | undefined = undefined;
94+
subsTail: alien.Link | undefined = undefined;
95+
deps: alien.Link | undefined = undefined;
96+
depsTail: alien.Link | undefined = undefined;
97+
flags = alien.SubscriberFlags.Dirty;
7498
isError = true;
7599
watchCount = 0;
100+
currentValue: T | undefined = undefined;
76101

77102
constructor(
78-
getter: () => T,
103+
private getter: () => T,
79104
private options?: Options<T>,
80105
) {
81-
super(getter);
82106
if (options && options.equals) {
83107
this.equals = options.equals;
84108
}
@@ -116,61 +140,71 @@ export namespace Signal {
116140
if (this.flags & alien.SubscriberFlags.Tracking) {
117141
throw new Error('Cycles detected');
118142
}
119-
if (alien.activeTrackId === WATCHER_TRACK_ID) {
143+
if (activeSub === WATCHER_PLACEHOLDER) {
120144
throw new Error('Cannot read from computed inside watcher');
121145
}
122-
const lastSub = this.subsTail;
123-
const value = super.get();
124-
const newSub = this.subsTail;
125-
if (lastSub !== newSub && newSub instanceof Computed && newSub.watchCount) {
126-
this.onWatched();
146+
147+
if (isDirty(this, this.flags)) {
148+
if (this.update()) {
149+
const subs = this.subs;
150+
if (subs !== undefined) {
151+
shallowPropagate(subs);
152+
}
153+
}
127154
}
155+
if (activeSub !== undefined && link(this, activeSub)) {
156+
const newSub = this.subsTail!;
157+
if (newSub instanceof Computed && newSub.watchCount) {
158+
this.onWatched();
159+
}
160+
}
161+
128162
if (this.isError) {
129-
throw value;
163+
throw this.currentValue;
130164
}
131-
return value;
165+
166+
return this.currentValue;
132167
}
133168

134169
update(): boolean {
135-
const prevSub = alien.activeSub;
136-
const prevTrackId = alien.activeTrackId;
137-
alien.setActiveSub(this, alien.nextTrackId());
138-
alien.startTrack(this);
170+
const prevSub = activeSub;
171+
activeSub = this;
172+
startTrack(this);
173+
const oldValue = this.currentValue;
139174
try {
140-
const oldValue = this.currentValue;
141-
const newValue = this.getter(oldValue);
175+
const newValue = this.getter();
142176
if (this.isError || !this.equals(oldValue!, newValue)) {
143177
this.isError = false;
144178
this.currentValue = newValue;
145179
return true;
146180
}
147181
return false;
148182
} catch (err) {
149-
this.isError = true;
150-
this.currentValue = err as any;
151-
return true;
183+
if (!this.isError || !this.equals(oldValue!, err as any)) {
184+
this.isError = true;
185+
this.currentValue = err as any;
186+
return true;
187+
}
188+
return false;
152189
} finally {
153-
let removeDeps!: AnySignal[];
154190
if (this.watchCount) {
155-
removeDeps = [];
156-
let link = this.depsTail ? this.depsTail.nextDep : this.deps;
157-
while (link) {
191+
for (
192+
let link = this.depsTail !== undefined ? this.depsTail.nextDep : this.deps;
193+
link !== undefined;
194+
link = link.nextDep
195+
) {
158196
const dep = link.dep as AnySignal;
159-
removeDeps.push(dep);
160-
link = link.nextDep;
197+
dep.onUnwatched();
161198
}
162199
}
163-
alien.setActiveSub(prevSub, prevTrackId);
164-
alien.endTrack(this);
200+
201+
activeSub = prevSub;
202+
endTrack(this);
203+
165204
if (this.watchCount) {
166-
for (const dep of removeDeps) {
167-
dep.onUnwatched();
168-
}
169-
let link = this.deps;
170-
while (link) {
205+
for (let link = this.deps; link !== undefined; link = link.nextDep) {
171206
const dep = link.dep as AnySignal;
172207
dep.onWatched();
173-
link = link.nextDep;
174208
}
175209
}
176210
}
@@ -180,56 +214,53 @@ export namespace Signal {
180214
// eslint-disable-next-line @typescript-eslint/no-explicit-any
181215
type AnySignal<T = any> = State<T> | Computed<T>;
182216

183-
const PENDING = 1 << 5;
184-
185217
export namespace subtle {
186-
export class Watcher extends alien.Effect {
187-
constructor(fn: () => void) {
188-
super(fn);
189-
}
218+
export class Watcher implements alien.Subscriber {
219+
deps: alien.Link | undefined = undefined;
220+
depsTail: alien.Link | undefined = undefined;
221+
flags = alien.SubscriberFlags.None;
222+
223+
constructor(private fn: () => void) {}
190224

191225
notify() {
192-
let flags = this.flags;
193-
if (flags & alien.SubscriberFlags.Dirty) {
226+
if (this.flags & alien.SubscriberFlags.Dirty) {
194227
this.run();
195-
} else if (flags & alien.SubscriberFlags.ToCheckDirty) {
196-
this.flags = PENDING;
197228
}
198229
}
199230

200231
run() {
201232
this.flags = alien.SubscriberFlags.None;
202-
const prevSub = alien.activeSub;
203-
const prevTrackId = alien.activeTrackId;
204-
alien.setActiveSub(undefined, WATCHER_TRACK_ID);
233+
const prevSub = activeSub;
234+
activeSub = WATCHER_PLACEHOLDER;
205235
try {
206236
this.fn();
207237
} finally {
208-
alien.setActiveSub(prevSub, prevTrackId);
238+
activeSub = prevSub;
209239
}
210240
}
211241

212242
watch(...signals: AnySignal[]): void {
213243
for (const signal of signals) {
214-
alien.link(signal, this);
215-
signal.onWatched();
244+
if (link(signal, this)) {
245+
signal.onWatched();
246+
}
216247
}
217248
this.flags = alien.SubscriberFlags.None;
218249
}
219250

220251
unwatch(...signals: AnySignal[]): void {
221-
alien.startTrack(this);
252+
startTrack(this);
222253
let dep = this.deps;
223254
while (dep) {
224255
if (!signals.includes(dep.dep as AnySignal)) {
225-
alien.link(dep.dep, this);
256+
link(dep.dep, this);
226257
}
227258
dep = dep.nextDep;
228259
}
229260
for (const signal of signals) {
230-
signal.onUnwatched();
261+
signal.onUnwatched(); // TODO: check if it's watched
231262
}
232-
alien.endTrack(this);
263+
endTrack(this);
233264
}
234265

235266
getPending() {

0 commit comments

Comments
 (0)