Skip to content

Commit

Permalink
Merge improvements (#1)
Browse files Browse the repository at this point in the history
More implementations of Signal concepts & clarifications
  • Loading branch information
wycats authored Apr 7, 2024
1 parent 0d35f78 commit 6aee078
Show file tree
Hide file tree
Showing 37 changed files with 2,227 additions and 323 deletions.
1 change: 1 addition & 0 deletions .env.test
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
FISHY=true
8 changes: 0 additions & 8 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,4 @@
{
"root": true,
"ignorePatterns": [
"*",
"!.eslintrc.json",
"!tsconfig.json",
"!package.json",
"!.vscode",
"!vitest.workspace.ts"
],
"extends": ["plugin:@starbeam-dev/library:recommended"]
}
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@
},
"devDependencies": {
"@starbeam-dev/eslint-plugin": "^1.0.4",
"@types/node": "^20.12.3",
"@types/node": "^20.12.5",
"@vitest/ui": "^1.4.0",
"esyes": "^1.0.3",
"happy-dom": "^14.3.10",
"terser": "^5.30.2",
"happy-dom": "^14.6.1",
"terser": "^5.30.3",
"turbo": "^1.13.2",
"typedoc": "^0.25.12",
"typedoc-material-theme": "^1.0.2",
"typescript": "^5.4.3",
"vite": "^5.2.7",
"typescript": "^5.4.4",
"vite": "^5.2.8",
"vitest": "^1.4.0"
},
"author": "",
Expand Down
17 changes: 12 additions & 5 deletions packages/@starbeam-lite/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,19 @@
"type": "module",
"version": "0.0.0",
"exports": {
".": "./src/index.ts"
".": "./src/index.ts",
"./fishy": "./src/fishy.ts",
"./subtle": "./src/subtle.ts"
},
"publishConfig": {
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"./subtle": {
"types": "./dist/subtle.d.ts",
"default": "./dist/subtle.js"
}
}
},
Expand All @@ -27,14 +33,15 @@
},
"devDependencies": {
"@rollup/plugin-terser": "^0.4.4",
"@swc/core": "^1.4.11",
"@starbeam-dev/eslint-plugin": "^1.0.4",
"@swc/core": "^1.4.12",
"@workspace/build-utils": "workspace:^",
"@workspace/test-utils": "workspace:^",
"rollup-plugin-swc-minify": "^1.1.0",
"terser": "^5.30.0",
"typescript": "^5.4.3",
"terser": "^5.30.3",
"typescript": "^5.4.4",
"unplugin-swc": "^1.4.4",
"vite": "^5.2.7",
"vite": "^5.2.8",
"vitest": "^1.4.0"
},
"peerDependencies": {
Expand Down
18 changes: 18 additions & 0 deletions packages/@starbeam-lite/core/src/fishy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
let FISHY_UNTRACKED = false;

export function untrack<T>(callback: () => T): T {
FISHY_UNTRACKED = true;
try {
return callback();
} finally {
FISHY_UNTRACKED = false;
}
}

export function isFishyUntracked(): boolean {
if (import.meta.env.MODE === "test") {
return FISHY_UNTRACKED;
} else {
return true;
}
}
100 changes: 48 additions & 52 deletions packages/@starbeam-lite/core/src/higher-level/cached-formula.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,69 +2,65 @@ import { now, TAG } from "@starbeam-lite/shared";

import { FormulaTag } from "../primitives/formula.js";
import * as runtime from "../primitives/runtime.js";
import type { Tag, Tagged, TagSnapshot } from "../primitives/tag.js";
import type { Tagged, TagSnapshot } from "../primitives/tag.js";
import { lastUpdated } from "../primitives/tag.js";

export class CachedFormula<T> implements Tagged<T> {
[TAG]: Tag;
readonly #compute: () => T;
readonly #last: {
formula: FormulaTag;
};
[TAG]: Tag = new FormulaTag();

read: () => T;
}

