import { Component, Director, director, isValid } from 'cc'; declare type GeneratorFunc = (...args: any[]) => Generator declare type GeneratorRecord = { generator: Generator, gfunc?: GeneratorFunc, comp?: Component, waiter?: IWaiter }; interface IWaiter { isOver(dt: number): boolean; } class WaitForSecond implements IWaiter { private _endTime: number = 0; private _currTime: number = 0; constructor(endTime: number) { this._endTime = endTime; } isOver(dt: number) { this._currTime += dt; return this._currTime >= this._endTime; } } class WaitForFrame implements IWaiter { private _frameCount: number = 0; private _frame: number = 0; constructor(frameCount: number) { this._frameCount = frameCount; } isOver(dt: number) { return this._frame++ >= this._frameCount; } } export default class Coroutine { private static _instance: Coroutine = null; static get instance() { if (Coroutine._instance == null) { Coroutine._instance = new Coroutine(); } return Coroutine._instance; } /**等待多少秒 */ static createWaitForSecond(sec: number) { return new WaitForSecond(sec); } /**等待多少帧 */ static createWaitForFrame(frameCount: number) { return new WaitForFrame(frameCount); } private _mapCoroutine: Map = new Map(); private _getTimmer: () => number = null; private _lastTime: number = 0; private constructor() { if (typeof window?.performance?.now !== 'function') { this._getTimmer = () => { return new Date().getTime(); } } else { this._lastTime = window.performance.now(); this._getTimmer = () => window.performance.now(); } } /**@summary start coroutiine * @param component cc component * @param func generator func or string * @param args your funcion params */ start(component: Component, func: GeneratorFunc | string, ...args: any[]) { if (!(component instanceof Component)) { throw Error('component must a component'); } const mapData: GeneratorRecord = { generator: null, comp: component }; if (typeof func === 'string') { const gf: GeneratorFunc = (component)[func]; const generator = gf.call(component, ...args); if (typeof generator?.next === 'function') { mapData.generator = generator; mapData.gfunc = (component)[func]; } else { throw Error(func + ' is not a GeneratorFunction !!!'); } } else { mapData.gfunc = func; mapData.generator = func.call(component, ...args); } const _processCount = this._mapCoroutine.size; if (mapData.generator) { const list = this._mapCoroutine.get(component); if (!list) { this._mapCoroutine.set(component, [mapData]); } else { list.push(mapData); } } if (_processCount == 0 && this._mapCoroutine.size > 0) { this._lastTime = this._getTimmer(); director.on(Director.EVENT_AFTER_UPDATE, this._update, this); } } /**@summary stop coroutiine * @param component cc component * @param func if no input, that will stop all coroutine of component; */ stop(component: Component, func?: GeneratorFunc | string) { if (!func) { this._mapCoroutine.delete(component); } else { const list = this._mapCoroutine.get(component); if (!list) return; let deleteIds: number[] = []; for (let i = 0; i < list.length; ++i) { if (!list[i]) { list[i] = null; deleteIds.push(i); } else if (typeof func === 'string' && list[i].gfunc === (component)[func]) { list[i] = null; deleteIds.push(i); } else if (list[i].gfunc == func) { list[i] = null; deleteIds.push(i); } } if (deleteIds.length >= list.length) { this._mapCoroutine.delete(component); return; } else if (deleteIds.length >= 2) { this._mapCoroutine.set(component, list.filter((e) => !!e)); } else if (deleteIds.length == 1) { list.splice(deleteIds[0], 1); } } } private _update() { const dt = (this._getTimmer() - this._lastTime) / 1000; this._mapCoroutine.forEach((gr, k) => { let deleteIds: number[] = []; let count = gr.length; for (let i = 0; i < count; ++i) { if (!(gr && isValid(gr[i]?.comp?.node, true))) { deleteIds.push(i); gr[i] = null; } else { if (gr[i].waiter) { if (!gr[i].waiter.isOver(dt)) { continue; } } const it = gr[i].generator.next(); if (it.value instanceof WaitForSecond) { if (!it.value.isOver(dt)) { gr[i].waiter = it.value; continue; } } else if (it.value instanceof WaitForFrame) { gr[i].waiter = it.value; if (!it.value.isOver(dt)) { gr[i].waiter = it.value; continue; } } if (it.done) { deleteIds.push(i); gr[i] = null; } } } if (deleteIds.length >= gr.length) { this._mapCoroutine.delete(k); return; } else if (deleteIds.length >= 2) { this._mapCoroutine.set(k, gr.filter((e) => !!e)); } else if (deleteIds.length == 1) { gr.splice(deleteIds[0], 1); } }); this._lastTime = this._getTimmer(); this._mapCoroutine.size == 0 && director.off(Director.EVENT_AFTER_UPDATE, this._update, this); } }