@kkarum/framework 2.3.18 → 2.3.20

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.
@@ -0,0 +1,233 @@
1
+ # Error Handling
2
+
3
+ 本文档定义框架内业务错误处理、异步失败、资源失败和生命周期清理规范。
4
+
5
+ ## 日志
6
+
7
+ 统一使用 `FW.Log`:
8
+
9
+ ```ts
10
+ FW.Log.debug("message");
11
+ FW.Log.warn("warning", data);
12
+ FW.Log.error("error", error);
13
+ FW.Log.request("request", msg);
14
+ FW.Log.response("response", msg);
15
+ FW.Log.send("socket send", msg);
16
+ FW.Log.receive("socket receive", msg);
17
+ FW.Log.system("system", msg);
18
+ ```
19
+
20
+ 发布构建中框架会关闭 console 输出。业务代码不要自行覆盖 console。
21
+
22
+ ## FrameworkBase.invoke
23
+
24
+ 异步业务方法优先用 `invoke()`:
25
+
26
+ ```ts
27
+ const result = await this.invoke(
28
+ FW.Entry.resMgr.loadAssetData(asset),
29
+ "loadAssetData",
30
+ );
31
+
32
+ if (!result) {
33
+ return;
34
+ }
35
+ ```
36
+
37
+ `invoke()` 会:
38
+
39
+ - 记录开始和结束时间。
40
+ - 上报到 `performanceMgr`。
41
+ - 慢操作输出 warn。
42
+ - 捕获异常并输出结构化错误。
43
+ - 返回 `undefined`。
44
+
45
+ 调用方必须处理 `undefined`。
46
+
47
+ ## Promise 错误
48
+
49
+ 使用 `FW.Entry.promiseMgr.execute()` 时设置合理超时:
50
+
51
+ ```ts
52
+ const proxy = FW.Entry.promiseMgr.execute(resolveWork, {
53
+ timeout: 10000,
54
+ retryCount: 3,
55
+ retryInterval: 2,
56
+ retryCondition: (error, count) => true,
57
+ });
58
+ ```
59
+
60
+ 取消:
61
+
62
+ ```ts
63
+ proxy.abort("layer closed");
64
+ ```
65
+
66
+ 批量结果不会直接抛出所有错误,而是返回:
67
+
68
+ ```ts
69
+ {
70
+ success: [{ id, value }],
71
+ failed: [{ id, reason }],
72
+ cancelled: [id]
73
+ }
74
+ ```
75
+
76
+ ## 资源加载失败
77
+
78
+ 资源加载统一走 `FW.Entry.resMgr`。可注册拒绝处理器:
79
+
80
+ ```ts
81
+ FW.Entry.resMgr.registerRejectHandler({
82
+ loadBundleHandler(error, bundleName) {
83
+ FW.Log.error("load bundle failed", bundleName, error);
84
+ },
85
+ loadResHandler(error, path) {
86
+ FW.Log.error("load resource failed", path, error);
87
+ },
88
+ });
89
+ ```
90
+
91
+ 加载失败时:
92
+
93
+ - 不继续操作节点。
94
+ - 不调用 `getAsset()` 兜底。
95
+ - UI 应显示空态、重试按钮或关闭弹窗。
96
+ - Codex 不应通过修改框架资源管理器绕过错误。
97
+
98
+ ## Layer 错误
99
+
100
+ `FWLayerManager.openAsync()` 失败时会:
101
+
102
+ - 设置状态为 loading/opening。
103
+ - 失败后移除状态。
104
+ - 清理失败的 Layer 注册。
105
+ - 抛出错误。
106
+
107
+ 业务调用:
108
+
109
+ ```ts
110
+ try {
111
+ await FW.Entry.layerMgr.openAsync({ type: RewardLayerController });
112
+ } catch (error) {
113
+ FW.Log.error("open reward layer failed", error);
114
+ }
115
+ ```
116
+
117
+ `openSync()` 失败通常返回 `undefined`,调用方必须判断。
118
+
119
+ ## Socket 错误
120
+
121
+ Socket 错误处理在 `Handle` 中实现:
122
+
123
+ ```ts
124
+ onError(msg: any) {
125
+ FW.Log.error("socket error", msg);
126
+ }
127
+
128
+ onTimeout() {
129
+ FW.Entry.evtMgr.dispatch("SOCKET_TIMEOUT");
130
+ }
131
+
132
+ onWeakNetWork() {
133
+ FW.Entry.evtMgr.dispatch("SOCKET_WEAK_NETWORK");
134
+ }
135
+
136
+ onClose() {
137
+ FW.Entry.evtMgr.dispatch("SOCKET_CLOSED");
138
+ }
139
+ ```
140
+
141
+ 框架会处理:
142
+
143
+ - 心跳发送。
144
+ - 弱网检测。
145
+ - 心跳超时重连。
146
+ - 最大重连次数。
147
+ - 协议轮询清理。
148
+
149
+ 业务不要重复实现底层重连循环。
150
+
151
+ ## 事件清理
152
+
153
+ LayerController 销毁时,框架会调用:
154
+
155
+ ```ts
156
+ FW.Entry.timeMgr.unSchedule(this);
157
+ FW.Entry.evtMgr.targetOff(this);
158
+ this.layer?.unscheduleAllCallbacks();
159
+ this.layer?.node?.stopAllActions();
160
+ this.layer?.node?.destroy();
161
+ ```
162
+
163
+ 普通对象或非 Layer 组件必须自行清理:
164
+
165
+ ```ts
166
+ onDestroy() {
167
+ FW.Entry.evtMgr.targetOff(this);
168
+ FW.Entry.timeMgr.unSchedule(this);
169
+ }
170
+ ```
171
+
172
+ UI 事件使用 `uiMgr.register()` 后,可用:
173
+
174
+ ```ts
175
+ FW.Entry.uiMgr.unRegisterTarget(target);
176
+ ```
177
+
178
+ ## 定时器清理
179
+
180
+ 创建定时器时传 target:
181
+
182
+ ```ts
183
+ FW.Entry.timeMgr.schedule(this.tick, 1, cc.macro.REPEAT_FOREVER, this);
184
+ ```
185
+
186
+ 销毁时:
187
+
188
+ ```ts
189
+ FW.Entry.timeMgr.unSchedule(this);
190
+ ```
191
+
192
+ 禁止使用无 target 的长期定时器,除非它属于全局系统并有明确 tag。
193
+
194
+ ## 空值和节点有效性
195
+
196
+ 操作 Cocos 节点前检查:
197
+
198
+ ```ts
199
+ if (!cc.isValid(node)) {
200
+ FW.Log.warn("node invalid");
201
+ return;
202
+ }
203
+ ```
204
+
205
+ `FW.Entry.uiMgr.find()` 找不到节点会返回 `null` 并打印 warn。业务代码要处理:
206
+
207
+ ```ts
208
+ const button = this.find("ConfirmButton", this.layer.node);
209
+ if (!button) return;
210
+ ```
211
+
212
+ ## 错误边界
213
+
214
+ 推荐分层处理:
215
+
216
+ - 网络层:解析、连接、超时错误。
217
+ - Logic:业务失败和重试。
218
+ - Data:不抛 UI 错误。
219
+ - LayerController:展示失败状态、关闭或重试。
220
+
221
+ 不要让底层错误直接导致 UI 生命周期中断且没有日志。
222
+
223
+ ## Codex 错误处理清单
224
+
225
+ 生成代码时必须检查:
226
+
227
+ - 每个 `await` 的失败路径是否可接受。
228
+ - `invoke()` 返回 `undefined` 时是否处理。
229
+ - `openSync()` 返回空是否处理。
230
+ - 节点查找失败是否处理。
231
+ - 事件和定时器是否随 target 清理。
232
+ - 资源路径是否来自 `AssetConfig`。
233
+ - Socket 错误是否在 `Handle` 中有入口。
@@ -0,0 +1,6 @@
1
+ {
2
+ "ver": "2.0.2",
3
+ "uuid": "a955657c-a1bb-4b4a-b218-f17ff5649eb3",
4
+ "importer": "markdown",
5
+ "subMetas": {}
6
+ }
@@ -0,0 +1,414 @@
1
+ # Framework API
2
+
3
+ 本文档记录业务开发最常复用的框架 API。除非维护框架本身,不要直接修改 `assets/framework`。
4
+
5
+ ## 全局入口
6
+
7
+ ### `FW.Entry`
8
+
9
+ 常用属性:
10
+
11
+ | 属性 | 用途 |
12
+ | --- | --- |
13
+ | `bundleName` | 当前业务 bundle 名 |
14
+ | `scene` | 当前 `FW.Scene` |
15
+ | `resMgr` | 资源和 bundle 管理 |
16
+ | `layerMgr` | Layer 打开、关闭、栈、队列 |
17
+ | `uiMgr` | UI 事件、节点查找 |
18
+ | `evtMgr` | 框架事件总线 |
19
+ | `timeMgr` | 定时器和 update |
20
+ | `promiseMgr` | 可取消/超时/重试 Promise |
21
+ | `socketMgr` | Socket 连接 |
22
+ | `objectMgr` | 对象池 |
23
+ | `taskMgr` | 分帧任务 |
24
+ | `languageMgr` | 多语言 |
25
+ | `performanceMgr` | 性能采样 |
26
+ | `utils` | 通用工具 |
27
+
28
+ 方法:
29
+
30
+ ```ts
31
+ FW.Entry.getComponent<T>(identifier): T
32
+ FW.Entry.getComponents<T>(identifier?): T[]
33
+ FW.Entry.launchScene(bundleName, forceRelease?)
34
+ FW.Entry.register({ bundleName, sceneName, depend, autoRelease })
35
+ FW.Entry.unRegister(...)
36
+ FW.Entry.getDepend(bundleName): string[]
37
+ FW.Entry.update(dt)
38
+ FW.Entry.restart()
39
+ ```
40
+
41
+ ## 依赖容器
42
+
43
+ ### `FW.Framework`
44
+
45
+ ```ts
46
+ FW.Framework.register({
47
+ bundleName,
48
+ logic,
49
+ data,
50
+ config,
51
+ sender,
52
+ handle,
53
+ });
54
+
55
+ FW.Framework.unRegister(...)
56
+ FW.Framework.getComponent<T>(ClassOrKey)
57
+ FW.Framework.getComponents<T>(BaseClass)
58
+ FW.Framework.addRegistry(bundleName, RegistryClass)
59
+ FW.Framework.createRegistry(bundleName)
60
+ FW.Framework.destroyRegistry(bundleName)
61
+ FW.Framework.restart()
62
+ ```
63
+
64
+ 绑定 key 规则:`${bundleName}${FW.SystemDefine.FWBindTag.*}`。
65
+
66
+ 业务代码通常通过 `FW.Entry.getComponent()` 间接使用,不直接操作容器。
67
+
68
+ ## 基类
69
+
70
+ ### `FW.FrameworkBase`
71
+
72
+ 继承者:
73
+
74
+ - `FW.Logic`
75
+ - `FW.Data`
76
+ - `FW.Service`
77
+ - `FW.Sender`
78
+ - `FW.Handle`
79
+ - `FWManager`
80
+ - `FW.LayerController`
81
+
82
+ 可用方法:
83
+
84
+ ```ts
85
+ this.entry
86
+ this.getLogic<T>()
87
+ this.getData<T>()
88
+ this.getConfig<T>()
89
+ this.getSender<T>()
90
+ this.getHandle<T>()
91
+ this.initializeDependencies()
92
+ await this.invoke(promise, "operationName")
93
+ ```
94
+
95
+ 自动注入依赖依赖类名约定:
96
+
97
+ - `*Logic`
98
+ - `*Data`
99
+ - `*Config` 或 `*AssetConfig`
100
+ - `*Sender` 或 `*SocketSender`
101
+ - `*Handle` 或 `*SocketHandle`
102
+
103
+ 类名还必须包含 bundle 名。例如 bundle 为 `shop`,推荐 `ShopLogic`、`ShopData`。
104
+
105
+ ## Registry
106
+
107
+ ### `FW.Registry`
108
+
109
+ ```ts
110
+ abstract readonly bundleName: string;
111
+ readonly autoRelease?: boolean;
112
+ readonly sceneName?: string;
113
+ readonly depend?: string[];
114
+ readonly logic?: FW.Newable<FW.Logic>;
115
+ readonly data?: FW.Newable<FW.Data>;
116
+ readonly config?: FW.Newable<FW.AssetConfig>;
117
+ readonly sender?: FW.Newable<FW.Sender>;
118
+ readonly handle?: FW.Newable<FW.Handle>;
119
+ register(): void;
120
+ unRegister(): void;
121
+ ```
122
+
123
+ 注册表用于把 bundle 元信息和业务组件交给框架。bundle 加载/释放会自动调用。
124
+
125
+ ## Layer API
126
+
127
+ ### `FW.Entry.layerMgr`
128
+
129
+ ```ts
130
+ openAsync<Ctr>({ type, parent?, position?, args? }): Promise<Ctr>
131
+ openSync<Ctr>({ type, parent?, position?, args? }): Ctr
132
+ close(ctr): Promise<Ctr>
133
+ closeFromStack(count?)
134
+ closeFromLayerName(name | name[])
135
+ displayLayer(ctr)
136
+ hideLayer(ctr)
137
+ removeAllChildren(node)
138
+ clear()
139
+ openDebug()
140
+ getLayerMap()
141
+ ```
142
+
143
+ `LayerOpenArgs`:
144
+
145
+ ```ts
146
+ type LayerOpenArgs = {
147
+ type: new () => FW.LayerController;
148
+ parent?: cc.Node;
149
+ position?: { x: number; y: number };
150
+ args?: any;
151
+ };
152
+ ```
153
+
154
+ ### `FW.LayerController`
155
+
156
+ 业务控制器必须实现:
157
+
158
+ ```ts
159
+ renderOrder: FW.SystemDefine.FWLayerRenderOrder;
160
+ layerType: FW.SystemDefine.FWLayerType;
161
+ layer: FW.Layer;
162
+ layerAssetProperty: FW.AssetProperty;
163
+ autoRelease: boolean;
164
+ isRepeatOpen: boolean;
165
+ ```
166
+
167
+ 生命周期:
168
+
169
+ ```ts
170
+ initialize()
171
+ onInit(...args)
172
+ onLoad()
173
+ onStart()
174
+ onEnable()
175
+ onDisable()
176
+ onUpdate(dt)
177
+ onLateUpdate(dt)
178
+ onClose()
179
+ onDestroy()
180
+ destroy()
181
+ ```
182
+
183
+ 便捷方法:
184
+
185
+ ```ts
186
+ find(path, referenceNode)
187
+ findComponent<T>(path, referenceNode, type)
188
+ registerEvent(args)
189
+ cc(targetNode, cb, responseInterval?, eventName?)
190
+ fw(eventName, cb, target?, responseInterval?)
191
+ pauseEvent()
192
+ resumeEvent()
193
+ hide()
194
+ display()
195
+ close()
196
+ ```
197
+
198
+ ## 资源 API
199
+
200
+ ### `FW.AssetProperty`
201
+
202
+ ```ts
203
+ type AssetProperty = {
204
+ resourceId?: number;
205
+ bundle?: string;
206
+ path: string;
207
+ type?: typeof cc.Asset;
208
+ cb?: (asset: FW.AssetData | FW.AssetData[]) => void;
209
+ progress?: (finish: number, total: number, item: cc.AssetManager.RequestItem) => void;
210
+ user?: string;
211
+ autoRelease?: boolean;
212
+ priorityLoaded?: boolean;
213
+ };
214
+ ```
215
+
216
+ ### `FW.Entry.resMgr`
217
+
218
+ ```ts
219
+ loadBundle(bundleName): Promise<cc.AssetManager.Bundle>
220
+ getBundle(bundleName): cc.AssetManager.Bundle
221
+ releaseBundle(bundleName): void
222
+ hasBundle(bundleName): boolean
223
+
224
+ loadAssetData<T>(assetProperty): Promise<FW.AssetData>
225
+ loadAsset<T>(assetProperty, texture?): Promise<T>
226
+ getAssetData<T>(assetProperty, texture?): FW.AssetData
227
+ getAsset<T>(assetProperty, texture?): T
228
+ preLoad(assetProperty | assetProperty[])
229
+ loadDir(assetProperty): Promise<FW.AssetData[]>
230
+ loadRemote<T>(url, options?): Promise<T>
231
+ loadSpineDataFromRemote({ img, ske, atlas, bin? }): Promise<sp.SkeletonData>
232
+ releaseAsset(assetProperty): void
233
+ hasAsset(assetProperty): boolean
234
+ registerRejectHandler(listener): void
235
+ ```
236
+
237
+ 纹理注意:默认 `Texture2D` 会转换为 `SpriteFrame`。需要原始纹理时传 `texture: true`。
238
+
239
+ ## 事件 API
240
+
241
+ ### `FW.Entry.evtMgr`
242
+
243
+ ```ts
244
+ register(eventName, cb, target, options?)
245
+ registerOnce(eventName, cb, target, options?)
246
+ dispatch(eventName, ...args)
247
+ unRegister(eventName, target)
248
+ eventOff(eventName)
249
+ targetOff(target)
250
+ targetPause(target)
251
+ targetResume(target)
252
+ has(eventName, target)
253
+ getListenerCount(eventName)
254
+ ```
255
+
256
+ `options`:
257
+
258
+ ```ts
259
+ {
260
+ priority?: FW.SystemDefine.FWPriorityOrder;
261
+ intercept?: boolean;
262
+ once?: boolean;
263
+ }
264
+ ```
265
+
266
+ ### `FW.Entry.uiMgr`
267
+
268
+ ```ts
269
+ register({ target?, CCEvent?, FWEvent? })
270
+ find(path, rootNode): cc.Node | null
271
+ pauseEvent(target)
272
+ resumeEvent(target)
273
+ unRegisterEvent(args)
274
+ unRegisterTarget(target)
275
+ unRegisterAll()
276
+ hasRegister(eventName, target)
277
+ buttonEnable(target)
278
+ buttonDisable(target)
279
+ ```
280
+
281
+ ## Time API
282
+
283
+ ```ts
284
+ FW.Entry.timeMgr.schedule(cb, intervalSeconds?, repeat?, target?, tag?)
285
+ FW.Entry.timeMgr.scheduleOnce(cbOrTime?, timeOrTargetOrTag?, targetOrTag?)
286
+ FW.Entry.timeMgr.update(cb, tagOrTarget?, target?)
287
+ FW.Entry.timeMgr.pauseSchedule(tag | id | target)
288
+ FW.Entry.timeMgr.resumeSchedule(tag | id | target)
289
+ FW.Entry.timeMgr.unSchedule(tag | id | target)
290
+ FW.Entry.timeMgr.onUpdate(dt)
291
+ ```
292
+
293
+ 返回的 `TimerSchedule` 支持:
294
+
295
+ ```ts
296
+ unSchedule()
297
+ pauseSchedule()
298
+ resumeSchedule()
299
+ promise? // scheduleOnce
300
+ ```
301
+
302
+ ## Promise API
303
+
304
+ ```ts
305
+ const proxy = FW.Entry.promiseMgr.execute(executor, {
306
+ reason,
307
+ timeout,
308
+ retryCount,
309
+ retryInterval,
310
+ retryCondition,
311
+ });
312
+
313
+ proxy.id
314
+ proxy.promise
315
+ proxy.status
316
+ proxy.abort(reason?)
317
+ proxy.addAbortEventListener(listener)
318
+ ```
319
+
320
+ 批量:
321
+
322
+ ```ts
323
+ FW.Entry.promiseMgr.all([proxy1, proxy2], options)
324
+ FW.Entry.promiseMgr.cancel(id, reason?)
325
+ FW.Entry.promiseMgr.cancelMultiple(ids, reason?)
326
+ FW.Entry.promiseMgr.cancelAll(reason?)
327
+ FW.Entry.promiseMgr.getStatus(id)
328
+ FW.Entry.promiseMgr.getAllStatus()
329
+ FW.Entry.promiseMgr.getActiveCount()
330
+ FW.Entry.promiseMgr.clearCompletedPromise()
331
+ ```
332
+
333
+ ## Socket API
334
+
335
+ ### `FW.Entry.socketMgr`
336
+
337
+ ```ts
338
+ createSocket(tag, address, sender, handle, config): Promise<FW.Socket>
339
+ getSocket(tag?): FW.Socket
340
+ getSocketMap(): Map<string, FW.Socket>
341
+ closeSocket(socket)
342
+ closeAll()
343
+ pauseMessageHandle()
344
+ resumeMessageHandle()
345
+ ```
346
+
347
+ ### `FW.Sender`
348
+
349
+ ```ts
350
+ sendHeart?()
351
+ onHeart?(msg): Promise<boolean>
352
+ onBeforeSendingMessage?(msg): Promise<FW.SocketMessage>
353
+ getProtocolKey?(msg): string
354
+ send(msg)
355
+ ```
356
+
357
+ ### `FW.Handle`
358
+
359
+ ```ts
360
+ onMessage(msg): void
361
+ onBeforeReceivingMessage?(msg): Promise<FW.SocketMessage>
362
+ onHeart?(msg): Promise<boolean>
363
+ getProtocolKey?(msg): string
364
+ onOpen?()
365
+ onClose?()
366
+ onError?(msg)
367
+ onTimeout?()
368
+ onWeakNetWork?()
369
+ ```
370
+
371
+ ## Object Pool API
372
+
373
+ ```ts
374
+ const pool = await FW.Entry.objectMgr.createObjectPool(prefabOrNodeOrAsset, parent, tagOrType?)
375
+ FW.Entry.objectMgr.getObjectPool(tag)
376
+ FW.Entry.objectMgr.destroyObjectPool(tag)
377
+ FW.Entry.objectMgr.getPoolStats()
378
+ FW.Entry.objectMgr.clearAllPools()
379
+ ```
380
+
381
+ `FW.ObjectPool`:
382
+
383
+ ```ts
384
+ get(...args): FW.ObjectProperty
385
+ put(node | component | uniqueId)
386
+ puts(array)
387
+ findObjectProperty(node | component | uniqueId)
388
+ isEmpty()
389
+ getEnableObjectMap()
390
+ getDisableObjectMap()
391
+ getAllObjectMap()
392
+ onDestroy()
393
+ ```
394
+
395
+ 池对象组件继承 `FW.Object`,实现 `onInit()`、`onGet()`、`onPut()`、`onDestroy()`。
396
+
397
+ ## 装饰器
398
+
399
+ 全局装饰器由 `initializeFramework()` 挂到 `globalThis`:
400
+
401
+ ```ts
402
+ @FWPropertyNode({ path? })
403
+ @FWPropertyNodes(...paths)
404
+ @FWPropertyComponent(ComponentClass, childName?, mute?)
405
+ @FWPropertyComponents(ComponentClass, childName?)
406
+ @PerformanceMonitor(operationName?)
407
+ @FWDeprecated(description?)
408
+ @FWSocketAutoProcessPause()
409
+ @FWSocketAutoProcessResume()
410
+ @AutoRegisterCCEvent(nodePath, eventName?)
411
+ @AutoRegisterFWEvent(eventName)
412
+ ```
413
+
414
+ 装饰器会改写 `onLoad` 或方法 descriptor。使用时必须确认目标类生命周期和路径稳定。
@@ -0,0 +1,6 @@
1
+ {
2
+ "ver": "2.0.2",
3
+ "uuid": "66b8acf8-acdf-43e2-a401-8b9afa24b01c",
4
+ "importer": "markdown",
5
+ "subMetas": {}
6
+ }