Coroutine.ts 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. import { Component, Director, director, isValid } from 'cc';
  2. declare type GeneratorFunc = (...args: any[]) => Generator<any, any, any>
  3. declare type GeneratorRecord = { generator: Generator<any, any, any>, gfunc?: GeneratorFunc, comp?: Component, waiter?: IWaiter };
  4. interface IWaiter {
  5. isOver(dt: number): boolean;
  6. }
  7. class WaitForSecond implements IWaiter {
  8. private _endTime: number = 0;
  9. private _currTime: number = 0;
  10. constructor(endTime: number) {
  11. this._endTime = endTime;
  12. }
  13. isOver(dt: number) {
  14. this._currTime += dt;
  15. return this._currTime >= this._endTime;
  16. }
  17. }
  18. class WaitForFrame implements IWaiter {
  19. private _frameCount: number = 0;
  20. private _frame: number = 0;
  21. constructor(frameCount: number) {
  22. this._frameCount = frameCount;
  23. }
  24. isOver(dt: number) {
  25. return this._frame++ >= this._frameCount;
  26. }
  27. }
  28. export default class Coroutine {
  29. private static _instance: Coroutine = null;
  30. static get instance() {
  31. if (Coroutine._instance == null) {
  32. Coroutine._instance = new Coroutine();
  33. }
  34. return Coroutine._instance;
  35. }
  36. /**等待多少秒 */
  37. static createWaitForSecond(sec: number) {
  38. return new WaitForSecond(sec);
  39. }
  40. /**等待多少帧 */
  41. static createWaitForFrame(frameCount: number) {
  42. return new WaitForFrame(frameCount);
  43. }
  44. private _mapCoroutine: Map<any, GeneratorRecord[]> = new Map();
  45. private _getTimmer: () => number = null;
  46. private _lastTime: number = 0;
  47. private constructor() {
  48. if (typeof window?.performance?.now !== 'function') {
  49. this._getTimmer = () => {
  50. return new Date().getTime();
  51. }
  52. } else {
  53. this._lastTime = window.performance.now();
  54. this._getTimmer = () => window.performance.now();
  55. }
  56. }
  57. /**@summary start coroutiine
  58. * @param component cc component
  59. * @param func generator func or string
  60. * @param args your funcion params
  61. */
  62. start(component: Component, func: GeneratorFunc | string, ...args: any[]) {
  63. if (!(component instanceof Component)) {
  64. throw Error('component must a component');
  65. }
  66. const mapData: GeneratorRecord = {
  67. generator: null,
  68. comp: component
  69. };
  70. if (typeof func === 'string') {
  71. const gf: GeneratorFunc = (<any>component)[func];
  72. const generator = gf.call(component, ...args);
  73. if (typeof generator?.next === 'function') {
  74. mapData.generator = generator;
  75. mapData.gfunc = (<any>component)[func];
  76. } else {
  77. throw Error(func + ' is not a GeneratorFunction !!!');
  78. }
  79. } else {
  80. mapData.gfunc = func;
  81. mapData.generator = func.call(component, ...args);
  82. }
  83. const _processCount = this._mapCoroutine.size;
  84. if (mapData.generator) {
  85. const list = this._mapCoroutine.get(component);
  86. if (!list) {
  87. this._mapCoroutine.set(component, [mapData]);
  88. } else {
  89. list.push(mapData);
  90. }
  91. }
  92. if (_processCount == 0 && this._mapCoroutine.size > 0) {
  93. this._lastTime = this._getTimmer();
  94. director.on(Director.EVENT_AFTER_UPDATE, this._update, this);
  95. }
  96. }
  97. /**@summary stop coroutiine
  98. * @param component cc component
  99. * @param func if no input, that will stop all coroutine of component;
  100. */
  101. stop(component: Component, func?: GeneratorFunc | string) {
  102. if (!func) {
  103. this._mapCoroutine.delete(component);
  104. } else {
  105. const list = this._mapCoroutine.get(component);
  106. if (!list) return;
  107. let deleteIds: number[] = [];
  108. for (let i = 0; i < list.length; ++i) {
  109. if (!list[i]) {
  110. list[i] = null;
  111. deleteIds.push(i);
  112. }
  113. else if (typeof func === 'string' && list[i].gfunc === (<any>component)[func]) {
  114. list[i] = null;
  115. deleteIds.push(i);
  116. } else if (list[i].gfunc == func) {
  117. list[i] = null;
  118. deleteIds.push(i);
  119. }
  120. }
  121. if (deleteIds.length >= list.length) {
  122. this._mapCoroutine.delete(component);
  123. return;
  124. } else if (deleteIds.length >= 2) {
  125. this._mapCoroutine.set(component, list.filter((e) => !!e));
  126. } else if (deleteIds.length == 1) {
  127. list.splice(deleteIds[0], 1);
  128. }
  129. }
  130. }
  131. private _update() {
  132. const dt = (this._getTimmer() - this._lastTime) / 1000;
  133. this._mapCoroutine.forEach((gr, k) => {
  134. let deleteIds: number[] = [];
  135. let count = gr.length;
  136. for (let i = 0; i < count; ++i) {
  137. if (!(gr && isValid(gr[i]?.comp?.node, true))) {
  138. deleteIds.push(i);
  139. gr[i] = null;
  140. } else {
  141. if (gr[i].waiter) {
  142. if (!gr[i].waiter.isOver(dt)) {
  143. continue;
  144. }
  145. }
  146. const it = gr[i].generator.next();
  147. if (it.value instanceof WaitForSecond) {
  148. if (!it.value.isOver(dt)) {
  149. gr[i].waiter = it.value;
  150. continue;
  151. }
  152. } else if (it.value instanceof WaitForFrame) {
  153. gr[i].waiter = it.value;
  154. if (!it.value.isOver(dt)) {
  155. gr[i].waiter = it.value;
  156. continue;
  157. }
  158. }
  159. if (it.done) {
  160. deleteIds.push(i);
  161. gr[i] = null;
  162. }
  163. }
  164. }
  165. if (deleteIds.length >= gr.length) {
  166. this._mapCoroutine.delete(k);
  167. return;
  168. } else if (deleteIds.length >= 2) {
  169. this._mapCoroutine.set(k, gr.filter((e) => !!e));
  170. } else if (deleteIds.length == 1) {
  171. gr.splice(deleteIds[0], 1);
  172. }
  173. });
  174. this._lastTime = this._getTimmer();
  175. this._mapCoroutine.size == 0 && director.off(Director.EVENT_AFTER_UPDATE, this._update, this);
  176. }
  177. }