diff --git a/src/common/types.ts b/src/common/types.ts index e35da22b..a28d8a68 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -1,10 +1,11 @@ export type ExecutionContext = { + onReadAtom: (atom: Atom) => void; unsubcribe?: (scheduledContexts: Set) => void; - update: Function; - atoms: Set; + update?: Function; + atoms?: Set; + meta?: any; // getParent: () => ExecutionContext | undefined; // getChildren: () => ExecutionContext[]; - meta: any; // schedule: () => void; }; @@ -15,9 +16,13 @@ export type customDirectives = Record< export type Atom = { executionContexts: Set; - // dependents: Set; + dependents: Set; + getValue: () => any; }; -// export type DerivedAtom = Atom & { -// dependencies: Set; -// }; +export type OldValue = any; + +export type DerivedAtom = Atom & { + dependencies: Map; + computed: boolean; +}; diff --git a/src/runtime/component_node.ts b/src/runtime/component_node.ts index 522d18ff..d143c39d 100644 --- a/src/runtime/component_node.ts +++ b/src/runtime/component_node.ts @@ -1,13 +1,12 @@ import { OwlError } from "../common/owl_error"; -import { ExecutionContext } from "../common/types"; +import { Atom, ExecutionContext } from "../common/types"; import type { App, Env } from "./app"; import { BDom, VNode } from "./blockdom"; import { makeTaskContext, TaskContext } from "./cancellableContext"; import { Component, ComponentConstructor, Props } from "./component"; import { fibersInError } from "./error_handling"; -import { makeExecutionContext } from "./executionContext"; 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"; let currentNode: ComponentNode | null = null; @@ -106,16 +105,14 @@ export class ComponentNode

implements VNode { this.render(false); }, - getParent: () => this.parent?.executionContext, - getChildren: () => { - return Object.values(this.children).map((c) => c.executionContext); - }, - meta: this, - }); + onReadAtom: (atom: Atom) => addAtomToContext(atom, this.executionContext), + atoms: new Set(), + }; const defaultProps = C.defaultProps; props = Object.assign({}, props); if (defaultProps) { @@ -123,12 +120,12 @@ export class ComponentNode

implements VNode implements VNode; withoutReactivity(() => { diff --git a/src/runtime/executionContext.ts b/src/runtime/executionContext.ts index cd386c85..b2cd344a 100644 --- a/src/runtime/executionContext.ts +++ b/src/runtime/executionContext.ts @@ -8,27 +8,6 @@ export function getExecutionContext() { 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) { executionContexts.push(context); } @@ -37,11 +16,11 @@ export function popExecutionContext() { executionContexts.pop(); } -export function makeExecutionContext({ update, meta }: { update: () => void; meta?: any }) { - const executionContext: ExecutionContext = { - update, - atoms: new Set(), - meta: meta || {}, - }; - return executionContext; -} +// export function makeExecutionContext({ update, meta }: { update: () => void; meta?: any }) { +// const executionContext: ExecutionContext = { +// update, +// atoms: new Set(), +// meta: meta || {}, +// }; +// return executionContext; +// } diff --git a/src/runtime/reactivity.ts b/src/runtime/reactivity.ts index ecffcd76..3aeeb940 100644 --- a/src/runtime/reactivity.ts +++ b/src/runtime/reactivity.ts @@ -1,5 +1,5 @@ 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"; // Special key to subscribe to, to be notified of key creation/deletion @@ -80,10 +80,11 @@ export function toRaw>(value: U | T): T const targetToKeysToAtomItem = new WeakMap>(); const scheduledAtoms = new Set(); -function makeAtom() { +function makeAtom(getValue: () => any): Atom { const atom: Atom = { executionContexts: new Set(), - // dependents: new Set(), + dependents: new Set(), + // getValue, }; return atom; } @@ -96,12 +97,17 @@ function getTargetKeyAtom(target: Target, key: PropertyKey): Atom { } let atom = keyToAtomItem.get(key)!; if (!atom) { - atom = makeAtom(); + atom = makeAtom(() => Reflect.get(target, key)); keyToAtomItem.set(key, 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 * called when the given key changes on the target. @@ -111,15 +117,9 @@ function getTargetKeyAtom(target: Target, key: PropertyKey): Atom { * or deletion) * @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(); - if (!executionContext) return; - - const atom = getTargetKeyAtom(target, key); - - // observerAtoms.add(atom); - executionContext.atoms.add(atom); - atom.executionContexts.add(executionContext); + executionContext?.onReadAtom(getTargetKeyAtom(target, key)); } let scheduled = false; @@ -134,7 +134,20 @@ function scheduleAtom(atom: Atom) { }); } +function processDerivedAtoms() { + const processedAtoms = new Set(); + for (const atom of scheduledAtoms) { + for (const dep of atom.dependents) { + if (processedAtoms.has(dep)) continue; + dep.computed = false; + processedAtoms.add(dep); + } + } +} + function processAtoms() { + processDerivedAtoms(); + const scheduledContexts = new Set( [...scheduledAtoms.values()].map((s) => [...s.executionContexts]).flat() ); @@ -154,7 +167,7 @@ function processAtoms() { for (const context of scheduledContexts) { pushExecutionContext(context); try { - context.update(); + context.update?.(); } finally { popExecutionContext(); } @@ -296,12 +309,7 @@ export function effect(fn: Function) { unsubscribeChildEffect(executionContext, scheduledContexts); }, update: fn, - // getParent: () => { - // return executionContext.meta.parent; - // }, - // getChildren: () => { - // return executionContext.meta.children || []; - // }, + onReadAtom: (atom: Atom) => addAtomToContext(atom, executionContext), atoms: new Set(), meta: { 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(); -// // const derrivedToAtom = new WeakMap(); -// export function derived(fn: Function) { -// const derivedAtom: DerivedAtom = { -// executionContexts: new Set(), -// // parent: null, -// dependencies: new Set(), -// dependents: new Set(), -// }; + const derivedAtom: DerivedAtom = { + executionContexts: new Set(), + dependents: new Set(), + dependencies: new Map(), + getValue: () => lastValue, + computed: false, + }; -// return () => { -// dependentStack.push([]); -// fn(); -// dependentStack.pop(); -// }; -// } + return () => { + const executionContext = getExecutionContext(); + executionContext?.onReadAtom(derivedAtom); + 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.