import { logger } from '@motajs/common'; import { ExcitationCurve, IAnimatable, IAnimater, IAnimationPlan, IExcitableController, IExcitation, IAnimatePlanIdentifier, EndRelation } from './types'; import { linear } from './utils'; interface IAnimaterTimedInfo extends IAnimatePlanIdentifier { /** 等待时长 */ readonly time: number; } interface IAnimaterResolvedTimedInfo extends IAnimaterTimedInfo { /** 当前定时动画计划被唤起的时刻 */ readonly arousedTime: number; } interface IAnimaterRawPlan extends IAnimationPlan { /** 当此动画开始时需要执行的计划 */ readonly when: Set; /** 当此动画结束后需要执行的计划 */ readonly after: Set; /** 兑现函数,当本次动画计划执行完毕后执行 */ readonly resolve: () => void; /** 终值参考模式 */ readonly end: EndRelation; } interface IAnimatingContent extends IAnimaterRawPlan { /** 动画执行开始的时刻 */ readonly startTime: number; /** 动画执行的初始值 */ readonly startValue: number; /** 动画终值与初值的差值 */ readonly diff: number; } interface IAnimaterContentPlan { /** 当前动画对象中所有的动画计划 */ readonly animationPlan: Map; /** 计数器,用于计算动画计划的索引 */ counter: number; } interface IAnimaterPlanGroupBase { /** 计划执行前的等待时间 */ readonly preTime: number; /** 计划执行后的等待时间 */ readonly postTime: number; /** 计划组的首个动画计划 */ readonly planStart: Set; } interface IAnimaterPlanGroup extends IAnimaterPlanGroupBase { /** 当前计划组中所有的动画对象计划 */ readonly contentStore: Map; } export class Animater implements IAnimater { /** 当前是否正在定义动画计划 */ private planning: boolean = false; /** 当前绑定的激励源 */ private excitation: IExcitation | null = null; /** 当前定义在绑定激励源上的可激励对象 */ private controller: IExcitableController | null = null; /** 计划组计数器 */ private groupCounter: number = 0; /** 当前正在计划的动画计划 */ private planningStore: Map = new Map(); /** 计划存储 */ private groupStore: Map = new Map(); /** 需要执行的计划队列 */ private pendingGroups: number[] = []; /** 当前所有正在等待执行的 `when` 操作 */ private whens: Set = new Set(); /** 当前所有正在等待执行的 `after` 操作 */ private afters: Set = new Set(); /** 当前正在执行的计划组 */ private executingGroup: number = -1; /** 当前正在执行的计划组对象 */ private executingGroupObj: IAnimaterPlanGroup | null = null; /** 当前正在执行的动画 */ private executing: Set = new Set(); /** 当前动画对象正在被哪个动画执行 */ private executingMap: Map = new Map(); /** 当前使用的速率曲线 */ private curveStatus: ExcitationCurve = linear(); /** 当前使用的动画对象计划 */ private animatableStatus: IAnimaterContentPlan | null = null; /** 当前使用的动画对象 */ private currentAnimatable: IAnimatable | null = null; /** 当前正在计划的计划组的起始动画 */ private planningStart: Set = new Set(); /** 当前的 `when` 计划内容 */ private whenBind: IAnimaterRawPlan | null = null; /** 当前的 `after` 计划内容 */ private afterBind: IAnimaterRawPlan | null = null; /** 当前的 `when` 等待时长 */ private whenTime: number = 0; /** 当前的 `after` 等待时长 */ private afterTime: number = 0; /** 计划组是否正在进行执行前等待 */ private waitingPre = false; /** 计划组执行前等待的时间 */ private waitingPreStart = 0; /** 计划组是否正在进行执行后等待 */ private waitingPost = false; /** 计划组执行后等待的时间 */ private waitingPostStart = 0; /** 上一个定义的动画计划 */ private lastAnimatable: IAnimaterRawPlan | null = null; constructor() { this.excited = this.excited.bind(this); } bindExcitation(excitation: IExcitation): void { if (excitation === this.excitation) return; this.unbindExcitation(); this.controller = excitation.add(this); this.excitation = excitation; } unbindExcitation(): void { if (!this.excitation) return; this.controller?.revoke(); this.excitation = null; } //#region 动画计划 animate(content: IAnimatable): this { if (!this.planningStore.has(content)) { const plan: IAnimaterContentPlan = { animationPlan: new Map(), counter: 0 }; this.animatableStatus = plan; this.planningStore.set(content, plan); } else { const plan = this.planningStore.get(content)!; this.animatableStatus = plan; } this.currentAnimatable = content; return this; } curve(curve: ExcitationCurve): this { this.curveStatus = curve; return this; } to(value: number, time: number, end: EndRelation = EndRelation.Self): this { if (!this.animatableStatus || !this.currentAnimatable) return this; this.planning = true; // 定义动画计划 const index = ++this.animatableStatus.counter; const { promise, resolve } = Promise.withResolvers(); const plan: IAnimaterRawPlan = { identifier: { content: this.currentAnimatable, index }, curve: this.curveStatus, targetValue: value, end, time, promise, resolve, when: new Set(), after: new Set() }; this.animatableStatus.animationPlan.set(index, plan); // 检查 when after 状态,如果是在这个状态下,加入到对应的计划中 if (this.whenBind) { const identifier: IAnimaterTimedInfo = { content: this.currentAnimatable, index, time: this.whenTime }; this.whenBind.when.add(identifier); } else if (this.afterBind) { const identifier: IAnimaterTimedInfo = { content: this.currentAnimatable, index, time: this.afterTime }; this.afterBind.after.add(identifier); } else { const identifier: IAnimatePlanIdentifier = { content: this.currentAnimatable, index }; this.planningStart.add(identifier); } // 将上一次的计划设为本计划,用于 after when 的调用 this.lastAnimatable = plan; return this; } after(time: number = 0): this { if (!this.lastAnimatable) return this; this.afterBind = this.lastAnimatable; this.afterTime = time; this.whenBind = null; return this; } afterPlan(content: IAnimatable, index: number, time: number = 0): this { const plan = this.queryRaw(content, index); if (!plan) return this; this.afterBind = plan; this.afterTime = time; this.whenBind = null; return this; } when(time: number = 0): this { if (!this.lastAnimatable) return this; this.whenBind = this.lastAnimatable; this.whenTime = time; this.afterBind = null; return this; } whenPlan(content: IAnimatable, index: number, time: number = 0): this { const plan = this.queryRaw(content, index); if (!plan) return this; this.whenBind = plan; this.whenTime = time; this.afterBind = null; return this; } /** * 类内查询动画计划,可以获取到外界获取不到的信息 * @param content 动画对象 * @param index 动画计划索引 * @param plan 计划组索引,不填时表示当前正在计划中的计划组,即从上一次调用 `planEnd` 至现在的这个区间。 */ private queryRaw( content: IAnimatable, index: number, plan: number = -1 ): IAnimaterRawPlan | null { if (plan === -1) { const animation = this.planningStore.get(content); if (!animation) return null; const result = animation.animationPlan.get(index); return result ?? null; } else { const group = this.groupStore.get(plan); if (!group) return null; const animation = group.contentStore.get(content); if (!animation) return null; const result = animation.animationPlan.get(index); return result ?? null; } } query( content: IAnimatable, index: number, plan: number = -1 ): IAnimationPlan | null { return this.queryRaw(content, index, plan); } wait( content: IAnimatable, index: number, plan: number = this.executingGroup ): Promise | undefined { const raw = this.query(content, index, plan); return raw?.promise; } planEnd(preTime: number = 0, postTime: number = 0): number { if (!this.planning) return -1; const group: IAnimaterPlanGroup = { contentStore: this.planningStore, planStart: this.planningStart, preTime, postTime }; this.whenBind = null; this.afterBind = null; this.currentAnimatable = null; this.animatableStatus = null; this.lastAnimatable = null; this.planningStart = new Set(); this.planningStore = new Map(); this.planning = false; const index = ++this.groupCounter; this.groupStore.set(index, group); this.pendingGroups.push(index); this.startPlanGroup(); return index; } //#endregion //#region 动画执行 excited(payload: number): void { if (this.planning) { logger.error(50); return; } // 计划组未执行 if (this.executingGroup === -1 || !this.executingGroupObj) return; // 计划组 preTime 等待 if (this.waitingPre) { const dt = payload - this.waitingPreStart; if (dt < this.executingGroupObj.preTime) return; this.waitingPre = false; // 启动所有 planStart 动画 for (const identifier of this.executingGroupObj.planStart) { this.executeAnimate(identifier); } } // 计划组 postTime 等待 if (this.waitingPost) { const dt = payload - this.waitingPostStart; if (dt >= this.executingGroupObj.postTime) { this.waitingPost = false; this.endPlanGroup(); } return; } // 处理 when/after 等待 const whenToDelete = new Set(); for (const w of this.whens) { if (payload - w.arousedTime >= w.time) { whenToDelete.add(w); this.executeAnimate(w); } } whenToDelete.forEach(v => this.whens.delete(v)); const afterToDelete = new Set(); for (const a of this.afters) { if (payload - a.arousedTime >= a.time) { afterToDelete.add(a); this.executeAnimate(a); } } afterToDelete.forEach(v => this.afters.delete(v)); // 动画执行 const endedAnimate = new Set(); const afters = new Set(); for (const anim of this.executing) { const progress = (payload - anim.startTime) / anim.time; const content = anim.identifier.content; if (progress >= 1) { // 动画结束 if (anim.end === EndRelation.Self) { content.value = anim.targetValue; } else { content.value = anim.startValue + anim.curve(1) * anim.diff; } anim.resolve(); endedAnimate.add(anim); anim.after.forEach(v => afters.add(v)); } else { const completion = anim.curve(progress); content.value = completion * anim.diff + anim.startValue; } } // 检查 after afters.forEach(v => { this.startAfter({ ...v, arousedTime: payload }); }); // 必要清理 endedAnimate.forEach(v => { this.executing.delete(v); this.executingMap.delete(v.identifier.content); }); // 检查计划组是否全部结束 if ( this.executing.size === 0 && this.whens.size === 0 && this.afters.size === 0 ) { this.waitingPost = true; this.waitingPostStart = payload; } } /** * 开始执行指定的动画 * @param identifier 要执行的动画标识符 */ private executeAnimate(identifier: IAnimatePlanIdentifier) { if (!this.executingGroupObj || !this.excitation) return; const plan = this.queryRaw( identifier.content, identifier.index, this.executingGroup ); if (!plan) return; // 冲突检测 if (this.executingMap.has(identifier.content)) { const current = this.executingMap.get(identifier.content)!; if (current.startTime === this.excitation?.payload()) { logger.error(51); return; } // 终止前一个动画 current.resolve(); this.executing.delete(current); } // 记录动画初始值和开始时间 const startValue = identifier.content.value; const startTime = this.excitation.payload(); const anim: IAnimatingContent = { ...plan, startTime, startValue, diff: plan.targetValue - startValue }; this.executing.add(anim); this.executingMap.set(identifier.content, anim); // 检查 when for (const when of plan.when) { this.startWhen({ ...when, arousedTime: startTime }); } } /** * 开始 `when` 状态计时 * @param when `when` 状态 */ private startWhen(when: IAnimaterResolvedTimedInfo) { if (when.time === 0) { this.executeAnimate(when); } else { this.whens.add(when); } } /** * 开始 `after` 状态计时 * @param after `after` 状态 */ private startAfter(after: IAnimaterResolvedTimedInfo) { if (after.time === 0) { this.executeAnimate(after); } else { this.afters.add(after); } } /** * 开始指定计划组动画的执行 */ private startPlanGroup() { if (this.executingGroup !== -1) return; if (this.pendingGroups.length === 0) return; if (!this.excitation) return; const group = this.pendingGroups.shift(); if (group === void 0) return; const obj = this.groupStore.get(group); if (!obj) return; this.executingGroup = group; this.executingGroupObj = obj; // preTime 等待 if (obj.preTime > 0) { this.waitingPre = true; this.waitingPreStart = this.excitation.payload(); } else { // 立即启动所有 planStart 动画 for (const identifier of this.executingGroupObj.planStart) { this.executeAnimate(identifier); } } } /** * 结束计划组的执行 */ private endPlanGroup() { // 清理状态 this.executingGroup = -1; this.executingGroupObj = null; this.executing.clear(); this.executingMap.clear(); this.whens.clear(); this.afters.clear(); this.waitingPre = false; this.waitingPost = false; // 启动下一个计划组 this.startPlanGroup(); } //#endregion }