@ives_xxz/framework 1.4.15 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,4 @@
1
+ import { FWSystemConfig } from '../config/FWSystemConfig';
1
2
  import FWLog from '../log/FWLog';
2
3
  import { FWManager } from './FWManager';
3
4
 
@@ -41,19 +42,20 @@ export class FWAssetManager extends FWManager implements FW.AssetManager {
41
42
  bin?: boolean;
42
43
  }): Promise<sp.SkeletonData> {
43
44
  try {
44
- return new Promise((resolve) => {
45
- const bin = data.bin === undefined ? false : data.bin;
46
- const img = data.img;
47
- const ske = data.ske;
48
- const atlas = data.atlas;
49
- const type = bin ? '.bin' : '.txt';
50
- cc.assetManager.loadAny(
51
- [
52
- { url: atlas, ext: type },
53
- { url: ske, ext: type },
54
- ],
55
- (error, assets) => {
56
- cc.assetManager.loadRemote(img, (error, texture: cc.Texture2D) => {
45
+ return await this.invoke(
46
+ FW.Entry.promiseMgr.execute((resolve, reject, signal) => {
47
+ const bin = data.bin === undefined ? false : data.bin;
48
+ const img = data.img;
49
+ const ske = data.ske;
50
+ const atlas = data.atlas;
51
+ const type = bin ? '.bin' : '.txt';
52
+ cc.assetManager.loadAny(
53
+ [
54
+ { url: atlas, ext: type },
55
+ { url: ske, ext: type },
56
+ ],
57
+ async (error, assets) => {
58
+ const texture: cc.Texture2D = await this.loadRemote();
57
59
  var asset = new sp.SkeletonData();
58
60
  asset.skeletonJson = assets[1];
59
61
  asset.atlasText = assets[0];
@@ -61,29 +63,32 @@ export class FWAssetManager extends FWManager implements FW.AssetManager {
61
63
  asset['textureNames'] = [`${img}.png`];
62
64
  asset['_uuid'] = ske;
63
65
  resolve(asset);
64
- });
65
- },
66
- );
67
- });
66
+ },
67
+ );
68
+ }, FWSystemConfig.PromiseConfig.loadAsset).promise,
69
+ );
68
70
  } catch (e) {
69
71
  FWLog.error('从远程加载spine动画资源失败!');
70
72
  }
71
73
  }
72
74
 
73
75
  /** 加载远程资源 */
74
- loadRemote<T extends cc.Asset = cc.Asset>(
76
+ async loadRemote<T extends cc.Asset = cc.Asset>(
75
77
  url?: string,
76
78
  cb?: (asset: cc.Asset) => void,
77
79
  ): Promise<T> {
78
- return new Promise((resolve, reject) => {
79
- cc.assetManager.loadRemote(url, { cacheEnabled: true, maxRetryCount: 0 }, (err, asset) => {
80
- if (err || !asset) {
81
- reject(err);
82
- }
83
- cb?.(asset as T);
84
- resolve(asset as T);
85
- });
86
- });
80
+ return await this.invoke(
81
+ FW.Entry.promiseMgr.execute((resolve, reject, signal) => {
82
+ cc.assetManager.loadRemote(url, { cacheEnabled: true, maxRetryCount: 3 }, (err, asset) => {
83
+ if (err || !asset) {
84
+ reject(err);
85
+ }
86
+ cb?.(asset as T);
87
+ resolve(asset as T);
88
+ });
89
+ }, FWSystemConfig.PromiseConfig.loadAsset).promise,
90
+ `loadAssets -> ${url}`,
91
+ );
87
92
  }
88
93
 
89
94
  /**
@@ -132,43 +137,36 @@ export class FWAssetManager extends FWManager implements FW.AssetManager {
132
137
  assetData.loaded = false;
133
138
 
134
139
  return await this.invoke(
135
- FW.Entry.promiseMgr.execute(
136
- (resolve, reject, signal) => {
137
- const self = this;
138
-
139
- bundle.load(
140
- path,
141
- type,
142
- (finish: number, total: number, item: cc.AssetManager.RequestItem) => {
143
- progress?.(finish, total, item);
144
- },
145
- (err: Error, asset: cc.Asset) => {
146
- if (err || !asset) {
147
- reject(err);
148
- return;
149
- }
150
- assetData.loaded = true;
151
- assetData.dependentBundle = bundleName;
152
- assetData.uuid = asset['_uuid'];
153
- assetData.autoRelease = autoRelease;
154
- assetData.asset = asset;
155
- assetData.user = assetProperty.user;
156
- assetData.refCount = 1;
157
- assetData.assetProperty = assetProperty;
158
-
159
- self.assetsMap.set(key, assetData);
160
-
161
- cb?.(assetData);
162
- resolve(assetData);
163
- },
164
- );
165
- },
166
- {
167
- retryCount: 3,
168
- retryInterval: 1,
169
- timeout: 10,
170
- },
171
- ).promise,
140
+ FW.Entry.promiseMgr.execute((resolve, reject, signal) => {
141
+ const self = this;
142
+
143
+ bundle.load(
144
+ path,
145
+ type,
146
+ (finish: number, total: number, item: cc.AssetManager.RequestItem) => {
147
+ progress?.(finish, total, item);
148
+ },
149
+ (err: Error, asset: cc.Asset) => {
150
+ if (err || !asset) {
151
+ reject(err);
152
+ return;
153
+ }
154
+ assetData.loaded = true;
155
+ assetData.dependentBundle = bundleName;
156
+ assetData.uuid = asset['_uuid'];
157
+ assetData.autoRelease = autoRelease;
158
+ assetData.asset = asset;
159
+ assetData.user = assetProperty.user;
160
+ assetData.refCount = 1;
161
+ assetData.assetProperty = assetProperty;
162
+
163
+ self.assetsMap.set(key, assetData);
164
+
165
+ cb?.(assetData);
166
+ resolve(assetData);
167
+ },
168
+ );
169
+ }, FWSystemConfig.PromiseConfig.loadAsset).promise,
172
170
  `loadAssets -> ${assetProperty.path}`,
173
171
  );
174
172
  }
@@ -322,9 +320,6 @@ export class FWAssetManager extends FWManager implements FW.AssetManager {
322
320
  * @returns
323
321
  */
324
322
  public getReferenceCount(assetProperty: FW.AssetProperty): number {
325
- const bundleName = assetProperty.bundle || FW.Entry.bundleName;
326
- const path = assetProperty.path;
327
- const type = assetProperty.type;
328
323
  const key = this.createAssetMapKey(assetProperty);
329
324
  return this.assetsMap.get(key)?.refCount || 0;
330
325
  }
@@ -1,29 +1,54 @@
1
1
  import { FWManager } from './FWManager';
2
- import Framework from '../Framework';
3
2
  import FWLog from '../log/FWLog';
3
+ import { FWSystemDefine } from '../define/FWSystemDefine';
4
+
5
+ class FWAudioData {
6
+ clip: cc.AudioClip;
7
+ volume: number = 1;
8
+ loop: boolean = false;
9
+ cb?: (id: number) => void;
10
+ tag?: string;
11
+ }
12
+
13
+ class FWAudioPoolItem {
14
+ constructor(public audioId: number, public startTime: number, public tag?: string) {}
15
+ }
4
16
 
5
17
  export default class FWAudioManager extends FWManager implements FW.AudioManager {
6
- /**背景音量 */
7
18
  musicVolume: number = 1;
8
- /**音效音量 */
9
19
  effectsVolume: number = 1;
10
- /**播放音效的音量 */
11
20
  soundVolume: number = 1;
12
21
 
13
- public initialize(): void {}
22
+ private audioPool: Map<number, FWAudioPoolItem> = new Map();
23
+ private maxConcurrentAudio: number = 10;
24
+ private cleanupInterval: number = 5;
14
25
 
15
- playMusic(path: string, volume?: number, loop?: boolean);
16
- playMusic(music: cc.AudioClip, volume?: number, loop?: boolean);
17
- playMusic(assetProperty: FW.AssetProperty, volume?: number, loop?: boolean);
18
- async playMusic() {
26
+ public initialize(): void {
27
+ this.autoClear();
28
+ }
29
+
30
+ /**
31
+ * 播放背景音乐
32
+ */
33
+ async playMusic(path: string, volume?: number, loop?: boolean): Promise<void>;
34
+ async playMusic(music: cc.AudioClip, volume?: number, loop?: boolean): Promise<void>;
35
+ async playMusic(assetProperty: FW.AssetProperty, volume?: number, loop?: boolean): Promise<void>;
36
+ async playMusic(): Promise<void> {
19
37
  try {
20
- const data = await this.dataProcessing(arguments[0], arguments[1], arguments[2]);
21
- if (!data) return;
22
- const clip = data.clip;
23
- const volume = data.volume === undefined ? this.musicVolume : arguments[1];
24
- const loop = data.loop === undefined ? true : arguments[2];
25
- const id = cc.audioEngine.playMusic(clip, loop);
38
+ const audioData = await this.processAudioArguments(
39
+ arguments,
40
+ FWSystemDefine.FWAudioType.MUSIC,
41
+ );
42
+ if (!audioData.clip) return;
43
+
44
+ const volume = audioData.volume ?? this.musicVolume;
45
+ const loop = audioData.loop ?? true;
46
+
47
+ cc.audioEngine.stopMusic();
48
+ const id = cc.audioEngine.playMusic(audioData.clip, loop);
26
49
  cc.audioEngine.setVolume(id, volume);
50
+
51
+ FWLog.debug(`播放背景音乐: ${this.getAudioName(audioData.clip)}`);
27
52
  } catch (e) {
28
53
  FWLog.error('播放音乐失败:', e);
29
54
  }
@@ -32,255 +57,275 @@ export default class FWAudioManager extends FWManager implements FW.AudioManager
32
57
  stopMusic(): void {
33
58
  cc.audioEngine.stopMusic();
34
59
  }
60
+
35
61
  pauseMusic(): void {
36
62
  cc.audioEngine.pauseMusic();
37
63
  }
64
+
38
65
  resumeMusic(): void {
39
66
  cc.audioEngine.resumeMusic();
40
67
  }
41
- /**设置背景音效是否静音 */
68
+
69
+ /** 设置背景音效是否静音 */
42
70
  setMusicMute(mute: boolean): void {
43
- if (mute) {
44
- this.musicVolume = 1;
45
- } else {
46
- this.musicVolume = 0;
47
- }
71
+ this.musicVolume = mute ? 0 : 1;
48
72
  cc.audioEngine.setMusicVolume(this.musicVolume);
49
73
  }
50
74
 
75
+ /**
76
+ * 播放音效
77
+ */
51
78
  async play(
52
79
  path: string,
53
80
  cb?: (id: number) => void,
54
81
  volume?: number,
55
82
  loop?: boolean,
83
+ tag?: string,
56
84
  ): Promise<number>;
57
85
  async play(
58
86
  audio: cc.AudioClip,
59
87
  cb?: (id: number) => void,
60
88
  volume?: number,
61
89
  loop?: boolean,
90
+ tag?: string,
62
91
  ): Promise<number>;
63
92
  async play(
64
93
  assetProperty: FW.AssetProperty,
65
94
  cb?: (id: number) => void,
66
95
  volume?: number,
67
96
  loop?: boolean,
97
+ tag?: string,
68
98
  ): Promise<number>;
69
99
  async play(): Promise<number> {
70
100
  try {
71
- const data = await this.dataProcessing(
72
- arguments[0],
73
- arguments[1],
74
- arguments[2],
75
- arguments[3],
101
+ if (this.audioPool.size >= this.maxConcurrentAudio) {
102
+ this.clearFinishedAudio();
103
+ if (this.audioPool.size >= this.maxConcurrentAudio) {
104
+ FWLog.warn('音频池已满,无法播放新音频');
105
+ return -1;
106
+ }
107
+ }
108
+
109
+ const audioData = await this.processAudioArguments(
110
+ arguments,
111
+ FWSystemDefine.FWAudioType.SOUND,
76
112
  );
77
- if (!data) return;
78
- const clip = data.clip;
79
- const volume = data.volume === undefined ? this.soundVolume : data.volume;
80
- const loop = data.loop === undefined ? false : data.loop;
81
- const id = cc.audioEngine.play(clip, loop, volume);
113
+ if (!audioData.clip) return -1;
114
+
115
+ const volume = (audioData.volume ?? this.soundVolume) * this.effectsVolume;
116
+ const loop = audioData.loop ?? false;
117
+ const tag = audioData.tag;
118
+
119
+ const id = cc.audioEngine.play(audioData.clip, loop, volume);
120
+
121
+ this.audioPool.set(id, new FWAudioPoolItem(id, Date.now(), tag));
122
+
82
123
  cc.audioEngine.setFinishCallback(id, () => {
83
- data.cb?.(id);
124
+ audioData.cb?.(id);
125
+ this.audioPool.delete(id);
84
126
  });
127
+
85
128
  return id;
86
129
  } catch (e) {
87
130
  FWLog.error('播放音效失败:', e);
131
+ return -1;
88
132
  }
89
133
  }
134
+
90
135
  /**
91
- *
92
- * @param id 停止
136
+ * 停止指定音效
93
137
  */
94
- public stop(id: number) {
95
- cc.audioEngine.stop(id);
138
+ stop(id: number): void {
139
+ if (this.audioPool.has(id)) {
140
+ cc.audioEngine.stop(id);
141
+ this.audioPool.delete(id);
142
+ FWLog.debug(`停止音效: ID ${id}`);
143
+ }
96
144
  }
145
+
97
146
  pauseAll(): void {
98
147
  cc.audioEngine.pauseAll();
148
+ FWLog.debug('暂停所有音效');
99
149
  }
150
+
100
151
  resumeAll(): void {
101
152
  cc.audioEngine.resumeAll();
153
+ FWLog.debug('恢复所有音效');
102
154
  }
155
+
103
156
  stopAll(): void {
104
157
  cc.audioEngine.stopAll();
158
+ this.audioPool.clear();
159
+ FWLog.debug('停止所有音效');
105
160
  }
106
- /**设置播放音效是否静音 */
161
+
162
+ /** 设置播放音效是否静音 */
107
163
  setSoundMute(mute: boolean): void {
108
- if (mute) {
109
- this.soundVolume = 1;
110
- } else {
111
- this.soundVolume = 0;
112
- }
164
+ this.soundVolume = mute ? 0 : 1;
165
+ FWLog.debug(`音效${mute ? '静音' : '取消静音'}`);
113
166
  }
114
167
 
115
- /**播放音效 */
116
- async playEffect(path: string, loop?: boolean): Promise<number>;
117
- async playEffect(audio: cc.AudioClip, loop?: boolean): Promise<number>;
118
- async playEffect(assetProperty: FW.AssetProperty, loop?: boolean): Promise<number>;
119
- async playEffect(): Promise<number> {
168
+ /**
169
+ * 预加载音频资源
170
+ */
171
+ async preloadAudio(assetProperty: FW.AssetProperty): Promise<cc.AudioClip> {
120
172
  try {
121
- const data = await this.dataProcessing(arguments[0], arguments[1]);
122
- if (!data) return;
123
- const clip = data.clip;
124
- const loop = data.loop === undefined ? false : arguments[1];
125
- const id = cc.audioEngine.playEffect(clip, loop);
126
- return id;
173
+ let clip: cc.AudioClip;
174
+
175
+ if (typeof assetProperty === 'string') {
176
+ clip = await FW.Entry.resMgr.loadAsset<cc.AudioClip>({
177
+ path: assetProperty,
178
+ bundle: FW.Entry.bundleName,
179
+ });
180
+ } else {
181
+ clip = await FW.Entry.resMgr.loadAsset<cc.AudioClip>(assetProperty);
182
+ }
183
+
184
+ return clip;
127
185
  } catch (e) {
128
- FWLog.error('播放音效失败:', e);
186
+ return null;
129
187
  }
130
188
  }
131
189
 
132
190
  /**
133
- * 停止音效
134
- * @param effectName 声音文件名
135
- * @returns
191
+ * 释放音频资源
136
192
  */
137
- stopEffect(audioID: number): void {
138
- if (!audioID) return;
139
- return cc.audioEngine.stopEffect(audioID);
193
+ releaseAudio(assetProperty: FW.AssetProperty): void {
194
+ FW.Entry.resMgr.releaseAsset(assetProperty);
140
195
  }
196
+
141
197
  /**
142
- * 设置音效音量
143
- * @param volume 音量
198
+ * 获取音频池信息
144
199
  */
145
- setEffectsVolume(volume: number): void {
146
- cc.audioEngine.setEffectsVolume(volume);
200
+ getAudioPoolInfo(): { playing: number; maxConcurrent: number } {
201
+ return {
202
+ playing: this.audioPool.size,
203
+ maxConcurrent: this.maxConcurrentAudio,
204
+ };
147
205
  }
206
+
148
207
  /**
149
- * 停止播放所有音效
208
+ * 设置最大并发音频数
150
209
  */
151
- stopAllEffects(): void {
152
- cc.audioEngine.stopAllEffects();
210
+ setMaxConcurrentAudio(max: number): void {
211
+ this.maxConcurrentAudio = Math.max(1, max);
153
212
  }
154
- /**设置音效是否静音 */
155
- setEffectsMute(mute: boolean): void {
156
- if (mute) {
157
- this.effectsVolume = 1;
158
- } else {
159
- this.effectsVolume = 0;
160
- }
161
- cc.audioEngine.setEffectsVolume(this.effectsVolume);
213
+
214
+ /**
215
+ * 根据标签停止音效
216
+ */
217
+ stopByTag(tag: string): void {
218
+ const arr: number[] = [];
219
+
220
+ this.audioPool.forEach((item, id) => {
221
+ if (item.tag === tag) {
222
+ arr.push(id);
223
+ }
224
+ });
225
+
226
+ arr.forEach((id) => {
227
+ this.stop(id);
228
+ });
162
229
  }
163
230
 
164
- private async dataProcessing(
165
- path: string,
166
- cb?: (id: number) => void,
167
- volume?: number,
168
- loop?: boolean,
169
- ): Promise<{
170
- clip: cc.AudioClip;
171
- volume: number;
172
- loop: boolean;
173
- cb: (id: number) => void;
174
- }>;
175
-
176
- private async dataProcessing(
177
- audio: cc.AudioClip,
178
- cb?: (id: number) => void,
179
- volume?: number,
180
- loop?: boolean,
181
- ): Promise<{
182
- clip: cc.AudioClip;
183
- volume: number;
184
- loop: boolean;
185
- cb: (id: number) => void;
186
- }>;
187
-
188
- private async dataProcessing(
189
- assetProperty: FW.AssetProperty,
190
- cb?: (id: number) => void,
191
- volume?: number,
192
- loop?: boolean,
193
- ): Promise<{
194
- clip: cc.AudioClip;
195
- volume: number;
196
- loop: boolean;
197
- cb: (id: number) => void;
198
- }>;
199
-
200
- private async dataProcessing(
201
- path: string,
202
- volume?: number,
203
- loop?: boolean,
204
- ): Promise<{
205
- clip: cc.AudioClip;
206
- volume: number;
207
- loop: boolean;
208
- cb: (id: number) => void;
209
- }>;
210
- private async dataProcessing(
211
- music: cc.AudioClip,
212
- volume?: number,
213
- loop?: boolean,
214
- ): Promise<{
215
- clip: cc.AudioClip;
216
- volume: number;
217
- loop: boolean;
218
- cb: (id: number) => void;
219
- }>;
220
- private async dataProcessing(
221
- assetProperty: FW.AssetProperty,
222
- volume?: number,
223
- loop?: boolean,
224
- ): Promise<{
225
- clip: cc.AudioClip;
226
- volume: number;
227
- loop: boolean;
228
- cb: (id: number) => void;
229
- }>;
230
- private async dataProcessing(): Promise<{
231
- clip: cc.AudioClip;
232
- volume: number;
233
- loop: boolean;
234
- cb: (id: number) => void;
235
- }> {
236
- let clip: cc.AudioClip;
237
- if (typeof arguments[0] === 'string') {
238
- const bundle = FW.Entry.bundleName;
239
- const path = arguments[0];
240
- clip = await FW.Entry.resMgr.loadAsset<cc.AudioClip>(<FW.AssetProperty>{
241
- path: path,
242
- bundle: bundle,
243
- });
244
- } else if (typeof arguments[0] === 'object') {
245
- const bundle = (arguments[0] as FW.AssetProperty).bundle || FW.Entry.bundleName;
246
- const path = (arguments[0] as FW.AssetProperty).path;
247
- clip = await FW.Entry.resMgr.loadAsset<cc.AudioClip>(<FW.AssetProperty>{
248
- path: path,
249
- bundle: bundle,
250
- });
251
- } else if (arguments[0] instanceof cc.AudioClip) {
252
- clip = arguments[0];
231
+ /**
232
+ * 处理音频参数
233
+ */
234
+ private async processAudioArguments(
235
+ args: IArguments,
236
+ type: FWSystemDefine.FWAudioType,
237
+ ): Promise<FWAudioData> {
238
+ const audioData = new FWAudioData();
239
+ const argsArray = Array.from(args);
240
+
241
+ if (argsArray[0] instanceof cc.AudioClip) {
242
+ audioData.clip = argsArray[0];
243
+ } else {
244
+ const assetProperty = this.normalizeAssetProperty(argsArray[0]);
245
+ audioData.clip = await this.loadAudioClip(assetProperty);
253
246
  }
254
247
 
255
- let volume: number;
248
+ if (type === FWSystemDefine.FWAudioType.MUSIC) {
249
+ audioData.volume = argsArray[1];
250
+ audioData.loop = argsArray[2];
251
+ } else {
252
+ audioData.cb = argsArray[1];
253
+ audioData.volume = argsArray[2];
254
+ audioData.loop = argsArray[3];
255
+ audioData.tag = argsArray[4];
256
+ }
256
257
 
257
- let loop: boolean;
258
+ return audioData;
259
+ }
258
260
 
259
- let cb: (id: number) => void;
260
- if (typeof arguments[1] === 'function') {
261
- cb = arguments[1];
262
- } else if (typeof arguments[1] === 'number') {
263
- volume = arguments[1];
264
- } else if (typeof arguments[1] === 'boolean') {
265
- loop = arguments[1];
261
+ /**
262
+ * 标准化资源属性
263
+ */
264
+ private normalizeAssetProperty(input: any): FW.AssetProperty {
265
+ if (typeof input === 'string') {
266
+ return {
267
+ path: input,
268
+ bundle: FW.Entry.bundleName,
269
+ };
266
270
  }
271
+ return input;
272
+ }
267
273
 
268
- if (typeof arguments[2] === 'boolean') {
269
- loop = arguments[2];
270
- } else if (typeof arguments[2] === 'number') {
271
- volume = arguments[2];
274
+ /**
275
+ * 加载音频片段
276
+ */
277
+ private async loadAudioClip(assetProperty: FW.AssetProperty): Promise<cc.AudioClip> {
278
+ try {
279
+ return await FW.Entry.resMgr.loadAsset<cc.AudioClip>(assetProperty);
280
+ } catch (e) {
281
+ return null;
272
282
  }
283
+ }
273
284
 
274
- if (arguments.length == 4) {
275
- loop = arguments[3];
285
+ /**
286
+ * 生成缓存键
287
+ */
288
+ private getCacheKey(assetProperty: FW.AssetProperty): string {
289
+ if (typeof assetProperty === 'string') {
290
+ return `${FW.Entry.bundleName}_${assetProperty}`;
276
291
  }
292
+ return `${assetProperty.bundle || FW.Entry.bundleName}_${assetProperty.path}`;
293
+ }
277
294
 
278
- return {
279
- clip: clip,
280
- volume: volume,
281
- loop: loop,
282
- cb: cb,
283
- };
295
+ /**
296
+ * 获取音频名称
297
+ */
298
+ private getAudioName(clip: cc.AudioClip): string {
299
+ return clip.name || '未知音频';
300
+ }
301
+
302
+ /**
303
+ * 清理已完成的音频
304
+ */
305
+ private clearFinishedAudio(): void {
306
+ const currentTime = Date.now();
307
+ const maxAge = 30000;
308
+
309
+ this.audioPool.forEach((item, id) => {
310
+ if (!cc.audioEngine.getState(id)) {
311
+ this.audioPool.delete(id);
312
+ } else if (currentTime - item.startTime > maxAge) {
313
+ FWLog.warn(`强制停止超时音频: ID ${id}`);
314
+ this.stop(id);
315
+ }
316
+ });
317
+ }
318
+
319
+ /**
320
+ * 启动自动清理
321
+ */
322
+ private autoClear(): void {
323
+ FW.Entry.timeMgr.schedule(
324
+ this.clearFinishedAudio,
325
+ this.cleanupInterval,
326
+ cc.macro.REPEAT_FOREVER,
327
+ this,
328
+ );
284
329
  }
285
330
 
286
331
  public onDestroy(): void {