This commit is contained in:
Nicolas Bayet
2025-10-01 13:55:40 +02:00
parent 38574a1f3a
commit 1ddf2ceff9
3 changed files with 92 additions and 59 deletions
+9 -4
View File
@@ -1,9 +1,9 @@
export type ExecutionContext = { export type ExecutionContext = {
unsubcribe?: (scheduledContexts: Set<ExecutionContext>) => void; unsubcribe?: (scheduledContexts: Set<ExecutionContext>) => void;
update: Function; update: Function;
signals: Set<Signal>; atoms: Set<Atom>;
getParent: () => ExecutionContext | undefined; // getParent: () => ExecutionContext | undefined;
getChildren: () => ExecutionContext[]; // getChildren: () => ExecutionContext[];
meta: any; meta: any;
// schedule: () => void; // schedule: () => void;
}; };
@@ -13,6 +13,11 @@ export type customDirectives = Record<
(node: Element, value: string, modifier: string[]) => void (node: Element, value: string, modifier: string[]) => void
>; >;
export type Signal = { export type Atom = {
executionContexts: Set<ExecutionContext>; executionContexts: Set<ExecutionContext>;
// dependents: Set<Atom>;
}; };
// export type DerivedAtom = Atom & {
// dependencies: Set<Atom>;
// };
+7 -7
View File
@@ -10,20 +10,20 @@ export function getExecutionContext() {
export function makeExecutionContext({ export function makeExecutionContext({
update, update,
getParent, // getParent,
getChildren, // getChildren,
meta, meta,
}: { }: {
update: () => void; update: () => void;
getParent?: () => ExecutionContext | undefined; // getParent?: () => ExecutionContext | undefined;
getChildren?: () => ExecutionContext[]; // getChildren?: () => ExecutionContext[];
meta?: any; meta?: any;
}) { }) {
const executionContext: ExecutionContext = { const executionContext: ExecutionContext = {
update, update,
getParent: getParent!, // getParent: getParent!,
getChildren: getChildren!, // getChildren: getChildren!,
signals: new Set(), atoms: new Set(),
meta: meta || {}, meta: meta || {},
}; };
return executionContext; return executionContext;
+76 -48
View File
@@ -1,5 +1,5 @@
import { OwlError } from "../common/owl_error"; import { OwlError } from "../common/owl_error";
import { ExecutionContext, Signal } from "../common/types"; import { ExecutionContext, Atom } from "../common/types";
import { getExecutionContext, popExecutionContext, pushExecutionContext } from "./executionContext"; import { getExecutionContext, popExecutionContext, pushExecutionContext } from "./executionContext";
// Special key to subscribe to, to be notified of key creation/deletion // Special key to subscribe to, to be notified of key creation/deletion
@@ -77,14 +77,29 @@ export function toRaw<T extends Target, U extends Reactive<T>>(value: U | T): T
return targets.has(value) ? (targets.get(value) as T) : value; return targets.has(value) ? (targets.get(value) as T) : value;
} }
const targetToKeysToSignalItem = new WeakMap<Target, Map<PropertyKey, Signal>>(); const targetToKeysToAtomItem = new WeakMap<Target, Map<PropertyKey, Atom>>();
const scheduledSignals = new Set<Signal>(); const scheduledAtoms = new Set<Atom>();
function makeSignal() { function makeAtom() {
const signal: Signal = { const atom: Atom = {
executionContexts: new Set<ExecutionContext>(), executionContexts: new Set<ExecutionContext>(),
// dependents: new Set<Atom>(),
}; };
return signal; return atom;
}
function getTargetKeyAtom(target: Target, key: PropertyKey): Atom {
let keyToAtomItem: Map<PropertyKey, Atom> = targetToKeysToAtomItem.get(target)!;
if (!keyToAtomItem) {
keyToAtomItem = new Map();
targetToKeysToAtomItem.set(target, keyToAtomItem);
}
let atom = keyToAtomItem.get(key)!;
if (!atom) {
atom = makeAtom();
keyToAtomItem.set(key, atom);
}
return atom;
} }
/** /**
@@ -100,44 +115,37 @@ function onReadTargetKey(target: Target, key: PropertyKey): void {
const executionContext = getExecutionContext(); const executionContext = getExecutionContext();
if (!executionContext) return; if (!executionContext) return;
let keyToSignalItem: Map<PropertyKey, Signal> = targetToKeysToSignalItem.get(target)!; const atom = getTargetKeyAtom(target, key);
if (!keyToSignalItem) {
keyToSignalItem = new Map(); // observerAtoms.add(atom);
targetToKeysToSignalItem.set(target, keyToSignalItem); executionContext.atoms.add(atom);
} atom.executionContexts.add(executionContext);
let signal = keyToSignalItem.get(key)!;
if (!signal) {
signal = makeSignal();
keyToSignalItem.set(key, signal);
}
// observerSignals.add(signal);
executionContext.signals.add(signal);
signal.executionContexts.add(executionContext);
} }
let scheduled = false; let scheduled = false;
function scheduleSignal(signal: Signal) { function scheduleAtom(atom: Atom) {
scheduledSignals.add(signal); scheduledAtoms.add(atom);
// batched(processAtoms)();
if (scheduled) return; if (scheduled) return;
scheduled = true; scheduled = true;
Promise.resolve().then(() => { Promise.resolve().then(() => {
scheduled = false; scheduled = false;
processSignals(); processAtoms();
}); });
} }
function processSignals() { function processAtoms() {
const scheduledContexts = new Set( const scheduledContexts = new Set(
[...scheduledSignals.values()].map((s) => [...s.executionContexts]).flat() [...scheduledAtoms.values()].map((s) => [...s.executionContexts]).flat()
); );
// schedule before context.update in case there is write operations during update // schedule before context.update in case there is write operations during update
// todo: add a test in case there is write operations during update the test // todo: add a test in case there is write operations during update the test
// will break is scheduledSignals.clear(); is called after context.update(); // will break is scheduledAtoms.clear(); is called after context.update();
// that writes // that writes
scheduledSignals.clear(); scheduledAtoms.clear();
for (const ctx of [...scheduledContexts]) { for (const ctx of [...scheduledContexts]) {
removeSignalsFromContext(ctx); removeAtomsFromContext(ctx);
// custom unsubscribe depending on the context. // custom unsubscribe depending on the context.
// scheduledContexts might be updated while we're iterating over it. // scheduledContexts might be updated while we're iterating over it.
ctx.unsubcribe?.(scheduledContexts); ctx.unsubcribe?.(scheduledContexts);
@@ -173,15 +181,15 @@ function processSignals() {
* or deleted) * or deleted)
*/ */
function onWriteTargetKey(target: Target, key: PropertyKey): void { function onWriteTargetKey(target: Target, key: PropertyKey): void {
const keyToSignalItem = targetToKeysToSignalItem.get(target)!; const keyToAtomItem = targetToKeysToAtomItem.get(target)!;
if (!keyToSignalItem) { if (!keyToAtomItem) {
return; return;
} }
const signal = keyToSignalItem.get(key); const atom = keyToAtomItem.get(key);
if (!signal) { if (!atom) {
return; return;
} }
scheduleSignal(signal); scheduleAtom(atom);
} }
// Maps reactive objects to the underlying target // Maps reactive objects to the underlying target
@@ -240,31 +248,31 @@ export function reactive<T extends Target>(target: T): T {
return proxy; return proxy;
} }
function removeSignalsFromContext(executionContext: ExecutionContext) { function removeAtomsFromContext(executionContext: ExecutionContext) {
for (const sig of executionContext.signals) { for (const sig of executionContext.atoms) {
sig.executionContexts.delete(executionContext); sig.executionContexts.delete(executionContext);
} }
executionContext.signals.clear(); executionContext.atoms.clear();
} }
/** /**
* Unsubscribe an execution context and all its children from all signals * Unsubscribe an execution context and all its children from all atoms
* they are subscribed to. * they are subscribed to.
* *
* @param executionContext the context to unsubscribe * @param parentExecutionContext the context to unsubscribe
*/ */
function unsubscribeChildEffect( function unsubscribeChildEffect(
executionContext: ExecutionContext, parentExecutionContext: ExecutionContext,
scheduledContexts: Set<ExecutionContext> scheduledContexts: Set<ExecutionContext>
) { ) {
// executionContext.update = () => {}; // executionContext.update = () => {};
for (const children of executionContext.meta.children) { for (const children of parentExecutionContext.meta.children) {
children.meta.parent = undefined; children.meta.parent = undefined;
removeSignalsFromContext(children); removeAtomsFromContext(children);
scheduledContexts.delete(children); scheduledContexts.delete(children);
unsubscribeChildEffect(children, scheduledContexts); unsubscribeChildEffect(children, scheduledContexts);
} }
executionContext.meta.children.length = 0; parentExecutionContext.meta.children.length = 0;
} }
export function withoutReactivity<T extends (...args: any[]) => any>(fn: T): ReturnType<T> { export function withoutReactivity<T extends (...args: any[]) => any>(fn: T): ReturnType<T> {
pushExecutionContext(undefined!); pushExecutionContext(undefined!);
@@ -276,6 +284,7 @@ export function withoutReactivity<T extends (...args: any[]) => any>(fn: T): Ret
} }
return r; return r;
} }
export function effect(fn: Function) { export function effect(fn: Function) {
let parent = getExecutionContext(); let parent = getExecutionContext();
// todo: is it useful? // todo: is it useful?
@@ -287,13 +296,13 @@ export function effect(fn: Function) {
unsubscribeChildEffect(executionContext, scheduledContexts); unsubscribeChildEffect(executionContext, scheduledContexts);
}, },
update: fn, update: fn,
getParent: () => { // getParent: () => {
return executionContext.meta.parent; // return executionContext.meta.parent;
}, // },
getChildren: () => { // getChildren: () => {
return executionContext.meta.children || []; // return executionContext.meta.children || [];
}, // },
signals: new Set(), atoms: new Set(),
meta: { meta: {
parent: parent, parent: parent,
children: [], children: [],
@@ -311,6 +320,25 @@ export function effect(fn: Function) {
} }
} }
// const dependentStack: any[][] = [];
// const derivedDependecies = new Set<Atom>();
// // const derrivedToAtom = new WeakMap<Function, Atom>();
// export function derived(fn: Function) {
// const derivedAtom: DerivedAtom = {
// executionContexts: new Set<ExecutionContext>(),
// // parent: null,
// dependencies: new Set<Atom>(),
// dependents: new Set<Atom>(),
// };
// return () => {
// dependentStack.push([]);
// fn();
// dependentStack.pop();
// };
// }
/** /**
* Creates a basic proxy handler for regular objects and arrays. * Creates a basic proxy handler for regular objects and arrays.
* *