export class FinalizedFormula<T> {
#lastValidated = now();
#dependencies: TagSnapshot;

constructor(dependencies: TagSnapshot) {
this.#dependencies = dependencies;
static create<T>(compute: () => T): CachedFormula<T> {
return new CachedFormula(compute);
}

isStale(): boolean {
return (
Math.max(...this.#dependencies.map(lastUpdated)) > this.#lastValidated
);
}
readonly #compute: () => T;
#last: {
children: TagSnapshot;
validated: number;
value: T;
} | null = null;

update() {
const done = runtime.start();
readonly [TAG]: FormulaTag = new FormulaTag();

return {
done: () => {
this.#dependencies = done();
this.#lastValidated = now();
return this;
},
};
private constructor(compute: () => T) {
this.#compute = compute;
}
}

// export function FinalizedFormula(children: TagSnapshot): FinalizedFormula {
// let lastValidated = NOW.now;
readonly read = (): T => {
const value = this.#evaluate();
runtime.consume(this[TAG]);
return value;
};

// const isStale = () => lastUpdated(...children).at > lastValidated.at;
#evaluate() {
const tag = this[TAG];

// function update() {
// const done = getRuntime().start();
if (this.#last === null) {
const done = runtime.start();
const value = this.#compute();
const children = [...done()];

// return {
// done: () => {
// children = done();
// lastValidated = NOW.now;
// return formula;
// },
// } satisfies InitializingTrackingFrame;
// }
this.#last = { children, validated: now(), value };
tag.updated(children);
} else if (isStale(this.#last)) {
const done = runtime.start();

// const formula = {
// isStale,
// children: () => children,
// update,
// } satisfies FinalizedFormula;
this.#last = {
value: this.#compute(),
children: [...done()],
validated: now(),
};
tag.updated(this.#last.children);
}

// return formula;
// }
runtime.consume(tag);
return this.#last.value;
}
}

function isStale({
children,
validated,
}: {
children: TagSnapshot;
validated: number;
}) {
return Math.max(...children.map(lastUpdated)) > validated;
}
4 changes: 3 additions & 1 deletion packages/@starbeam-lite/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export {};
export { CachedFormula } from "./higher-level/cached-formula.js";
export { Cell } from "./primitives/cell.js";
export { Formula } from "./primitives/formula.js";
62 changes: 24 additions & 38 deletions packages/@starbeam-lite/core/src/primitives/cell.ts
Original file line number Diff line number Diff line change
@@ -1,88 +1,74 @@
import { bump, consume, now, TAG } from "@starbeam-lite/shared";
import { bump, consume, TAG } from "@starbeam-lite/shared";

import { isFishyUntracked } from "../fishy.js";
import { notify } from "./runtime.js";
import type { Subscription } from "./subscriptions.js";
import type { Tagged } from "./tag.js";

export class DependencyTag {
#revision: number;
subscriptions: Set<Subscription> | undefined;

constructor(revision: number) {
this.#revision = revision;
}

update(revision: number): void {
this.#revision = revision;
}

get lastUpdated(): number {
return this.#revision;
}
}

export class MutableTag {
static create(revision: number = bump()): MutableTag {
return new MutableTag(revision);
}

readonly type = "mutable";
readonly #tag: DependencyTag;
#frozen = false;
#lastUpdated: number;
#dependency: MutableTag | null = this;
subscriptions: Set<Subscription> | undefined;

private constructor(revision: number) {
this.#tag = new DependencyTag(revision);
this.#lastUpdated = revision;
}

get dependency(): DependencyTag | null {
return this.#frozen ? null : this.#tag;
get dependency(): MutableTag | null {
return this.#dependency;
}

get lastUpdated(): number {
return this.#tag.lastUpdated;
}

get isFrozen(): boolean {
return this.#frozen;
return this.#lastUpdated;
}

consume(): void {
consume(this.#tag);
if (!isFishyUntracked()) {
consume(this);
}
}

mark(): void {
if (import.meta.env.DEV && this.#frozen) {
if (import.meta.env.DEV && this.#dependency === null) {
throw new Error("Attempted to update a freezable tag, but it was frozen");
}

this.#tag.update(now());
notify(this);
if (!isFishyUntracked()) {
this.#lastUpdated = bump();
notify(this);
}
}

freeze(): void {
this.#frozen = true;
this.#dependency = null;
}
}

export type Equality<T> = (a: T, b: T) => boolean;

export class Cell<T> implements Tagged<T> {
static create<T>(value: T, equals = Object.is): Cell<T> {
static create<T>(value: T, equals: Equality<T> = Object.is): Cell<T> {
return new Cell(value, equals);
}

#value: T;
readonly #equals: Equality<T>;
readonly [TAG]: MutableTag;

constructor(value: T, equals: Equality<T>) {
private constructor(value: T, equals: Equality<T>) {
this.#value = value;
this.#equals = equals;
this[TAG] = MutableTag.create();
}

read(): T {
consume(this[TAG]);
this[TAG].consume();

return this.#value;
}

Expand All @@ -92,14 +78,14 @@ export class Cell<T> implements Tagged<T> {
}

this.#value = value;

this[TAG].mark();

return true;
}

update(updater: (value: T) => T): void {
// Intentionally avoid consuming the value in in-place updates.
this.set(updater(this.#value));
this.set(updater(this.read()));
}

freeze(): void {
Expand Down
Loading

0 comments on commit 6aee078

Please sign in to comment.