@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,580 @@
|
|
|
1
|
+
# TypeScript Performance Optimization
|
|
2
|
+
|
|
3
|
+
## Zero Allocations in update()
|
|
4
|
+
|
|
5
|
+
**Critical Rule**: Never allocate objects in `update()`, `lateUpdate()`, or any method called every frame.
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { _decorator, Component, Node, Vec3, Quat } from 'cc';
|
|
9
|
+
const { ccclass, property } = _decorator;
|
|
10
|
+
|
|
11
|
+
@ccclass('OptimizedController')
|
|
12
|
+
export class OptimizedController extends Component {
|
|
13
|
+
@property(Node)
|
|
14
|
+
private readonly targetNode: Node | null = null;
|
|
15
|
+
|
|
16
|
+
// ✅ EXCELLENT: Preallocated reusable objects
|
|
17
|
+
private readonly tempVec3: Vec3 = new Vec3();
|
|
18
|
+
private readonly tempQuat: Quat = new Quat();
|
|
19
|
+
private readonly tempVec3Array: Vec3[] = [];
|
|
20
|
+
|
|
21
|
+
// ✅ EXCELLENT: No allocations in update
|
|
22
|
+
protected update(dt: number): void {
|
|
23
|
+
if (!this.targetNode) return;
|
|
24
|
+
|
|
25
|
+
// Reuse preallocated vector
|
|
26
|
+
this.targetNode.getPosition(this.tempVec3);
|
|
27
|
+
this.tempVec3.y += 10 * dt;
|
|
28
|
+
this.targetNode.setPosition(this.tempVec3);
|
|
29
|
+
|
|
30
|
+
// Reuse preallocated quaternion
|
|
31
|
+
this.targetNode.getRotation(this.tempQuat);
|
|
32
|
+
Quat.rotateY(this.tempQuat, this.tempQuat, dt);
|
|
33
|
+
this.targetNode.setRotation(this.tempQuat);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ✅ EXCELLENT: Reuse array instead of creating new one
|
|
37
|
+
public updateMultipleNodes(nodes: Node[]): void {
|
|
38
|
+
this.tempVec3Array.length = 0; // Clear without allocating
|
|
39
|
+
|
|
40
|
+
for (const node of nodes) {
|
|
41
|
+
node.getPosition(this.tempVec3);
|
|
42
|
+
this.tempVec3Array.push(this.tempVec3.clone());
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ❌ WRONG: Allocating in update
|
|
48
|
+
protected update(dt: number): void {
|
|
49
|
+
if (!this.targetNode) return;
|
|
50
|
+
|
|
51
|
+
// Creates new Vec3 every frame (60 allocations/second)
|
|
52
|
+
const currentPos = this.targetNode.position.clone();
|
|
53
|
+
currentPos.y += 10 * dt;
|
|
54
|
+
this.targetNode.setPosition(currentPos);
|
|
55
|
+
|
|
56
|
+
// Creates new array every frame
|
|
57
|
+
const positions = this.nodes.map(n => n.position.clone());
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ❌ WRONG: String concatenation in update
|
|
61
|
+
protected update(dt: number): void {
|
|
62
|
+
// Creates new string every frame
|
|
63
|
+
const debugInfo = `Position: ${this.node.position.x}, ${this.node.position.y}`;
|
|
64
|
+
console.log(debugInfo);
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Object Pooling Pattern
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { _decorator, Component, Node, Prefab, instantiate, NodePool } from 'cc';
|
|
72
|
+
const { ccclass, property } = _decorator;
|
|
73
|
+
|
|
74
|
+
@ccclass('BulletPool')
|
|
75
|
+
export class BulletPool extends Component {
|
|
76
|
+
@property(Prefab)
|
|
77
|
+
private readonly bulletPrefab: Prefab | null = null;
|
|
78
|
+
|
|
79
|
+
private readonly pool: NodePool = new NodePool();
|
|
80
|
+
private static readonly INITIAL_POOL_SIZE: number = 20;
|
|
81
|
+
|
|
82
|
+
// ✅ EXCELLENT: Prewarm pool on initialization
|
|
83
|
+
protected onLoad(): void {
|
|
84
|
+
if (!this.bulletPrefab) {
|
|
85
|
+
throw new Error('BulletPool: bulletPrefab is required');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
for (let i = 0; i < BulletPool.INITIAL_POOL_SIZE; i++) {
|
|
89
|
+
const bullet = instantiate(this.bulletPrefab);
|
|
90
|
+
this.pool.put(bullet);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ✅ EXCELLENT: Get from pool (no allocation if available)
|
|
95
|
+
public getBullet(): Node {
|
|
96
|
+
let bullet: Node;
|
|
97
|
+
|
|
98
|
+
if (this.pool.size() > 0) {
|
|
99
|
+
bullet = this.pool.get()!;
|
|
100
|
+
} else {
|
|
101
|
+
// Only allocate if pool is empty
|
|
102
|
+
if (!this.bulletPrefab) {
|
|
103
|
+
throw new Error('BulletPool: bulletPrefab is required');
|
|
104
|
+
}
|
|
105
|
+
bullet = instantiate(this.bulletPrefab);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
bullet.active = true;
|
|
109
|
+
return bullet;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ✅ EXCELLENT: Return to pool (no deallocation)
|
|
113
|
+
public returnBullet(bullet: Node): void {
|
|
114
|
+
bullet.active = false;
|
|
115
|
+
this.pool.put(bullet);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ✅ EXCELLENT: Clear pool on cleanup
|
|
119
|
+
protected onDestroy(): void {
|
|
120
|
+
this.pool.clear();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Usage in game
|
|
125
|
+
@ccclass('Gun')
|
|
126
|
+
export class Gun extends Component {
|
|
127
|
+
private readonly bulletPool!: BulletPool;
|
|
128
|
+
|
|
129
|
+
public shoot(): void {
|
|
130
|
+
// ✅ GOOD: Get from pool instead of instantiate
|
|
131
|
+
const bullet = this.bulletPool.getBullet();
|
|
132
|
+
bullet.setPosition(this.node.position);
|
|
133
|
+
|
|
134
|
+
// Set up bullet with timeout to return to pool
|
|
135
|
+
this.scheduleOnce(() => {
|
|
136
|
+
this.bulletPool.returnBullet(bullet);
|
|
137
|
+
}, 3.0);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ❌ WRONG: Creating new instances every time
|
|
142
|
+
public shoot(): void {
|
|
143
|
+
// Allocates and deallocates constantly
|
|
144
|
+
const bullet = instantiate(this.bulletPrefab!);
|
|
145
|
+
bullet.setPosition(this.node.position);
|
|
146
|
+
|
|
147
|
+
this.scheduleOnce(() => {
|
|
148
|
+
bullet.destroy(); // Triggers garbage collection
|
|
149
|
+
}, 3.0);
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Caching Expensive Operations
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
import { _decorator, Component, Node } from 'cc';
|
|
157
|
+
const { ccclass, property } = _decorator;
|
|
158
|
+
|
|
159
|
+
@ccclass('EnemyManager')
|
|
160
|
+
export class EnemyManager extends Component {
|
|
161
|
+
@property([Node])
|
|
162
|
+
private readonly enemyNodes: Node[] = [];
|
|
163
|
+
|
|
164
|
+
// ✅ EXCELLENT: Cache component references
|
|
165
|
+
private readonly enemyControllers: EnemyController[] = [];
|
|
166
|
+
private cachedActiveEnemies: EnemyController[] = [];
|
|
167
|
+
private activeEnemiesDirty: boolean = true;
|
|
168
|
+
|
|
169
|
+
protected onLoad(): void {
|
|
170
|
+
// Cache component references on initialization
|
|
171
|
+
for (const node of this.enemyNodes) {
|
|
172
|
+
const controller = node.getComponent(EnemyController);
|
|
173
|
+
if (controller) {
|
|
174
|
+
this.enemyControllers.push(controller);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ✅ EXCELLENT: Mark cache as dirty instead of recalculating
|
|
180
|
+
public onEnemyStateChanged(): void {
|
|
181
|
+
this.activeEnemiesDirty = true;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ✅ EXCELLENT: Lazy recalculation only when needed
|
|
185
|
+
public getActiveEnemies(): EnemyController[] {
|
|
186
|
+
if (this.activeEnemiesDirty) {
|
|
187
|
+
this.cachedActiveEnemies = this.enemyControllers.filter(e => e.isActive);
|
|
188
|
+
this.activeEnemiesDirty = false;
|
|
189
|
+
}
|
|
190
|
+
return this.cachedActiveEnemies;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
protected update(dt: number): void {
|
|
194
|
+
// ✅ GOOD: Use cached active enemies
|
|
195
|
+
const activeEnemies = this.getActiveEnemies();
|
|
196
|
+
|
|
197
|
+
for (const enemy of activeEnemies) {
|
|
198
|
+
enemy.update(dt);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ❌ WRONG: Finding components every frame
|
|
204
|
+
protected update(dt: number): void {
|
|
205
|
+
for (const node of this.enemyNodes) {
|
|
206
|
+
const controller = node.getComponent(EnemyController); // Expensive lookup!
|
|
207
|
+
if (controller?.isActive) {
|
|
208
|
+
controller.update(dt);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ❌ WRONG: Filtering every frame
|
|
214
|
+
protected update(dt: number): void {
|
|
215
|
+
const activeEnemies = this.enemyControllers.filter(e => e.isActive); // Allocates array every frame!
|
|
216
|
+
for (const enemy of activeEnemies) {
|
|
217
|
+
enemy.update(dt);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Throttling Expensive Operations
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
import { _decorator, Component } from 'cc';
|
|
226
|
+
const { ccclass } = _decorator;
|
|
227
|
+
|
|
228
|
+
@ccclass('AIController')
|
|
229
|
+
export class AIController extends Component {
|
|
230
|
+
private frameCount: number = 0;
|
|
231
|
+
private static readonly AI_UPDATE_INTERVAL: number = 10; // Every 10 frames
|
|
232
|
+
private static readonly PATHFINDING_INTERVAL: number = 60; // Every 60 frames (1 second at 60fps)
|
|
233
|
+
|
|
234
|
+
// ✅ EXCELLENT: Update AI every N frames, not every frame
|
|
235
|
+
protected update(dt: number): void {
|
|
236
|
+
this.frameCount++;
|
|
237
|
+
|
|
238
|
+
// Run expensive AI logic every 10 frames instead of every frame
|
|
239
|
+
if (this.frameCount % AIController.AI_UPDATE_INTERVAL === 0) {
|
|
240
|
+
this.updateAIDecision();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Run very expensive pathfinding every 60 frames (1 second)
|
|
244
|
+
if (this.frameCount % AIController.PATHFINDING_INTERVAL === 0) {
|
|
245
|
+
this.recalculatePath();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Cheap operations can run every frame
|
|
249
|
+
this.moveTowardsTarget(dt);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
private updateAIDecision(): void {
|
|
253
|
+
// Expensive: Check all enemies, evaluate threats, etc.
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private recalculatePath(): void {
|
|
257
|
+
// Very expensive: A* pathfinding
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private moveTowardsTarget(dt: number): void {
|
|
261
|
+
// Cheap: Simple movement
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ❌ WRONG: Expensive operations every frame
|
|
266
|
+
protected update(dt: number): void {
|
|
267
|
+
this.recalculatePath(); // A* pathfinding 60 times per second!
|
|
268
|
+
this.updateAIDecision(); // Complex AI logic 60 times per second!
|
|
269
|
+
this.moveTowardsTarget(dt);
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Time-Based Throttling
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
import { _decorator, Component } from 'cc';
|
|
277
|
+
const { ccclass } = _decorator;
|
|
278
|
+
|
|
279
|
+
@ccclass('PerformanceMonitor')
|
|
280
|
+
export class PerformanceMonitor extends Component {
|
|
281
|
+
private lastUpdateTime: number = 0;
|
|
282
|
+
private static readonly UPDATE_INTERVAL: number = 1.0; // 1 second
|
|
283
|
+
|
|
284
|
+
// ✅ EXCELLENT: Time-based throttling
|
|
285
|
+
protected update(dt: number): void {
|
|
286
|
+
const currentTime = Date.now() / 1000;
|
|
287
|
+
|
|
288
|
+
if (currentTime - this.lastUpdateTime >= PerformanceMonitor.UPDATE_INTERVAL) {
|
|
289
|
+
this.performExpensiveOperation();
|
|
290
|
+
this.lastUpdateTime = currentTime;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
private performExpensiveOperation(): void {
|
|
295
|
+
// Expensive operation that runs once per second
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Alternative using scheduleOnce
|
|
300
|
+
@ccclass('TimerBased')
|
|
301
|
+
export class TimerBased extends Component {
|
|
302
|
+
private static readonly CHECK_INTERVAL: number = 2.0; // 2 seconds
|
|
303
|
+
|
|
304
|
+
protected start(): void {
|
|
305
|
+
this.scheduleCheckRecurring();
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
private scheduleCheckRecurring(): void {
|
|
309
|
+
this.performCheck();
|
|
310
|
+
this.scheduleOnce(this.scheduleCheckRecurring, TimerBased.CHECK_INTERVAL);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
private performCheck(): void {
|
|
314
|
+
// Expensive check operation
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
## Avoid Expensive Lookups
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
import { _decorator, Component, Node, find } from 'cc';
|
|
323
|
+
const { ccclass } = _decorator;
|
|
324
|
+
|
|
325
|
+
@ccclass('GameManager')
|
|
326
|
+
export class GameManager extends Component {
|
|
327
|
+
// ✅ EXCELLENT: Cache references in onLoad
|
|
328
|
+
private uiRootNode!: Node;
|
|
329
|
+
private playerNode!: Node;
|
|
330
|
+
private enemyNodes: Node[] = [];
|
|
331
|
+
|
|
332
|
+
protected onLoad(): void {
|
|
333
|
+
// Cache node references once
|
|
334
|
+
const uiRoot = find('Canvas/UI');
|
|
335
|
+
if (!uiRoot) {
|
|
336
|
+
throw new Error('GameManager: UI root not found');
|
|
337
|
+
}
|
|
338
|
+
this.uiRootNode = uiRoot;
|
|
339
|
+
|
|
340
|
+
const player = find('Canvas/Player');
|
|
341
|
+
if (!player) {
|
|
342
|
+
throw new Error('GameManager: Player not found');
|
|
343
|
+
}
|
|
344
|
+
this.playerNode = player;
|
|
345
|
+
|
|
346
|
+
// Cache array of enemy nodes
|
|
347
|
+
const enemyParent = find('Canvas/Enemies');
|
|
348
|
+
if (enemyParent) {
|
|
349
|
+
this.enemyNodes = enemyParent.children.slice();
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
protected update(dt: number): void {
|
|
354
|
+
// ✅ GOOD: Use cached references
|
|
355
|
+
this.updatePlayer(this.playerNode, dt);
|
|
356
|
+
this.updateEnemies(this.enemyNodes, dt);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// ❌ WRONG: Finding nodes every frame
|
|
361
|
+
protected update(dt: number): void {
|
|
362
|
+
const player = find('Canvas/Player'); // Expensive search every frame!
|
|
363
|
+
const enemies = find('Canvas/Enemies')?.children; // Expensive search every frame!
|
|
364
|
+
|
|
365
|
+
if (player) {
|
|
366
|
+
this.updatePlayer(player, dt);
|
|
367
|
+
}
|
|
368
|
+
if (enemies) {
|
|
369
|
+
this.updateEnemies(enemies, dt);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// ❌ WRONG: getComponent every frame
|
|
374
|
+
protected update(dt: number): void {
|
|
375
|
+
const playerController = this.playerNode.getComponent(PlayerController); // Expensive lookup!
|
|
376
|
+
playerController?.update(dt);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// ✅ BETTER: Cache component reference
|
|
380
|
+
private playerController!: PlayerController;
|
|
381
|
+
|
|
382
|
+
protected onLoad(): void {
|
|
383
|
+
const controller = this.playerNode.getComponent(PlayerController);
|
|
384
|
+
if (!controller) {
|
|
385
|
+
throw new Error('PlayerController not found');
|
|
386
|
+
}
|
|
387
|
+
this.playerController = controller;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
protected update(dt: number): void {
|
|
391
|
+
this.playerController.update(dt);
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
## String Concatenation Performance
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
import { _decorator, Component } from 'cc';
|
|
399
|
+
const { ccclass } = _decorator;
|
|
400
|
+
|
|
401
|
+
@ccclass('DebugDisplay')
|
|
402
|
+
export class DebugDisplay extends Component {
|
|
403
|
+
// ✅ EXCELLENT: Use template literals for readability
|
|
404
|
+
public getDebugInfo(player: Player): string {
|
|
405
|
+
return `Player: ${player.name}, HP: ${player.health}/${player.maxHealth}, Level: ${player.level}`;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// ✅ EXCELLENT: Build strings efficiently with array join (for large strings)
|
|
409
|
+
public generateReport(players: Player[]): string {
|
|
410
|
+
const lines: string[] = [];
|
|
411
|
+
lines.push('=== Player Report ===');
|
|
412
|
+
|
|
413
|
+
for (const player of players) {
|
|
414
|
+
lines.push(`${player.name}: Level ${player.level}, HP ${player.health}`);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
lines.push('=== End Report ===');
|
|
418
|
+
return lines.join('\n');
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// ✅ EXCELLENT: Avoid string operations in update loop
|
|
422
|
+
private debugText: string = '';
|
|
423
|
+
private frameCount: number = 0;
|
|
424
|
+
|
|
425
|
+
protected update(dt: number): void {
|
|
426
|
+
this.frameCount++;
|
|
427
|
+
|
|
428
|
+
// Only update debug text every 30 frames
|
|
429
|
+
if (this.frameCount % 30 === 0) {
|
|
430
|
+
this.debugText = this.generateDebugText();
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// ❌ WRONG: String concatenation in loop
|
|
436
|
+
public generateReport(players: Player[]): string {
|
|
437
|
+
let report = '=== Player Report ===\n';
|
|
438
|
+
|
|
439
|
+
for (const player of players) {
|
|
440
|
+
report += `${player.name}: Level ${player.level}\n`; // Allocates new string each iteration
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
report += '=== End Report ===';
|
|
444
|
+
return report;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// ❌ WRONG: Building strings in update
|
|
448
|
+
protected update(dt: number): void {
|
|
449
|
+
this.debugText = `FPS: ${1/dt}, Position: ${this.node.position}`; // Allocates every frame
|
|
450
|
+
}
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
## Number Operations Performance
|
|
454
|
+
|
|
455
|
+
```typescript
|
|
456
|
+
import { _decorator, Component } from 'cc';
|
|
457
|
+
const { ccclass } = _decorator;
|
|
458
|
+
|
|
459
|
+
@ccclass('MathOptimizations')
|
|
460
|
+
export class MathOptimizations extends Component {
|
|
461
|
+
// ✅ EXCELLENT: Use multiplication instead of division
|
|
462
|
+
private static readonly INV_FRAME_RATE: number = 1 / 60;
|
|
463
|
+
|
|
464
|
+
public calculateTimedValue(value: number): number {
|
|
465
|
+
return value * MathOptimizations.INV_FRAME_RATE; // Faster than value / 60
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// ✅ EXCELLENT: Use bitwise operations for integer math
|
|
469
|
+
public fastFloor(value: number): number {
|
|
470
|
+
return value | 0; // Faster than Math.floor for positive numbers
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
public isPowerOfTwo(value: number): boolean {
|
|
474
|
+
return (value & (value - 1)) === 0; // Faster than logarithm check
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// ✅ EXCELLENT: Cache expensive math operations
|
|
478
|
+
private readonly sinCache: Map<number, number> = new Map();
|
|
479
|
+
|
|
480
|
+
public getCachedSin(angle: number): number {
|
|
481
|
+
if (!this.sinCache.has(angle)) {
|
|
482
|
+
this.sinCache.set(angle, Math.sin(angle));
|
|
483
|
+
}
|
|
484
|
+
return this.sinCache.get(angle)!;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// ✅ EXCELLENT: Use squared distance to avoid sqrt
|
|
488
|
+
public isWithinRange(pos1: Vec3, pos2: Vec3, range: number): boolean {
|
|
489
|
+
const dx = pos2.x - pos1.x;
|
|
490
|
+
const dy = pos2.y - pos1.y;
|
|
491
|
+
const dz = pos2.z - pos1.z;
|
|
492
|
+
const distSquared = dx * dx + dy * dy + dz * dz;
|
|
493
|
+
const rangeSquared = range * range;
|
|
494
|
+
return distSquared <= rangeSquared; // No sqrt needed
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// ❌ WRONG: Using expensive operations
|
|
499
|
+
public isWithinRange(pos1: Vec3, pos2: Vec3, range: number): boolean {
|
|
500
|
+
const distance = Vec3.distance(pos1, pos2); // Uses sqrt internally
|
|
501
|
+
return distance <= range;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// ❌ WRONG: Division in hot path
|
|
505
|
+
protected update(dt: number): void {
|
|
506
|
+
const value = this.baseValue / 60; // Division is slower than multiplication
|
|
507
|
+
}
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
## Memory Management Best Practices
|
|
511
|
+
|
|
512
|
+
```typescript
|
|
513
|
+
import { _decorator, Component, Node } from 'cc';
|
|
514
|
+
const { ccclass } = _decorator;
|
|
515
|
+
|
|
516
|
+
@ccclass('ResourceManager')
|
|
517
|
+
export class ResourceManager extends Component {
|
|
518
|
+
private readonly loadedAssets: Map<string, Asset> = new Map();
|
|
519
|
+
private readonly nodeReferences: Set<Node> = new Set();
|
|
520
|
+
|
|
521
|
+
// ✅ EXCELLENT: Clear references on cleanup
|
|
522
|
+
protected onDestroy(): void {
|
|
523
|
+
// Clear maps and sets
|
|
524
|
+
this.loadedAssets.clear();
|
|
525
|
+
this.nodeReferences.clear();
|
|
526
|
+
|
|
527
|
+
// Remove event listeners
|
|
528
|
+
this.node.off(Node.EventType.TOUCH_START);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// ✅ EXCELLENT: Remove unused assets
|
|
532
|
+
public unloadAsset(assetId: string): void {
|
|
533
|
+
const asset = this.loadedAssets.get(assetId);
|
|
534
|
+
if (asset) {
|
|
535
|
+
asset.decRef(); // Release reference
|
|
536
|
+
this.loadedAssets.delete(assetId);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// ✅ EXCELLENT: Weak references for caches
|
|
541
|
+
private readonly weakNodeCache: WeakMap<Node, CachedData> = new WeakMap();
|
|
542
|
+
|
|
543
|
+
public getCachedData(node: Node): CachedData | undefined {
|
|
544
|
+
return this.weakNodeCache.get(node);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
public setCachedData(node: Node, data: CachedData): void {
|
|
548
|
+
this.weakNodeCache.set(node, data);
|
|
549
|
+
// Node is garbage collected → cache entry is automatically removed
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// ❌ WRONG: Memory leaks
|
|
554
|
+
protected onDestroy(): void {
|
|
555
|
+
// Forgot to clear references - memory leak!
|
|
556
|
+
// this.loadedAssets.clear();
|
|
557
|
+
// this.nodeReferences.clear();
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// ❌ WRONG: Strong references prevent garbage collection
|
|
561
|
+
private readonly nodeCache: Map<Node, CachedData> = new Map();
|
|
562
|
+
// Nodes are never garbage collected even when destroyed
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
## Summary: Performance Checklist
|
|
566
|
+
|
|
567
|
+
**Critical for playable ads (<5MB, <10 DrawCalls):**
|
|
568
|
+
|
|
569
|
+
- [ ] Zero allocations in update() (preallocate and reuse)
|
|
570
|
+
- [ ] Object pooling for frequently created/destroyed objects
|
|
571
|
+
- [ ] Cache component and node references (no getComponent in update)
|
|
572
|
+
- [ ] Throttle expensive operations (every N frames, not every frame)
|
|
573
|
+
- [ ] Avoid string operations in hot paths
|
|
574
|
+
- [ ] Use multiplication instead of division
|
|
575
|
+
- [ ] Use squared distance instead of distance (avoid sqrt)
|
|
576
|
+
- [ ] Clear references in onDestroy() to prevent memory leaks
|
|
577
|
+
- [ ] Use WeakMap for caches that should be garbage collected
|
|
578
|
+
- [ ] Array.length = 0 to clear arrays (don't create new arrays)
|
|
579
|
+
|
|
580
|
+
**Performance is critical for smooth 60fps playable ads.**
|