Unwrapping a signal with lambda type #980
-
I'm writing a const amp = createSignal(1);
const freq = createSignal(20);
view.add(
<MathFunction
func={(x) => Math.sin(x / freq()) * 100 * amp()}
numPoints={1000}
stroke="red"
lineWidth={5}
/>,
); Complete scene exampleimport { Circle, Grid, Line, Node, makeScene2D, Spline, Layout } from '@motion-canvas/2d';
import {
Vector2, all, createRef, createSignal, waitUntil,
useDuration, waitFor, useScene, useLogger, DEFAULT, delay
} from '@motion-canvas/core';
import { MathFunction } from '../components/MathFunction';
export default makeScene2D(function* (view) {
const logger = useLogger();
const amp = createSignal(1);
const freq = createSignal(20);
const func = createRef<MathFunction>();
view.add(
<MathFunction
func={(x) => Math.sin(x / freq()) * 100 * amp()}
ref={func}
numPoints={1000}
lineWidth={6}
stroke="red"
opacity={0}
end={0.0}
/>,
);
amp(0);
yield* all(
func().opacity(1, 0.2),
func().end(1.0, 1.5),
delay(0.2, amp(2, 1.5))
);
yield* waitFor(0.2);
yield* freq(8, 2);
yield* waitFor(0.5);
yield* all(
amp(0, 1),
func().end(0.0, 1),
delay(0.8, func().opacity(0, 0.2))
)
yield* waitFor(0.2);
}); project.mp4My import { Spline, SplineProps, initial, signal } from '@motion-canvas/2d';
import { SignalValue, SimpleSignal, useLogger, unwrap} from '@motion-canvas/core';
export interface MathFunctionProps extends SplineProps {
func: SignalValue<(x: number) => number>
numPoints?: SignalValue<number>
}
export class MathFunction extends Spline {
@signal()
public declare readonly func: SimpleSignal<(x: number) => number, this>;
@initial(600)
@signal()
public declare readonly numPoints: SimpleSignal<number, this>;
public constructor(props: MathFunctionProps) {
super({
...props,
points: () => this.getFuncPoints(props.func, unwrap(props.numPoints)).map(
([x, y]) => [x - unwrap(props.numPoints) / 2, y]
),
});
}
private getFuncPoints(func: (x: number) => number, numPoints: number): number[][] {
useLogger().info(`getFuncPoints: ${func}, ${numPoints}`);
const points = [];
for (let i = 0; i < numPoints; i++) {
points.push([i, func(i)]);
}
return points;
}
} This works fine and my animation is rendered as I wanted. However, running
This was to be expected as I pass motion-canvas/packages/core/src/signals/utils.ts Lines 14 to 16 in 6c9bbfe what we end up with is the evaluated value of |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 1 reply
-
The value of a signal cannot be a function for the reasons you've just discovered. // define a custom type
export interface Sampler {
sample(x: number): number;
}
export interface MathFunctionProps extends SplineProps {
// use it in your definition
func: SignalValue<Sampler>;
numPoints?: SignalValue<number>;
}
// optionally define a helper function:
export function sampler(fn: (x: number) => number): Sampler {
return {
sample(x: number) {
return fn(x);
},
};
}
// use that in JSX:
view.add(
<MathFunction
// with the helper function:
func={sampler((x) => Math.sin(x / freq()) * 100 * amp())}
// as a raw object:
func={{sample: (x) => Math.sin(x / freq()) * 100 * amp()}}
// ...
/>,
); On a different note, you shouldn't access the values from the public constructor(props: MathFunctionProps) {
super({
...props,
points: () => this.getFuncPoints().map(([x, y]) => [x - this.numPoints() / 2, y]),
});
}
private getFuncPoints(): number[][] {
useLogger().info(`getFuncPoints: ${func}, ${numPoints}`);
const func = this.func().sample;
const numPoints = this.numPoints();
const points = [];
for (let i = 0; i < numPoints; i++) {
points.push([i, func(i)]);
}
return points;
} Otherwise, the Also, if you don't intend to change the export interface MathFunctionProps extends SplineProps {
// no SignalValue
func: (x: number) => number;
numPoints?: SignalValue<number>;
}
export class MathFunction extends Spline {
// no declare @signal
public readonly func: (x: number) => number;
@initial(600)
@signal()
public declare readonly numPoints: SimpleSignal<number, this>;
public constructor({func, ...props}: MathFunctionProps) {
super({
...props,
points: () => this.getFuncPoints().map(([x, y]) => [x - this.numPoints() / 2, y]),
});
this.func = func;
}
private getFuncPoints(): number[][] {
useLogger().info(`getFuncPoints: ${func}, ${numPoints}`);
// no need to unwrap it:
const func = this.func;
const numPoints = this.numPoints();
const points = [];
for (let i = 0; i < numPoints; i++) {
points.push([i, func(i)]);
}
return points;
}
} |
Beta Was this translation helpful? Give feedback.
The value of a signal cannot be a function for the reasons you've just discovered.
The easiest solution is to have a wrapper type that holds the function: