This commit is contained in:
Nicolas Bayet
2025-10-02 15:22:13 +02:00
parent a309ec498b
commit d0cdc489b1
4 changed files with 96 additions and 93 deletions
+12 -7
View File
@@ -1,10 +1,11 @@
export type ExecutionContext = { export type ExecutionContext = {
onReadAtom: (atom: Atom) => void;
unsubcribe?: (scheduledContexts: Set<ExecutionContext>) => void; unsubcribe?: (scheduledContexts: Set<ExecutionContext>) => void;
update: Function; update?: Function;
atoms: Set<Atom>; atoms?: Set<Atom>;
meta?: any;
// getParent: () => ExecutionContext | undefined; // getParent: () => ExecutionContext | undefined;
// getChildren: () => ExecutionContext[]; // getChildren: () => ExecutionContext[];
meta: any;
// schedule: () => void; // schedule: () => void;
}; };
@@ -15,9 +16,13 @@ export type customDirectives = Record<
export type Atom = { export type Atom = {
executionContexts: Set<ExecutionContext>; executionContexts: Set<ExecutionContext>;
// dependents: Set<Atom>; dependents: Set<DerivedAtom>;
getValue: () => any;
}; };
// export type DerivedAtom = Atom & { export type OldValue = any;
// dependencies: Set<Atom>;
// }; export type DerivedAtom = Atom & {
dependencies: Map<Atom, OldValue>;
computed: boolean;
};
+19 -22
View File
@@ -1,13 +1,12 @@
import { OwlError } from "../common/owl_error"; import { OwlError } from "../common/owl_error";
import { ExecutionContext } from "../common/types"; import { Atom, ExecutionContext } from "../common/types";
import type { App, Env } from "./app"; import type { App, Env } from "./app";
import { BDom, VNode } from "./blockdom"; import { BDom, VNode } from "./blockdom";
import { makeTaskContext, TaskContext } from "./cancellableContext"; import { makeTaskContext, TaskContext } from "./cancellableContext";
import { Component, ComponentConstructor, Props } from "./component"; import { Component, ComponentConstructor, Props } from "./component";
import { fibersInError } from "./error_handling"; import { fibersInError } from "./error_handling";
import { makeExecutionContext } from "./executionContext";
import { Fiber, makeChildFiber, makeRootFiber, MountFiber, MountOptions } from "./fibers"; import { Fiber, makeChildFiber, makeRootFiber, MountFiber, MountOptions } from "./fibers";
import { reactive, targets, withoutReactivity } from "./reactivity"; import { addAtomToContext, reactive, targets, withoutReactivity } from "./reactivity";
import { STATUS } from "./status"; import { STATUS } from "./status";
let currentNode: ComponentNode | null = null; let currentNode: ComponentNode | null = null;
@@ -106,16 +105,14 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
this.props = props; this.props = props;
this.parentKey = parentKey; this.parentKey = parentKey;
this.taskContext = makeTaskContext(); this.taskContext = makeTaskContext();
this.executionContext = makeExecutionContext({ this.executionContext = {
meta: this,
update: () => { update: () => {
this.render(false); this.render(false);
}, },
getParent: () => this.parent?.executionContext, onReadAtom: (atom: Atom) => addAtomToContext(atom, this.executionContext),
getChildren: () => { atoms: new Set<Atom>(),
return Object.values(this.children).map((c) => c.executionContext); };
},
meta: this,
});
const defaultProps = C.defaultProps; const defaultProps = C.defaultProps;
props = Object.assign({}, props); props = Object.assign({}, props);
if (defaultProps) { if (defaultProps) {
@@ -123,12 +120,12 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
} }
const env = (parent && parent.childEnv) || app.env; const env = (parent && parent.childEnv) || app.env;
this.childEnv = env; this.childEnv = env;
for (const key in props) { // for (const key in props) {
const prop = props[key]; // const prop = props[key];
if (prop && typeof prop === "object" && targets.has(prop)) { // if (prop && typeof prop === "object" && targets.has(prop)) {
props[key] = useState(prop); // props[key] = useState(prop);
} // }
} // }
this.component = new C(props, env, this); this.component = new C(props, env, this);
const ctx = Object.assign(Object.create(this.component), { this: this.component }); const ctx = Object.assign(Object.create(this.component), { this: this.component });
this.renderFn = app.getTemplate(C.template).bind(this.component, ctx, this); this.renderFn = app.getTemplate(C.template).bind(this.component, ctx, this);
@@ -271,12 +268,12 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
} }
currentNode = this; currentNode = this;
for (const key in props) { // for (const key in props) {
const prop = props[key]; // const prop = props[key];
if (prop && typeof prop === "object" && targets.has(prop)) { // if (prop && typeof prop === "object" && targets.has(prop)) {
props[key] = useState(prop); // props[key] = useState(prop);
} // }
} // }
currentNode = null; currentNode = null;
let prom: Promise<any[]>; let prom: Promise<any[]>;
withoutReactivity(() => { withoutReactivity(() => {
+8 -29
View File
@@ -8,27 +8,6 @@ export function getExecutionContext() {
return executionContexts[executionContexts.length - 1]; return executionContexts[executionContexts.length - 1];
} }
export function makeExecutionContext({
update,
// getParent,
// getChildren,
meta,
}: {
update: () => void;
// getParent?: () => ExecutionContext | undefined;
// getChildren?: () => ExecutionContext[];
meta?: any;
}) {
const executionContext: ExecutionContext = {
update,
// getParent: getParent!,
// getChildren: getChildren!,
atoms: new Set(),
meta: meta || {},
};
return executionContext;
}
export function pushExecutionContext(context: ExecutionContext) { export function pushExecutionContext(context: ExecutionContext) {
executionContexts.push(context); executionContexts.push(context);
} }
@@ -37,11 +16,11 @@ export function popExecutionContext() {
executionContexts.pop(); executionContexts.pop();
} }
export function makeExecutionContext({ update, meta }: { update: () => void; meta?: any }) { // export function makeExecutionContext({ update, meta }: { update: () => void; meta?: any }) {
const executionContext: ExecutionContext = { // const executionContext: ExecutionContext = {
update, // update,
atoms: new Set(), // atoms: new Set(),
meta: meta || {}, // meta: meta || {},
}; // };
return executionContext; // return executionContext;
} // }
+57 -35
View File
@@ -1,5 +1,5 @@
import { OwlError } from "../common/owl_error"; import { OwlError } from "../common/owl_error";
import { ExecutionContext, Atom } from "../common/types"; import { ExecutionContext, Atom, DerivedAtom, OldValue } 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
@@ -80,10 +80,11 @@ export function toRaw<T extends Target, U extends Reactive<T>>(value: U | T): T
const targetToKeysToAtomItem = new WeakMap<Target, Map<PropertyKey, Atom>>(); const targetToKeysToAtomItem = new WeakMap<Target, Map<PropertyKey, Atom>>();
const scheduledAtoms = new Set<Atom>(); const scheduledAtoms = new Set<Atom>();
function makeAtom() { function makeAtom(getValue: () => any): Atom {
const atom: Atom = { const atom: Atom = {
executionContexts: new Set<ExecutionContext>(), executionContexts: new Set<ExecutionContext>(),
// dependents: new Set<Atom>(), dependents: new Set<Atom>(),
// getValue,
}; };
return atom; return atom;
} }
@@ -96,12 +97,17 @@ function getTargetKeyAtom(target: Target, key: PropertyKey): Atom {
} }
let atom = keyToAtomItem.get(key)!; let atom = keyToAtomItem.get(key)!;
if (!atom) { if (!atom) {
atom = makeAtom(); atom = makeAtom(() => Reflect.get(target, key));
keyToAtomItem.set(key, atom); keyToAtomItem.set(key, atom);
} }
return atom; return atom;
} }
export function addAtomToContext(atom: Atom, executionContext: ExecutionContext) {
executionContext.atoms.add(atom);
atom.executionContexts.add(executionContext);
}
/** /**
* Observes a given key on a target with an callback. The callback will be * Observes a given key on a target with an callback. The callback will be
* called when the given key changes on the target. * called when the given key changes on the target.
@@ -111,15 +117,9 @@ function getTargetKeyAtom(target: Target, key: PropertyKey): Atom {
* or deletion) * or deletion)
* @param callback the function to call when the key changes * @param callback the function to call when the key changes
*/ */
function onReadTargetKey(target: Target, key: PropertyKey): void { function onReadTargetKey(target: Target, key: PropertyKey, receiver: any): void {
const executionContext = getExecutionContext(); const executionContext = getExecutionContext();
if (!executionContext) return; executionContext?.onReadAtom(getTargetKeyAtom(target, key));
const atom = getTargetKeyAtom(target, key);
// observerAtoms.add(atom);
executionContext.atoms.add(atom);
atom.executionContexts.add(executionContext);
} }
let scheduled = false; let scheduled = false;
@@ -134,7 +134,20 @@ function scheduleAtom(atom: Atom) {
}); });
} }
function processDerivedAtoms() {
const processedAtoms = new Set<Atom>();
for (const atom of scheduledAtoms) {
for (const dep of atom.dependents) {
if (processedAtoms.has(dep)) continue;
dep.computed = false;
processedAtoms.add(dep);
}
}
}
function processAtoms() { function processAtoms() {
processDerivedAtoms();
const scheduledContexts = new Set( const scheduledContexts = new Set(
[...scheduledAtoms.values()].map((s) => [...s.executionContexts]).flat() [...scheduledAtoms.values()].map((s) => [...s.executionContexts]).flat()
); );
@@ -154,7 +167,7 @@ function processAtoms() {
for (const context of scheduledContexts) { for (const context of scheduledContexts) {
pushExecutionContext(context); pushExecutionContext(context);
try { try {
context.update(); context.update?.();
} finally { } finally {
popExecutionContext(); popExecutionContext();
} }
@@ -296,12 +309,7 @@ export function effect(fn: Function) {
unsubscribeChildEffect(executionContext, scheduledContexts); unsubscribeChildEffect(executionContext, scheduledContexts);
}, },
update: fn, update: fn,
// getParent: () => { onReadAtom: (atom: Atom) => addAtomToContext(atom, executionContext),
// return executionContext.meta.parent;
// },
// getChildren: () => {
// return executionContext.meta.children || [];
// },
atoms: new Set(), atoms: new Set(),
meta: { meta: {
parent: parent, parent: parent,
@@ -320,24 +328,38 @@ export function effect(fn: Function) {
} }
} }
// const dependentStack: any[][] = []; export function derived(fn: Function) {
let lastValue: any;
// const derivedDependecies = new Set<Atom>(); const derivedAtom: DerivedAtom = {
// // const derrivedToAtom = new WeakMap<Function, Atom>(); executionContexts: new Set<ExecutionContext>(),
// export function derived(fn: Function) { dependents: new Set<Atom>(),
// const derivedAtom: DerivedAtom = { dependencies: new Map<Atom, OldValue>(),
// executionContexts: new Set<ExecutionContext>(), getValue: () => lastValue,
// // parent: null, computed: false,
// dependencies: new Set<Atom>(), };
// dependents: new Set<Atom>(),
// };
// return () => { return () => {
// dependentStack.push([]); const executionContext = getExecutionContext();
// fn(); executionContext?.onReadAtom(derivedAtom);
// dependentStack.pop(); if (derivedAtom.computed) return lastValue;
// };
// } const derivedExecutionContext: ExecutionContext = {
onReadAtom: (atom: Atom) => {
atom.dependents.add(derivedAtom);
// derivedAtom.executionContexts.add(executionContext);
},
};
pushExecutionContext(derivedExecutionContext);
try {
lastValue = fn();
} finally {
popExecutionContext();
}
derivedAtom.computed = true;
return lastValue;
};
}
/** /**
* Creates a basic proxy handler for regular objects and arrays. * Creates a basic proxy handler for regular objects and arrays.