@mclawnet/agent 0.6.20 → 0.6.22
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.
- package/cli.js +63 -0
- package/dist/__tests__/checkpoint.test.d.ts +2 -0
- package/dist/__tests__/checkpoint.test.d.ts.map +1 -0
- package/dist/__tests__/fs-handler-decode.test.d.ts +2 -0
- package/dist/__tests__/fs-handler-decode.test.d.ts.map +1 -0
- package/dist/__tests__/idle-sweeper.test.d.ts +2 -0
- package/dist/__tests__/idle-sweeper.test.d.ts.map +1 -0
- package/dist/__tests__/mcp-config.test.d.ts +2 -0
- package/dist/__tests__/mcp-config.test.d.ts.map +1 -0
- package/dist/__tests__/schedule-runtime-spawn.test.d.ts +2 -0
- package/dist/__tests__/schedule-runtime-spawn.test.d.ts.map +1 -0
- package/dist/__tests__/schedule-runtime.test.d.ts +2 -0
- package/dist/__tests__/schedule-runtime.test.d.ts.map +1 -0
- package/dist/__tests__/session-limit.test.d.ts +2 -0
- package/dist/__tests__/session-limit.test.d.ts.map +1 -0
- package/dist/__tests__/swarm-cli-client.test.d.ts +2 -0
- package/dist/__tests__/swarm-cli-client.test.d.ts.map +1 -0
- package/dist/__tests__/swarm-control-dispatch.test.d.ts +2 -0
- package/dist/__tests__/swarm-control-dispatch.test.d.ts.map +1 -0
- package/dist/__tests__/swarm-session-bridge.test.d.ts +2 -0
- package/dist/__tests__/swarm-session-bridge.test.d.ts.map +1 -0
- package/dist/backend-adapter.d.ts +43 -0
- package/dist/backend-adapter.d.ts.map +1 -1
- package/dist/checkpoint.d.ts +67 -0
- package/dist/checkpoint.d.ts.map +1 -0
- package/dist/{chunk-RIK7IXSW.js → chunk-WJWCYGLQ.js} +1130 -147
- package/dist/chunk-WJWCYGLQ.js.map +1 -0
- package/dist/errors.d.ts +40 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/fs-handler.d.ts.map +1 -1
- package/dist/hub-connection.d.ts +13 -0
- package/dist/hub-connection.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/schedule-runtime.d.ts +125 -0
- package/dist/schedule-runtime.d.ts.map +1 -0
- package/dist/session-manager.d.ts +102 -0
- package/dist/session-manager.d.ts.map +1 -1
- package/dist/skill-loader.d.ts +20 -0
- package/dist/skill-loader.d.ts.map +1 -1
- package/dist/start.d.ts +2 -0
- package/dist/start.d.ts.map +1 -1
- package/dist/start.js +1 -1
- package/dist/swarm-cli-client.d.ts +24 -0
- package/dist/swarm-cli-client.d.ts.map +1 -0
- package/dist/swarm-cli-client.js +83 -0
- package/dist/swarm-cli-client.js.map +1 -0
- package/dist/swarm-control-dispatch.d.ts +47 -0
- package/dist/swarm-control-dispatch.d.ts.map +1 -0
- package/dist/swarm-session-bridge.d.ts +22 -0
- package/dist/swarm-session-bridge.d.ts.map +1 -0
- package/package.json +6 -4
- package/skills/cocos-creator-3x-cn/SKILL.md +475 -0
- package/skills/cocos-creator-3x-cn/references/framework/asset-management.md +322 -0
- package/skills/cocos-creator-3x-cn/references/framework/component-system.md +348 -0
- package/skills/cocos-creator-3x-cn/references/framework/event-patterns.md +410 -0
- package/skills/cocos-creator-3x-cn/references/framework/playable-optimization.md +257 -0
- package/skills/cocos-creator-3x-cn/references/language/performance.md +363 -0
- package/skills/cocos-creator-3x-cn/references/language/quality-hygiene.md +307 -0
- package/skills/cocos-creator-3x-cn/references/review/architecture-review.md +183 -0
- package/skills/cocos-creator-3x-cn/references/review/quality-review.md +251 -0
- package/skills/cocos-performance-optimizer/SKILL.md +214 -0
- package/skills/game-development/2d-games/SKILL.md +129 -0
- package/skills/game-development/3d-games/SKILL.md +145 -0
- package/skills/game-development/SKILL.md +175 -0
- package/skills/game-development/game-art/SKILL.md +195 -0
- package/skills/game-development/game-audio/SKILL.md +200 -0
- package/skills/game-development/game-design/SKILL.md +139 -0
- package/skills/game-development/mobile-games/SKILL.md +118 -0
- package/skills/game-development/multiplayer/SKILL.md +142 -0
- package/skills/game-development/pc-games/SKILL.md +154 -0
- package/skills/game-development/vr-ar/SKILL.md +133 -0
- package/skills/game-development/web-games/SKILL.md +160 -0
- package/skills/game-engine/SKILL.md +140 -0
- package/skills/game-engine/assets/2d-maze-game.md +528 -0
- package/skills/game-engine/assets/2d-platform-game.md +1855 -0
- package/skills/game-engine/assets/gameBase-template-repo.md +310 -0
- package/skills/game-engine/assets/paddle-game-template.md +1528 -0
- package/skills/game-engine/assets/simple-2d-engine.md +507 -0
- package/skills/game-engine/references/3d-web-games.md +754 -0
- package/skills/game-engine/references/algorithms.md +843 -0
- package/skills/game-engine/references/basics.md +343 -0
- package/skills/game-engine/references/game-control-mechanisms.md +617 -0
- package/skills/game-engine/references/game-engine-core-principles.md +695 -0
- package/skills/game-engine/references/game-publishing.md +352 -0
- package/skills/game-engine/references/techniques.md +894 -0
- package/skills/game-engine/references/terminology.md +354 -0
- package/skills/game-engine/references/web-apis.md +1394 -0
- package/skills/theone-cocos-standards/SKILL.md +557 -0
- package/skills/theone-cocos-standards/references/framework/component-system.md +645 -0
- package/skills/theone-cocos-standards/references/framework/event-patterns.md +433 -0
- package/skills/theone-cocos-standards/references/framework/playable-optimization.md +429 -0
- package/skills/theone-cocos-standards/references/framework/size-optimization.md +308 -0
- package/skills/theone-cocos-standards/references/language/modern-typescript.md +658 -0
- package/skills/theone-cocos-standards/references/language/performance.md +580 -0
- package/skills/theone-cocos-standards/references/language/quality-hygiene.md +582 -0
- package/skills/theone-cocos-standards/references/review/architecture-review.md +250 -0
- package/skills/theone-cocos-standards/references/review/performance-review.md +288 -0
- package/skills/theone-cocos-standards/references/review/quality-review.md +239 -0
- package/dist/chunk-RIK7IXSW.js.map +0 -1
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
# Cocos Creator Event Patterns
|
|
2
|
+
|
|
3
|
+
## EventDispatcher Pattern (Custom Events)
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { _decorator, Component, EventTarget } from 'cc';
|
|
7
|
+
const { ccclass } = _decorator;
|
|
8
|
+
|
|
9
|
+
// ✅ EXCELLENT: Centralized event system
|
|
10
|
+
export enum GameEvent {
|
|
11
|
+
SCORE_CHANGED = 'score_changed',
|
|
12
|
+
LEVEL_COMPLETE = 'level_complete',
|
|
13
|
+
PLAYER_DIED = 'player_died',
|
|
14
|
+
ENEMY_SPAWNED = 'enemy_spawned',
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ScoreChangedEvent {
|
|
18
|
+
oldScore: number;
|
|
19
|
+
newScore: number;
|
|
20
|
+
combo: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface LevelCompleteEvent {
|
|
24
|
+
level: number;
|
|
25
|
+
stars: number;
|
|
26
|
+
time: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@ccclass('EventManager')
|
|
30
|
+
export class EventManager extends Component {
|
|
31
|
+
private static instance: EventManager | null = null;
|
|
32
|
+
private readonly eventTarget: EventTarget = new EventTarget();
|
|
33
|
+
|
|
34
|
+
protected onLoad(): void {
|
|
35
|
+
if (EventManager.instance) {
|
|
36
|
+
throw new Error('EventManager: instance already exists');
|
|
37
|
+
}
|
|
38
|
+
EventManager.instance = this;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
protected onDestroy(): void {
|
|
42
|
+
this.eventTarget.clear();
|
|
43
|
+
EventManager.instance = null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ✅ EXCELLENT: Type-safe emit
|
|
47
|
+
public static emit<T>(event: GameEvent, data?: T): void {
|
|
48
|
+
if (!EventManager.instance) {
|
|
49
|
+
throw new Error('EventManager: instance not initialized');
|
|
50
|
+
}
|
|
51
|
+
EventManager.instance.eventTarget.emit(event, data);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ✅ EXCELLENT: Type-safe subscribe
|
|
55
|
+
public static on<T>(event: GameEvent, callback: (data: T) => void, target?: any): void {
|
|
56
|
+
if (!EventManager.instance) {
|
|
57
|
+
throw new Error('EventManager: instance not initialized');
|
|
58
|
+
}
|
|
59
|
+
EventManager.instance.eventTarget.on(event, callback, target);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ✅ EXCELLENT: Type-safe unsubscribe
|
|
63
|
+
public static off<T>(event: GameEvent, callback: (data: T) => void, target?: any): void {
|
|
64
|
+
if (!EventManager.instance) {
|
|
65
|
+
throw new Error('EventManager: instance not initialized');
|
|
66
|
+
}
|
|
67
|
+
EventManager.instance.eventTarget.off(event, callback, target);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ✅ EXCELLENT: Once (auto-unsubscribe after first call)
|
|
71
|
+
public static once<T>(event: GameEvent, callback: (data: T) => void, target?: any): void {
|
|
72
|
+
if (!EventManager.instance) {
|
|
73
|
+
throw new Error('EventManager: instance not initialized');
|
|
74
|
+
}
|
|
75
|
+
EventManager.instance.eventTarget.once(event, callback, target);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Usage in component
|
|
80
|
+
@ccclass('ScoreManager')
|
|
81
|
+
export class ScoreManager extends Component {
|
|
82
|
+
private currentScore: number = 0;
|
|
83
|
+
|
|
84
|
+
public addScore(points: number): void {
|
|
85
|
+
const oldScore = this.currentScore;
|
|
86
|
+
this.currentScore += points;
|
|
87
|
+
|
|
88
|
+
// ✅ EXCELLENT: Emit typed event
|
|
89
|
+
EventManager.emit<ScoreChangedEvent>(GameEvent.SCORE_CHANGED, {
|
|
90
|
+
oldScore,
|
|
91
|
+
newScore: this.currentScore,
|
|
92
|
+
combo: 1,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Subscriber component
|
|
98
|
+
@ccclass('ScoreDisplay')
|
|
99
|
+
export class ScoreDisplay extends Component {
|
|
100
|
+
protected onEnable(): void {
|
|
101
|
+
// ✅ EXCELLENT: Subscribe in onEnable
|
|
102
|
+
EventManager.on<ScoreChangedEvent>(GameEvent.SCORE_CHANGED, this.onScoreChanged, this);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
protected onDisable(): void {
|
|
106
|
+
// ✅ CRITICAL: Always unsubscribe in onDisable
|
|
107
|
+
EventManager.off<ScoreChangedEvent>(GameEvent.SCORE_CHANGED, this.onScoreChanged, this);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private onScoreChanged(data: ScoreChangedEvent): void {
|
|
111
|
+
console.log(`Score: ${data.oldScore} → ${data.newScore}`);
|
|
112
|
+
this.updateDisplay(data.newScore);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private updateDisplay(score: number): void {
|
|
116
|
+
// Update UI
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ❌ WRONG: No unsubscription (memory leak)
|
|
121
|
+
protected onEnable(): void {
|
|
122
|
+
EventManager.on<ScoreChangedEvent>(GameEvent.SCORE_CHANGED, this.onScoreChanged, this);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Missing onDisable - memory leak!
|
|
126
|
+
|
|
127
|
+
// ❌ WRONG: String-based events (not type-safe)
|
|
128
|
+
EventManager.emit('score_changed', { score: 100 }); // Typo-prone
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Node Event System (Built-in Events)
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
import { _decorator, Component, Node, EventTouch, EventKeyboard } from 'cc';
|
|
135
|
+
const { ccclass, property } = _decorator;
|
|
136
|
+
|
|
137
|
+
@ccclass('TouchHandler')
|
|
138
|
+
export class TouchHandler extends Component {
|
|
139
|
+
@property(Node)
|
|
140
|
+
private readonly buttonNode: Node | null = null;
|
|
141
|
+
|
|
142
|
+
// ✅ EXCELLENT: Touch event handling
|
|
143
|
+
protected onEnable(): void {
|
|
144
|
+
if (!this.buttonNode) return;
|
|
145
|
+
|
|
146
|
+
this.buttonNode.on(Node.EventType.TOUCH_START, this.onTouchStart, this);
|
|
147
|
+
this.buttonNode.on(Node.EventType.TOUCH_MOVE, this.onTouchMove, this);
|
|
148
|
+
this.buttonNode.on(Node.EventType.TOUCH_END, this.onTouchEnd, this);
|
|
149
|
+
this.buttonNode.on(Node.EventType.TOUCH_CANCEL, this.onTouchCancel, this);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
protected onDisable(): void {
|
|
153
|
+
if (!this.buttonNode) return;
|
|
154
|
+
|
|
155
|
+
this.buttonNode.off(Node.EventType.TOUCH_START, this.onTouchStart, this);
|
|
156
|
+
this.buttonNode.off(Node.EventType.TOUCH_MOVE, this.onTouchMove, this);
|
|
157
|
+
this.buttonNode.off(Node.EventType.TOUCH_END, this.onTouchEnd, this);
|
|
158
|
+
this.buttonNode.off(Node.EventType.TOUCH_CANCEL, this.onTouchCancel, this);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private onTouchStart(event: EventTouch): void {
|
|
162
|
+
const location = event.getUILocation();
|
|
163
|
+
console.log(`Touch start at: ${location.x}, ${location.y}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private onTouchMove(event: EventTouch): void {
|
|
167
|
+
const delta = event.getUIDelta();
|
|
168
|
+
console.log(`Touch delta: ${delta.x}, ${delta.y}`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private onTouchEnd(event: EventTouch): void {
|
|
172
|
+
console.log('Touch ended');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private onTouchCancel(event: EventTouch): void {
|
|
176
|
+
console.log('Touch cancelled');
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ✅ EXCELLENT: Keyboard event handling
|
|
181
|
+
@ccclass('KeyboardHandler')
|
|
182
|
+
export class KeyboardHandler extends Component {
|
|
183
|
+
protected onEnable(): void {
|
|
184
|
+
this.node.on(Node.EventType.KEY_DOWN, this.onKeyDown, this);
|
|
185
|
+
this.node.on(Node.EventType.KEY_UP, this.onKeyUp, this);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
protected onDisable(): void {
|
|
189
|
+
this.node.off(Node.EventType.KEY_DOWN, this.onKeyDown, this);
|
|
190
|
+
this.node.off(Node.EventType.KEY_UP, this.onKeyUp, this);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private onKeyDown(event: EventKeyboard): void {
|
|
194
|
+
switch (event.keyCode) {
|
|
195
|
+
case macro.KEY.w:
|
|
196
|
+
case macro.KEY.up:
|
|
197
|
+
this.moveUp();
|
|
198
|
+
break;
|
|
199
|
+
case macro.KEY.s:
|
|
200
|
+
case macro.KEY.down:
|
|
201
|
+
this.moveDown();
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private onKeyUp(event: EventKeyboard): void {
|
|
207
|
+
this.stopMovement();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Event Cleanup Patterns
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
import { _decorator, Component, Node } from 'cc';
|
|
216
|
+
const { ccclass, property } = _decorator;
|
|
217
|
+
|
|
218
|
+
// ✅ EXCELLENT: Comprehensive cleanup pattern
|
|
219
|
+
@ccclass('CompleteEventCleanup')
|
|
220
|
+
export class CompleteEventCleanup extends Component {
|
|
221
|
+
@property(Node)
|
|
222
|
+
private readonly targetNode: Node | null = null;
|
|
223
|
+
|
|
224
|
+
// Track registered listeners for complete cleanup
|
|
225
|
+
private readonly registeredListeners: Array<{
|
|
226
|
+
node: Node;
|
|
227
|
+
eventType: string;
|
|
228
|
+
callback: Function;
|
|
229
|
+
}> = [];
|
|
230
|
+
|
|
231
|
+
protected onEnable(): void {
|
|
232
|
+
if (!this.targetNode) return;
|
|
233
|
+
|
|
234
|
+
// Register and track listeners
|
|
235
|
+
this.registerListener(
|
|
236
|
+
this.targetNode,
|
|
237
|
+
Node.EventType.TOUCH_START,
|
|
238
|
+
this.onTouchStart
|
|
239
|
+
);
|
|
240
|
+
this.registerListener(
|
|
241
|
+
this.node,
|
|
242
|
+
Node.EventType.CHILD_ADDED,
|
|
243
|
+
this.onChildAdded
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
// Subscribe to global events
|
|
247
|
+
EventManager.on(GameEvent.LEVEL_COMPLETE, this.onLevelComplete, this);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
protected onDisable(): void {
|
|
251
|
+
// Unregister all tracked listeners
|
|
252
|
+
for (const { node, eventType, callback } of this.registeredListeners) {
|
|
253
|
+
node.off(eventType, callback, this);
|
|
254
|
+
}
|
|
255
|
+
this.registeredListeners.length = 0;
|
|
256
|
+
|
|
257
|
+
// Unsubscribe from global events
|
|
258
|
+
EventManager.off(GameEvent.LEVEL_COMPLETE, this.onLevelComplete, this);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
private registerListener(node: Node, eventType: string, callback: Function): void {
|
|
262
|
+
node.on(eventType, callback, this);
|
|
263
|
+
this.registeredListeners.push({ node, eventType, callback });
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private onTouchStart(event: EventTouch): void {
|
|
267
|
+
// Handle touch
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private onChildAdded(child: Node): void {
|
|
271
|
+
// Handle child added
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
private onLevelComplete(): void {
|
|
275
|
+
// Handle level complete
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// ✅ EXCELLENT: Automatic cleanup with disposable pattern
|
|
280
|
+
interface IDisposable {
|
|
281
|
+
dispose(): void;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
class EventSubscription implements IDisposable {
|
|
285
|
+
constructor(
|
|
286
|
+
private readonly eventManager: EventManager,
|
|
287
|
+
private readonly event: GameEvent,
|
|
288
|
+
private readonly callback: Function,
|
|
289
|
+
private readonly target: any
|
|
290
|
+
) {}
|
|
291
|
+
|
|
292
|
+
public dispose(): void {
|
|
293
|
+
EventManager.off(this.event, this.callback as any, this.target);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
@ccclass('DisposablePattern')
|
|
298
|
+
export class DisposablePattern extends Component {
|
|
299
|
+
private readonly subscriptions: IDisposable[] = [];
|
|
300
|
+
|
|
301
|
+
protected onEnable(): void {
|
|
302
|
+
// ✅ EXCELLENT: Track subscriptions for auto-cleanup
|
|
303
|
+
this.subscriptions.push(
|
|
304
|
+
new EventSubscription(
|
|
305
|
+
EventManager.instance!,
|
|
306
|
+
GameEvent.SCORE_CHANGED,
|
|
307
|
+
this.onScoreChanged,
|
|
308
|
+
this
|
|
309
|
+
)
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
protected onDisable(): void {
|
|
314
|
+
// ✅ EXCELLENT: Dispose all subscriptions
|
|
315
|
+
for (const subscription of this.subscriptions) {
|
|
316
|
+
subscription.dispose();
|
|
317
|
+
}
|
|
318
|
+
this.subscriptions.length = 0;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
private onScoreChanged(data: ScoreChangedEvent): void {
|
|
322
|
+
// Handle score change
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
## Event Performance Best Practices
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
import { _decorator, Component } from 'cc';
|
|
331
|
+
const { ccclass } = _decorator;
|
|
332
|
+
|
|
333
|
+
@ccclass('PerformanceOptimizedEvents')
|
|
334
|
+
export class PerformanceOptimizedEvents extends Component {
|
|
335
|
+
// ✅ EXCELLENT: Throttle frequent events
|
|
336
|
+
private lastEmitTime: number = 0;
|
|
337
|
+
private static readonly EMIT_THROTTLE_MS: number = 100; // Max 10 events/second
|
|
338
|
+
|
|
339
|
+
public emitThrottled(event: GameEvent, data: any): void {
|
|
340
|
+
const now = Date.now();
|
|
341
|
+
if (now - this.lastEmitTime >= PerformanceOptimizedEvents.EMIT_THROTTLE_MS) {
|
|
342
|
+
EventManager.emit(event, data);
|
|
343
|
+
this.lastEmitTime = now;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// ✅ EXCELLENT: Batch events to reduce overhead
|
|
348
|
+
private readonly pendingEvents: Array<{ event: GameEvent; data: any }> = [];
|
|
349
|
+
private batchEmitScheduled: boolean = false;
|
|
350
|
+
|
|
351
|
+
public emitBatched(event: GameEvent, data: any): void {
|
|
352
|
+
this.pendingEvents.push({ event, data });
|
|
353
|
+
|
|
354
|
+
if (!this.batchEmitScheduled) {
|
|
355
|
+
this.batchEmitScheduled = true;
|
|
356
|
+
this.scheduleOnce(() => {
|
|
357
|
+
this.flushBatchedEvents();
|
|
358
|
+
}, 0);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
private flushBatchedEvents(): void {
|
|
363
|
+
for (const { event, data } of this.pendingEvents) {
|
|
364
|
+
EventManager.emit(event, data);
|
|
365
|
+
}
|
|
366
|
+
this.pendingEvents.length = 0;
|
|
367
|
+
this.batchEmitScheduled = false;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// ✅ EXCELLENT: Debounce events (emit only after quiet period)
|
|
371
|
+
private debounceTimer: number | null = null;
|
|
372
|
+
private static readonly DEBOUNCE_MS: number = 300;
|
|
373
|
+
|
|
374
|
+
public emitDebounced(event: GameEvent, data: any): void {
|
|
375
|
+
if (this.debounceTimer !== null) {
|
|
376
|
+
clearTimeout(this.debounceTimer);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
this.debounceTimer = setTimeout(() => {
|
|
380
|
+
EventManager.emit(event, data);
|
|
381
|
+
this.debounceTimer = null;
|
|
382
|
+
}, PerformanceOptimizedEvents.DEBOUNCE_MS) as any;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// ❌ WRONG: Emitting events in update loop
|
|
387
|
+
protected update(dt: number): void {
|
|
388
|
+
// Emits 60 events per second!
|
|
389
|
+
EventManager.emit(GameEvent.PLAYER_MOVED, this.node.position);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// ✅ BETTER: Throttle or emit only on significant changes
|
|
393
|
+
private lastPosition: Vec3 = new Vec3();
|
|
394
|
+
private static readonly MOVE_THRESHOLD: number = 1.0;
|
|
395
|
+
|
|
396
|
+
protected update(dt: number): void {
|
|
397
|
+
const distance = Vec3.distance(this.node.position, this.lastPosition);
|
|
398
|
+
|
|
399
|
+
if (distance >= PerformanceOptimizedEvents.MOVE_THRESHOLD) {
|
|
400
|
+
EventManager.emit(GameEvent.PLAYER_MOVED, this.node.position.clone());
|
|
401
|
+
this.lastPosition.set(this.node.position);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
## Summary: Event Pattern Checklist
|
|
407
|
+
|
|
408
|
+
**EventDispatcher (Custom Events):**
|
|
409
|
+
- [ ] Use centralized EventManager with EventTarget
|
|
410
|
+
- [ ] Define event names as enum (not strings)
|
|
411
|
+
- [ ] Use typed event data interfaces
|
|
412
|
+
- [ ] Subscribe in onEnable(), unsubscribe in onDisable()
|
|
413
|
+
- [ ] Always pass `this` as target parameter for proper cleanup
|
|
414
|
+
|
|
415
|
+
**Node Events (Built-in):**
|
|
416
|
+
- [ ] Use Node.EventType constants (TOUCH_START, KEY_DOWN, etc.)
|
|
417
|
+
- [ ] Register listeners in onEnable()
|
|
418
|
+
- [ ] Unregister listeners in onDisable() with same parameters
|
|
419
|
+
- [ ] Handle EventTouch and EventKeyboard properly
|
|
420
|
+
|
|
421
|
+
**Event Cleanup:**
|
|
422
|
+
- [ ] Track all registered listeners for complete cleanup
|
|
423
|
+
- [ ] Unregister in both onDisable() and onDestroy()
|
|
424
|
+
- [ ] Use disposable pattern for automatic cleanup
|
|
425
|
+
- [ ] Clear event collections in onDestroy()
|
|
426
|
+
|
|
427
|
+
**Performance:**
|
|
428
|
+
- [ ] Throttle frequent events (max 10/second)
|
|
429
|
+
- [ ] Batch events to reduce overhead
|
|
430
|
+
- [ ] Debounce events for user input
|
|
431
|
+
- [ ] Never emit events in update() without throttling
|
|
432
|
+
|
|
433
|
+
**Always unsubscribe from events to prevent memory leaks.**
|