I’m making a animation engine, and it is being replaced and refactored with Canvaskit, I tried to use CanvasKit to load an animation, this is my code:
import { config } from "@newcar/utils";
import type { Canvas, CanvasKit, Paint, Surface } from "canvaskit-wasm";
import CanvasKitInit from "canvaskit-wasm";
import mitt from "mitt";
import type { Scene } from "./scene";
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
export type CarHookEventMap = {
"before-frame-update": Car;
"frame-updated": Car;
};
export class Car {
private _playing!: boolean;
private lastUpdateTime!: number;
// readonly context: CanvasRenderingContext2D;
readonly canvasKitLoaded = CanvasKitInit({
locateFile(_file) {
return config.canvaskitWasmFile;
},
});
canvaskit: CanvasKit;
paint: Paint;
surface: Surface;
events: {
"ready-to-play": (() => any)[];
} = {
"ready-to-play": [],
};
readonly hook = mitt<CarHookEventMap>();
constructor(public element: HTMLCanvasElement, public scene: Scene) {
this.playing = false;
this.element.style.backgroundColor = "black";
// this.context = this.element.getContext("2d")!;
this.canvasKitLoaded.then((canvaskit) => {
this.canvaskit = canvaskit;
this.surface = canvaskit.MakeWebGLCanvasSurface(this.element);
this.paint = new canvaskit.Paint();
for (const callback of this.events["ready-to-play"]) {
callback();
}
});
}
static update(car: Car): void {
car.hook.emit("before-frame-update", car);
let elapsed: number;
switch (config.timing) {
case "frame": {
elapsed = 1;
break;
}
case "second": {
const currentTimestamp = performance.now();
const intervalTime = (currentTimestamp - car.lastUpdateTime) / 1000;
car.lastUpdateTime = currentTimestamp;
elapsed = intervalTime;
break;
}
}
car.scene.elapsed += elapsed;
// car.context.clearRect(0, 0, car.element.width, car.element.height);
for (const update of car.scene.updates) {
update(car.scene.elapsed);
}
(function f(objects: typeof car.scene.objects) {
for (const object of objects) {
object.beforeUpdate(car);
for (const animation of object.animations) {
if (animation.elapsed <= animation.duration) {
animation.elapsed += elapsed;
animation.animate(
object,
animation.elapsed / animation.duration,
animation.by,
animation.params ?? {},
);
}
}
f(object.children);
object.updated(car);
}
})(car.scene.objects);
try {
car.surface.drawOnce((canvas: Canvas) => { // The problem is start from this column.
canvas.clear(car.canvaskit.BLACK);
for (const object of car.scene.objects) {
object.update(car.paint, canvas, car.canvaskit);
object.updated(car);
}
});
} catch (err) {}
car.hook.emit("frame-updated", car);
if (car.playing) {
requestAnimationFrame(() => Car.update(car));
}
}
play(at?: number): this {
this.playing = true;
if (typeof at !== "undefined") {
this.scene.elapsed = at;
}
return this;
}
stop(at?: number): this {
this.playing = false;
if (typeof at !== "undefined") {
this.scene.elapsed = at;
}
return this;
}
on(event: "ready-to-play", callback: () => any): this {
switch (event) {
case "ready-to-play": {
this.events[event].push(callback);
break;
}
}
return this;
}
get playing(): boolean {
return this._playing;
}
set playing(playing: boolean) {
this._playing = playing;
if (playing) {
this.lastUpdateTime = performance.now();
requestAnimationFrame(() => Car.update(this));
}
}
}
It erred: Uncaught RuntimeError: index out of bounds
and the browser tips is refering to canvaskit-wasm.js:3787
and the problem seems to be at 92th column, in car.surface.drawOnce
(I have signed it in codes, and you can find it in https://github.com/Bug-Duck/newcar/blob/093b14704951d2986ecfe5313f9ae3bec6874f88/packages/core/src/car.ts
I hope the animation could be ran directly.