import { HZEngineCore, Storage, UI } from "../../index.js"; import { Save, CustomSave, } from "../../storage/decorator.js"; import { Animation, Profile, Wrapper } from "./animation.js"; // import { Time } from "@zos/sensor"; export class AnimationPlugin { constructor(public _core: HZEngineCore) { console.log("[AnimationPlugin] init"); _core.loadPlugin("animation", () => this) _core.on("anime.cb", this._timerCb.bind(this)); _core.async.addRepeatTask("anime.cb", [], 0); // 此處周期應考慮加個sync update this._lastCbUtc = this._core.platform.getTime() } @Save("anime.nid") private accessor _nextAnimationId: number = 1; // self-increment @CustomSave( "anime.map", function (obj): Record { let res: Record = {}; for (let id in obj) { if (!obj[id].isSave) continue; res[id] = { destroyOnEnd: obj[id].destroyOnEnd, isSave: obj[id].isSave, targetViewId: obj[id].targetViewId, serializedInstance: obj[id].instance.serialize(), }; } return res; }, function ( obj: Record ): Record { let res: Record = {}; for (let id in obj) { if (!obj[id].isSave) continue; let animation = Animation.unserialize(obj[id].serializedInstance); this._linkAnimationCb(animation, Number(id)); res[id] = { destroyOnEnd: obj[id].destroyOnEnd, isSave: obj[id].isSave, targetViewId: obj[id].targetViewId, instance: animation, }; } return res; } ) private accessor _animationMap: Record< string, AnimationPlugin.AnimationItem > = {}; // private _timeSensor = new Time(); private _lastCbUtc: number; public applyAnimation

({ profile, targetView, options, }: { profile: Profile

; targetView: UI.View>; options?: AnimationPlugin.Options; }): number { const id = this._nextAnimationId++; let animation = new Animation(profile, { // @ts-ignore initProps: targetView.prop ?? {}, wrappers: options?.customWrappers, }); if (!targetView.id) throw "targetView.id must be not null"; this._animationMap[id] = { destroyOnEnd: options?.destroyOnEnd ?? false, isSave: options?.isSave ?? true, instance: animation, targetViewId: targetView.id!, }; this._linkAnimationCb(animation, id); animation.goto(0); this._core.on("afterLoadArchive", () => { this._lastCbUtc = this._core.platform.getTime(); }); return id; } public stopAnimation(id: number) { if (this._animationMap[id] !== undefined) { this._animationMap[id].instance.stop(); delete this._animationMap[id]; } } private _nextTempAnimationId: number = -1; // self-decrement _tempAnimationMap: Record = {}; public createTempAnimation

({ profile, onFrame, onEnd, initProps, wrappers, }: { profile: Profile

; onFrame?: (props: Partial

) => void; onEnd?: () => void; initProps?: Partial

; wrappers?: Record; }): number { let id = this._nextTempAnimationId--; let animation = new Animation(profile, { initProps, wrappers, }); animation.onFrame = onFrame ?? null; animation.onEnd = () => { if (onEnd) onEnd(); delete this._tempAnimationMap[id]; }; this._tempAnimationMap[id] = animation return id; } public clearTempAnimation(id: number) { if (this._tempAnimationMap[id] !== undefined) { this._tempAnimationMap[id].stop(); delete this._tempAnimationMap[id]; } } private _timerCb() { let utc = this._core.platform.getTime(); let delta_time = (utc - this._lastCbUtc) / 1000; this._lastCbUtc = utc; // this._core.debug.log(`delta time: ${delta_time}`); for (let id in this._animationMap) { this._animationMap[id].instance.step(delta_time); } for (let id in this._tempAnimationMap) { this._tempAnimationMap[id].step(delta_time); } } private _frameCb(props: Record, id: number) { if (this._animationMap[id] !== undefined) { let view = this._core.ui.getView(this._animationMap[id].targetViewId); if (!view) { this._core.debug.log(`view ${id} not found, stop animation`); this.stopAnimation(id); // TODO return; } view.commit(props); // this._core.debug.log(`update view ${view.name} with ${JSON.stringify(props)}`); } } private _linkAnimationCb(animation: Animation, id: number) { animation.onFrame = (props) => { // console.log("onFrame cb"); this._frameCb(props, id); }; animation.onEnd = () => this._endCb(id); } private _endCb(id: number) { if (this._animationMap[id] !== undefined) { if (this._animationMap[id].destroyOnEnd) { let view = this._core.ui.getView(id); if (view) this._core.ui.destroyView(view); } delete this._animationMap[id]; console.log("map item", this._animationMap[id]); } else console.log("nf map item", this._animationMap[id]); } } namespace AnimationPlugin { export interface Options { destroyOnEnd?: boolean; isSave?: boolean; customWrappers?: Record; } export interface AnimationItem { destroyOnEnd: boolean; isSave: boolean; targetViewId: number; instance: Animation; } export namespace AnimationItem { export interface Serialized { destroyOnEnd: boolean; isSave: boolean; targetViewId: number; serializedInstance: Animation.Serialized; } } }