Skip to content

Commit

Permalink
refactor: rewrite in function based API
Browse files Browse the repository at this point in the history
  • Loading branch information
johnsoncodehk committed Jan 11, 2025
1 parent b1c20bd commit f82e014
Show file tree
Hide file tree
Showing 27 changed files with 280 additions and 1,066 deletions.
6 changes: 3 additions & 3 deletions benchs/complex.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ boxplot(() => {
let last = src;
for (let j = 0; j < h; j++) {
const prev = last;
last = computed(() => ({ [`${i}-${j}`]: prev.get() }));
last = computed(() => ({ [`${i}-${j}`]: prev() }));
}
effect(() => last.get());
effect(() => last());
}

yield () => src.set({ upstream: src.get() });
yield () => src({ upstream: src() });
})
.args('h', [1, 10, 100])
.args('w', [1, 10, 100]);
Expand Down
10 changes: 5 additions & 5 deletions benchs/memoryUsage.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ console.log(`signal: ${((end - start) / 1024).toFixed(2)} KB`);

start = end;

const computeds = Array.from({ length: 10000 }, (_, i) => computed(() => signals[i].get() + 1));
const computeds = Array.from({ length: 10000 }, (_, i) => computed(() => signals[i]() + 1));

globalThis.gc();
end = process.memoryUsage().heapUsed;
Expand All @@ -21,7 +21,7 @@ console.log(`computed: ${((end - start) / 1024).toFixed(2)} KB`);

start = end;

Array.from({ length: 10000 }, (_, i) => effect(() => computeds[i].get()));
Array.from({ length: 10000 }, (_, i) => effect(() => computeds[i]()));

globalThis.gc();
end = process.memoryUsage().heapUsed;
Expand All @@ -38,12 +38,12 @@ for (let i = 0; i < w; i++) {
let last = src;
for (let j = 0; j < h; j++) {
const prev = last;
last = computed(() => prev.get() + 1);
effect(() => last.get());
last = computed(() => prev() + 1);
effect(() => last());
}
}

src.set(src.get() + 1);
src(src() + 1);

globalThis.gc();
end = process.memoryUsage().heapUsed;
Expand Down
10 changes: 6 additions & 4 deletions benchs/propagate.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { run, bench, boxplot } from 'mitata';
import { computed, effect, signal } from '../esm/index.mjs';
import { getDefaultSystem } from '../esm/index.mjs';

const { computed, effect, signal } = getDefaultSystem();

boxplot(() => {
bench('propagate: $w * $h', function* (state) {
Expand All @@ -10,11 +12,11 @@ boxplot(() => {
let last = src;
for (let j = 0; j < h; j++) {
const prev = last;
last = computed(() => prev.get() + 1);
last = computed(() => prev() + 1);
}
effect(() => last.get());
effect(() => last());
}
yield () => src.set(src.get() + 1);
yield () => src(src() + 1);
})
.args('h', [1, 10, 100])
.args('w', [1, 10, 100]);
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"build:esm": "esbuild src/index.ts --bundle --format=esm --outfile=esm/index.mjs",
"build:cjs": "esbuild src/index.ts --bundle --format=cjs --outfile=cjs/index.cjs",
"test": "vitest run",
"bench": "npm run build:esm && node --jitless --expose-gc benchs/propagate.mjs && node --jitless --expose-gc benchs/complex.mjs",
"bench": "npm run build:esm && node --jitless --expose-gc benchs/propagate.mjs",
"bench:memory": "npm run build:esm && node --expose-gc benchs/memoryUsage.mjs"
},
"devDependencies": {
Expand Down
13 changes: 0 additions & 13 deletions src/batch.ts

This file was deleted.

65 changes: 0 additions & 65 deletions src/computed.ts

This file was deleted.

211 changes: 211 additions & 0 deletions src/defaultSystem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
import { createSystem, Dependency, Subscriber, SubscriberFlags } from './system.js';

interface Effect extends Subscriber, Dependency {
fn(): void;
}

interface Computed<T = any> extends Signal<T | undefined>, Subscriber {
getter: (cachedValue?: T) => T;
}

interface Signal<T = any> extends Dependency {
currentValue: T;
}

export function createDefaultSystem() {
const {
drainQueuedEffects,
endTrack,
isDirty,
link,
propagate,
runInnerEffects,
shallowPropagate,
startTrack,
} = createSystem({
isComputed,
isEffect,
updateComputed,
notifyEffect,
});

let batchDepth = 0;
let activeSub: Subscriber | undefined;

function startBatch(): void {
++batchDepth;
}

function endBatch(): void {
if (!--batchDepth) {
drainQueuedEffects();
}
}

function untrack<T>(fn: () => T): T {
const prevSub = activeSub;
activeSub = undefined;
try {
return fn();
} finally {
activeSub = prevSub;
}
}

type WriteableSignal<T> = {
(): T;
(value: T): void;
};

function signal<T>(): WriteableSignal<T | undefined>;
function signal<T>(oldValue: T): WriteableSignal<T>;
function signal<T>(oldValue?: T): WriteableSignal<T | undefined> {
return getSetSignalValue.bind({
currentValue: oldValue,
subs: undefined,
subsTail: undefined,
}) as WriteableSignal<T | undefined>;
}

function computed<T>(getter: (cachedValue?: T) => T): () => T {
return getComputedValue.bind({
currentValue: undefined,
subs: undefined,
subsTail: undefined,
deps: undefined,
depsTail: undefined,
flags: SubscriberFlags.Dirty,
getter: getter as (cachedValue?: unknown) => unknown,
}) as () => T;
}

function effect<T>(fn: () => T): () => void {
const e: Effect = {
fn,
subs: undefined,
subsTail: undefined,
deps: undefined,
depsTail: undefined,
flags: SubscriberFlags.None,
};
if (activeSub !== undefined) {
link(e, activeSub);
}
runEffect(e);
return stopEffect.bind(e);
}

return {
get batchDepth() {
return batchDepth;
},
get activeSub() {
return activeSub;
},
set activeSub(sub) {
activeSub = sub;
},
startBatch,
endBatch,
untrack,
signal,
computed,
effect,
};

function isComputed(sub: Subscriber & Dependency): sub is Computed {
return 'getter' in sub;
}

function isEffect(sub: Subscriber): sub is Effect {
return !('getter' in sub);
}

function notifyEffect(effect: Effect): void {
const flags = effect.flags;
if (
flags & (SubscriberFlags.ToCheckDirty | SubscriberFlags.Dirty)
&& isDirty(effect, flags)
) {
runEffect(effect);
return;
}
if (flags & SubscriberFlags.InnerEffectsPending) {
effect.flags = flags & ~SubscriberFlags.InnerEffectsPending;
runInnerEffects(effect.deps!);
}
}

function updateComputed(computed: Computed): boolean {
const prevSub = activeSub;
activeSub = computed;
startTrack(computed);
try {
const oldValue = computed.currentValue;
const newValue = computed.getter(oldValue);
if (oldValue !== newValue) {
computed.currentValue = newValue;
return true;
}
return false;
} finally {
activeSub = prevSub;
endTrack(computed);
}
}

function getComputedValue<T>(this: Computed<T>): T {
const flags = this.flags;
if (
flags & (SubscriberFlags.ToCheckDirty | SubscriberFlags.Dirty)
&& isDirty(this, flags)
) {
if (updateComputed(this)) {
const subs = this.subs;
if (subs !== undefined) {
shallowPropagate(subs);
}
}
}
if (activeSub !== undefined) {
link(this, activeSub);
}
return this.currentValue!;
}

function getSetSignalValue<T>(this: Signal<T>, ...value: [T]): T | void {
if (value.length) {
if (this.currentValue !== (this.currentValue = value[0])) {
const subs = this.subs;
if (subs !== undefined) {
propagate(subs);
if (!batchDepth) {
drainQueuedEffects();
}
}
}
} else {
if (activeSub !== undefined) {
link(this, activeSub);
}
return this.currentValue;
}
}

function runEffect(e: Effect): void {
const prevSub = activeSub;
activeSub = e;
startTrack(e);
try {
e.fn();
} finally {
activeSub = prevSub;
endTrack(e);
}
}

function stopEffect(this: Subscriber): void {
startTrack(this);
endTrack(this);
}
}
Loading

0 comments on commit f82e014

Please sign in to comment.