feat: signal error handling (#89)

- Signal calculations are wrapped in try/catch blocks.
- Signals keep track of their owner for better logging.
This commit is contained in:
Jacob
2022-12-03 10:31:54 +01:00
committed by GitHub
parent 3d82e863af
commit 472ac65938
4 changed files with 31 additions and 4 deletions

View File

@@ -12,7 +12,7 @@ export function computed(): MethodDecorator {
return (target: any, key) => {
addInitializer(target, (instance: any) => {
const method = Object.getPrototypeOf(instance)[key];
instance[key] = createComputed(method.bind(instance));
instance[key] = createComputed(method.bind(instance), instance);
});
};
}

View File

@@ -209,6 +209,7 @@ export abstract class GeneratorScene<T>
'Tried to access an asynchronous property before the node was ready. ' +
'Make sure to yield the node before accessing the property.',
stack: promises[0].stack,
inspect: promises[0].owner?.key ?? undefined,
});
}

View File

@@ -5,17 +5,20 @@ import {
finishCollecting,
startCollecting,
} from './createSignal';
import {useLogger} from './useProject';
export type Computed<TValue> = (...args: any[]) => TValue;
export function createComputed<TValue>(
factory: Computed<TValue>,
owner?: any,
): Computed<TValue> {
let last: TValue;
const event = new FlagDispatcher();
const context: DependencyContext = {
dependencies: new Set<Subscribable<void>>(),
handler: () => event.raise(),
owner,
};
const handler = <Computed<TValue>>function handler(...args: any[]) {
@@ -24,7 +27,15 @@ export function createComputed<TValue>(
context.dependencies.clear();
context.stack = new Error().stack;
startCollecting(context);
last = factory(...args);
try {
last = factory(...args);
} catch (e) {
useLogger().error({
message: e.message,
stack: e.stack,
inspect: context.owner?.key,
});
}
finishCollecting(context);
}
event.reset();

View File

@@ -7,11 +7,13 @@ import {
InterpolationFunction,
} from '../tweening';
import {ThreadGenerator} from '../threading';
import {useLogger} from './useProject';
export interface DependencyContext {
dependencies: Set<Subscribable<void>>;
handler: EventHandler<void>;
stack?: string;
owner?: any;
}
export type SignalValue<TValue> = TValue | (() => TValue);
@@ -121,6 +123,7 @@ export interface PromiseHandle<T> {
promise: Promise<T>;
value: T;
stack?: string;
owner?: any;
}
export function collectPromise<T>(
@@ -130,10 +133,13 @@ export function collectPromise<T>(
const handle: PromiseHandle<T> = {
promise,
value: initialValue,
stack: collectionStack[0].stack,
stack: collectionStack[0]?.stack,
};
const context = collectionStack.at(-2);
if (context) {
handle.owner = context.owner;
}
promise.then(value => {
handle.value = value;
context?.handler();
@@ -164,6 +170,7 @@ export function createSignal<TValue, TReturn = void>(
const context: DependencyContext = {
dependencies: new Set<Subscribable<void>>(),
handler: () => event.raise(),
owner: setterReturn,
};
function set(value: SignalValue<TValue>) {
@@ -192,7 +199,15 @@ export function createSignal<TValue, TReturn = void>(
context.dependencies.clear();
context.stack = new Error().stack;
startCollecting(context);
last = current();
try {
last = current();
} catch (e) {
useLogger().error({
message: e.message,
stack: e.stack,
inspect: context.owner?.key,
});
}
finishCollecting(context);
}
event.reset();