@kkarum/framework 2.3.17 → 2.3.19

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,127 @@
1
+ # Framework Overview
2
+
3
+ 本文档说明 `assets/framework` 的整体设计,面向人类开发者、Codex,以及使用 cocos-mcp 做 UI 自动化的 AI 工作流。
4
+
5
+ ## 核心定位
6
+
7
+ 该框架是 Cocos Creator 2.x 项目的业务基础层,统一管理:
8
+
9
+ - 全局入口:`FW.Entry`
10
+ - 依赖容器:`FW.Framework`
11
+ - Bundle 注册:`FW.Registry`
12
+ - 业务组件:`FW.Logic`、`FW.Data`、`FW.AssetConfig`、`FW.Sender`、`FW.Handle`
13
+ - UI 层级:`FW.Layer`、`FW.LayerController`、`FW.Entry.layerMgr`
14
+ - 资源、事件、时间、Promise、Socket、对象池、语言、任务、性能等管理器
15
+
16
+ 框架启动后会在 `window.FW` 上挂载全局命名空间,并把常用装饰器挂到 `globalThis`。业务代码应通过 `FW.Entry` 和继承基类来使用框架能力。
17
+
18
+ ## 初始化链路
19
+
20
+ 入口函数是 `initializeFramework()`:
21
+
22
+ 1. 创建 `window.FW`。
23
+ 2. 注入系统枚举和事件枚举:`FW.SystemDefine`、`FW.EventDefine`。
24
+ 3. 挂载核心类和单例:`FW.Framework`、`FW.Entry`、`FW.Log` 等。
25
+ 4. 调用 `FW.Framework.initialize()` 初始化 Inversify 容器。
26
+ 5. 调用 `FW.Entry.initialize()` 初始化所有管理器。
27
+
28
+ 初始化是幂等的:如果 `FW.Framework` 和 `FW.Entry` 已存在,会直接返回。
29
+
30
+ ## 架构分层
31
+
32
+ ### Framework
33
+
34
+ `FW.Framework` 负责依赖注入和业务组件生命周期:
35
+
36
+ - 使用 Inversify `Container`,默认单例。
37
+ - `register(data)` 绑定业务 bundle 的 Logic/Data/Config/Sender/Handle。
38
+ - `getComponent()` 通过类、字符串 key 或当前 `FW.Entry.bundleName` 的命名约定获取组件。
39
+ - `restart()` 会销毁所有组件、重建容器、重新注册已有组件。
40
+
41
+ ### Entry
42
+
43
+ `FW.Entry` 是所有业务代码的统一入口。常用字段:
44
+
45
+ - `resMgr`:资源与 bundle。
46
+ - `layerMgr`:UI Layer 打开、关闭、队列、栈。
47
+ - `uiMgr`:节点查找、UI 事件注册、按钮禁用。
48
+ - `evtMgr`:框架事件总线。
49
+ - `timeMgr`:统一定时器和 update。
50
+ - `promiseMgr`:可取消、超时、重试的 Promise。
51
+ - `socketMgr`:Socket 连接管理。
52
+ - `objectMgr`:对象池。
53
+ - `taskMgr`:分帧异步任务。
54
+ - `performanceMgr`:性能统计。
55
+
56
+ 业务代码不要自行 new 管理器,不要绕过 `FW.Entry` 调用底层实现。
57
+
58
+ ### Registry
59
+
60
+ 每个业务 bundle 应有一个继承 `FW.Registry` 的注册类,声明:
61
+
62
+ - `bundleName`
63
+ - `sceneName`
64
+ - `depend`
65
+ - `autoRelease`
66
+ - `logic`
67
+ - `data`
68
+ - `config`
69
+ - `sender`
70
+ - `handle`
71
+
72
+ bundle 加载时,`FWBundleManager.load()` 会调用 `FW.Framework.createRegistry(bundleName)?.register()`。bundle 释放时会调用注册表 `unRegister()` 并销毁注册表实例。
73
+
74
+ ### FrameworkBase
75
+
76
+ `FW.FrameworkBase` 是业务组件和管理器基类,提供:
77
+
78
+ - `entry` 快捷访问 `FW.Entry`。
79
+ - `getLogic()`、`getData()`、`getConfig()`、`getSender()`、`getHandle()`。
80
+ - 自动依赖注入:根据类名包含的 bundle 名和后缀 `Logic/Data/Config/Sender/Handle` 推断依赖。
81
+ - `invoke(operation, operationName)`:包装异步操作,记录性能,统一错误日志。
82
+ - `onRestart()` 钩子:构造时注册到系统重启事件。
83
+
84
+ 新增业务组件必须继承对应基类,命名必须保留后缀,否则依赖注入可能失败。
85
+
86
+ ## UI 模型
87
+
88
+ UI 的核心单位是 Layer:
89
+
90
+ - 预制体节点上挂 `FW.Layer` 或继承组件。
91
+ - 控制器继承 `FW.LayerController`。
92
+ - 控制器通过 `layerAssetProperty` 指向 prefab。
93
+ - 业务通过 `FW.Entry.layerMgr.openAsync({ type, parent, position, args })` 打开。
94
+
95
+ LayerController 负责业务生命周期、事件绑定、节点查找和关闭。不要在普通组件中自行 `cc.instantiate` 业务弹窗,除非它不是框架 Layer。
96
+
97
+ ## 资源模型
98
+
99
+ 所有业务资源应通过 `FW.AssetConfig` 声明,并通过 `FW.Entry.resMgr` 加载:
100
+
101
+ - `preLoad`:随 bundle 或依赖优先加载的资源。
102
+ - `demandLoad`:按需加载的资源。
103
+ - `AssetProperty` 至少包含 `path`,通常包含 `bundle` 和 `type`。
104
+
105
+ `FWResManager` 会递归加载 bundle 依赖,并优先加载 `preLoad` 中 `priorityLoaded: true` 的资源。
106
+
107
+ ## 事件模型
108
+
109
+ 框架事件分两类:
110
+
111
+ - Cocos 节点事件:通过 `FW.Entry.uiMgr.register()` 或 `LayerController.cc()` 注册。
112
+ - 框架事件:通过 `FW.Entry.evtMgr.register()`、`dispatch()` 或 `LayerController.fw()` 注册。
113
+
114
+ Layer 销毁时框架会清理控制器的定时器和事件。新增代码应避免直接散落 `node.on`,优先走框架注册。
115
+
116
+ ## Codex 开发原则
117
+
118
+ Codex 新增业务功能时必须先判断功能属于哪个层:
119
+
120
+ - 页面、弹窗、常驻面板:新增 `LayerController` 和 prefab。
121
+ - 业务状态或流程:新增或扩展 `Logic`。
122
+ - 可变业务数据:放入 `Data`。
123
+ - 资源地址:放入 `AssetConfig`。
124
+ - 网络协议发送:放入 `Sender`。
125
+ - 网络消息处理:放入 `Handle`。
126
+
127
+ 不要修改 `assets/framework` 内部源码来适配业务需求。优先新增业务 bundle 代码,复用 `FW.Entry` 已提供能力。
@@ -0,0 +1,6 @@
1
+ {
2
+ "ver": "2.0.2",
3
+ "uuid": "abc3993f-769f-4afa-8cae-057e52d49298",
4
+ "importer": "markdown",
5
+ "subMetas": {}
6
+ }
@@ -0,0 +1,261 @@
1
+ # Framework Patterns
2
+
3
+ 本文档定义新增业务功能时的推荐模式和禁止事项。Codex 必须优先遵守本文档。
4
+
5
+ ## 模块边界
6
+
7
+ 业务功能按职责拆分:
8
+
9
+ | 职责 | 放置位置 |
10
+ | --- | --- |
11
+ | 页面/弹窗生命周期 | `FW.LayerController` |
12
+ | 业务流程 | `FW.Logic` |
13
+ | 业务数据 | `FW.Data` |
14
+ | 资源声明 | `FW.AssetConfig` |
15
+ | Socket 发送包装 | `FW.Sender` |
16
+ | Socket 接收分发 | `FW.Handle` |
17
+ | 跨模块事件 | `FW.Entry.evtMgr` |
18
+ | UI 节点事件 | `LayerController.cc()` 或 `registerEvent()` |
19
+
20
+ 禁止把所有逻辑堆进 `LayerController`。控制器只协调 UI、事件和调用 Logic。
21
+
22
+ ## 命名约定
23
+
24
+ 必须保留后缀:
25
+
26
+ - `<Bundle>Logic`
27
+ - `<Bundle>Data`
28
+ - `<Bundle>AssetConfig`
29
+ - `<Bundle>Sender`
30
+ - `<Bundle>Handle`
31
+ - `<Feature>LayerController`
32
+
33
+ 原因:`FrameworkBase` 根据类名推断 bundle 和组件类型。类名不含 bundle 名或后缀错误会导致 `getLogic()` 等依赖为空。
34
+
35
+ ## 新功能实现步骤
36
+
37
+ 1. 查找现有 bundle 和功能目录,优先复用已有结构。
38
+ 2. 如果是新 bundle,新增 Registry、Logic、Data、AssetConfig。
39
+ 3. 如果需要 UI,先定义 LayerController,再创建 prefab。
40
+ 4. 所有 prefab 资源写入 AssetConfig。
41
+ 5. UI 打开走 `FW.Entry.layerMgr.openAsync()`。
42
+ 6. 节点事件走 `this.cc()` 或 `registerEvent()`。
43
+ 7. 跨模块通知走 `FW.Entry.evtMgr.dispatch()`。
44
+ 8. 网络走 Sender/Handle,不在 UI 里直接拼 WebSocket。
45
+ 9. 异步和资源错误使用 `invoke()`、`promiseMgr`、`FW.Log`。
46
+
47
+ ## LayerController 模式
48
+
49
+ 推荐:
50
+
51
+ ```ts
52
+ export class RewardLayerController extends FW.LayerController {
53
+ renderOrder = FW.SystemDefine.FWLayerRenderOrder.POPUP;
54
+ layerType = FW.SystemDefine.FWLayerType.POPUP_QUEUE;
55
+ autoRelease = true;
56
+ isRepeatOpen = false;
57
+
58
+ get layerAssetProperty(): FW.AssetProperty {
59
+ return this.getConfig<RewardAssetConfig>().preLoad.prefab["RewardLayer"] as FW.AssetProperty;
60
+ }
61
+
62
+ onInit(args: RewardArgs) {
63
+ this.bindNodes();
64
+ this.bindEvents();
65
+ this.render(args);
66
+ }
67
+
68
+ private bindNodes() {}
69
+
70
+ private bindEvents() {
71
+ this.cc(this.find("ConfirmButton", this.layer.node), this.onConfirm);
72
+ }
73
+
74
+ private onConfirm = () => {
75
+ this.close();
76
+ };
77
+ }
78
+ ```
79
+
80
+ 要点:
81
+
82
+ - `onInit()` 接收 `openAsync({ args })` 传入的数据。
83
+ - 节点查找基于 `this.layer.node`。
84
+ - 不在构造函数里访问节点。
85
+ - `POPUP_QUEUE` 会在已有队列弹窗打开时排队。
86
+ - `PERMANENT` 可用 `hide()`/`display()` 控制透明度和事件。
87
+
88
+ ## Logic/Data 模式
89
+
90
+ 推荐:
91
+
92
+ ```ts
93
+ export class MailLogic extends FW.Logic {
94
+ async requestMailList() {
95
+ const msg = await this.invoke(
96
+ this.getSender<MailSender>().requestMailList(),
97
+ "requestMailList",
98
+ );
99
+ this.getData<MailData>().setMailList(msg.list);
100
+ }
101
+ }
102
+ ```
103
+
104
+ ```ts
105
+ export class MailData extends FW.Data {
106
+ private list: MailItem[] = [];
107
+
108
+ setMailList(list: MailItem[]) {
109
+ this.list = list || [];
110
+ FW.Entry.evtMgr.dispatch("MAIL_LIST_CHANGED", this.list);
111
+ }
112
+
113
+ getMailList() {
114
+ return this.list;
115
+ }
116
+ }
117
+ ```
118
+
119
+ 要点:
120
+
121
+ - Logic 发起流程。
122
+ - Data 保持状态。
123
+ - UI 监听状态变化。
124
+
125
+ ## 资源模式
126
+
127
+ 所有业务资源用 `AssetProperty` 声明:
128
+
129
+ ```ts
130
+ readonly preLoad = {
131
+ prefab: {
132
+ MailLayer: {
133
+ bundle: "mail",
134
+ path: "prefab/MailLayer",
135
+ type: cc.Prefab,
136
+ priorityLoaded: true,
137
+ autoRelease: true,
138
+ },
139
+ },
140
+ };
141
+ ```
142
+
143
+ 使用规则:
144
+
145
+ - `openSync()` 只用于确定资源已加载的 Layer。
146
+ - 默认使用 `openAsync()`。
147
+ - 资源获取前先加载;`getAsset()` 不会自动加载。
148
+ - 自动释放资源时确认 `autoRelease` 语义。
149
+
150
+ ## 事件模式
151
+
152
+ 节点事件:
153
+
154
+ ```ts
155
+ this.cc(buttonNode, this.onClick, 500);
156
+ ```
157
+
158
+ 框架事件:
159
+
160
+ ```ts
161
+ this.fw("MAIL_LIST_CHANGED", this.renderList);
162
+ ```
163
+
164
+ 全局注册:
165
+
166
+ ```ts
167
+ FW.Entry.evtMgr.register("PLAYER_LEVEL_UP", this.onLevelUp, this);
168
+ ```
169
+
170
+ 清理:
171
+
172
+ ```ts
173
+ FW.Entry.evtMgr.targetOff(this);
174
+ FW.Entry.timeMgr.unSchedule(this);
175
+ ```
176
+
177
+ LayerController 通常由框架销毁流程清理,普通对象必须自己清理。
178
+
179
+ ## 异步模式
180
+
181
+ 推荐用 `invoke()`:
182
+
183
+ ```ts
184
+ const data = await this.invoke(
185
+ FW.Entry.resMgr.loadAssetData(asset),
186
+ "loadRewardIcon",
187
+ );
188
+ ```
189
+
190
+ 需要取消/超时/重试:
191
+
192
+ ```ts
193
+ const proxy = FW.Entry.promiseMgr.execute(resolveWork, {
194
+ timeout: 10000,
195
+ retryCount: 2,
196
+ retryInterval: 1,
197
+ });
198
+ ```
199
+
200
+ 不要吞掉错误。`invoke()` 返回 `undefined` 时调用方必须处理空值。
201
+
202
+ ## 对象池模式
203
+
204
+ 适用于列表项、飘字、特效对象:
205
+
206
+ ```ts
207
+ const pool = await FW.Entry.objectMgr.createObjectPool(prefab, container, "reward_item");
208
+ const item = pool.get(data);
209
+ pool.put(item.node);
210
+ ```
211
+
212
+ 池对象继承 `FW.Object`:
213
+
214
+ ```ts
215
+ export class RewardItem extends FW.Object {
216
+ onInit() {}
217
+ onGet(args: any[]) {}
218
+ onPut() {}
219
+ }
220
+ ```
221
+
222
+ ## 禁止事项
223
+
224
+ - 不修改 `assets/framework` 内部代码来实现业务需求。
225
+ - 不在业务代码中 new 框架管理器。
226
+ - 不绕过 `FW.Entry.resMgr` 直接散落 `cc.loader` 或 `cc.assetManager.loadBundle`。
227
+ - 不绕过 `FW.Entry.layerMgr` 手动实例化业务 Layer prefab。
228
+ - 不在 UI 控制器里直接维护网络连接。
229
+ - 不在 `Data` 中操作节点或发起网络请求。
230
+ - 不在构造函数中访问 Cocos 节点。
231
+ - 不使用没有 bundle 名和后缀的业务组件类名。
232
+ - 不直接使用 `setTimeout`/`setInterval` 管理游戏内生命周期定时器。
233
+ - 不让 `node.on` 分散在多个生命周期中且没有清理路径。
234
+
235
+ ## Codex 决策表
236
+
237
+ 当用户要求“新增一个页面”:
238
+
239
+ - 先创建/定位 prefab。
240
+ - 创建 `LayerController`。
241
+ - `layerAssetProperty` 指向 Config 中的 prefab。
242
+ - 用 cocos-mcp 创建 UI 节点。
243
+ - 用 `cc()` 绑定按钮事件。
244
+
245
+ 当用户要求“新增一个接口”:
246
+
247
+ - 修改或新增 `Sender` 方法。
248
+ - 在 `Handle.onMessage()` 或协议分发中处理返回。
249
+ - 写入 `Data`。
250
+ - 派发事件通知 UI。
251
+
252
+ 当用户要求“新增列表”:
253
+
254
+ - 小列表可普通节点。
255
+ - 大列表优先 `FWVirtualViewComponent` 或对象池。
256
+ - item 预制体资源写入 Config。
257
+
258
+ 当用户要求“切场景”:
259
+
260
+ - 使用 `FW.Entry.launchScene(bundleName, forceRelease?)`。
261
+ - 确认 Registry 中 `sceneName` 已配置。
@@ -0,0 +1,6 @@
1
+ {
2
+ "ver": "2.0.2",
3
+ "uuid": "41fff980-7e94-4a19-ad9e-d35b17048068",
4
+ "importer": "markdown",
5
+ "subMetas": {}
6
+ }
@@ -0,0 +1,309 @@
1
+ # Framework Usage
2
+
3
+ 本文档给出业务开发时的实际用法和推荐流程。
4
+
5
+ ## 基本规则
6
+
7
+ - 所有框架能力从 `FW.Entry` 进入。
8
+ - 业务组件继承框架基类:`FW.Logic`、`FW.Data`、`FW.AssetConfig`、`FW.Sender`、`FW.Handle`、`FW.LayerController`。
9
+ - 每个 bundle 使用 `FW.Registry` 注册自身组件。
10
+ - 异步资源走 `FW.Entry.resMgr`。
11
+ - UI 打开关闭走 `FW.Entry.layerMgr`。
12
+ - 事件绑定走 `LayerController.registerEvent()`、`cc()`、`fw()` 或 `FW.Entry.evtMgr`。
13
+ - 定时器走 `FW.Entry.timeMgr`。
14
+ - 可取消/带重试异步流程走 `FW.Entry.promiseMgr`。
15
+
16
+ ## 新增业务 Bundle
17
+
18
+ 推荐目录结构:
19
+
20
+ ```text
21
+ assets/<bundle>/
22
+ config/<Bundle>AssetConfig.ts
23
+ data/<Bundle>Data.ts
24
+ logic/<Bundle>Logic.ts
25
+ registry/<Bundle>Registry.ts
26
+ ui/<Feature>/<Feature>LayerController.ts
27
+ prefab/<Feature>Layer.prefab
28
+ ```
29
+
30
+ 注册类示例:
31
+
32
+ ```ts
33
+ import { FWRegistry } from "../../framework/registry/FWRegistry";
34
+ import { ShopLogic } from "../logic/ShopLogic";
35
+ import { ShopData } from "../data/ShopData";
36
+ import { ShopAssetConfig } from "../config/ShopAssetConfig";
37
+
38
+ export class ShopRegistry extends FWRegistry {
39
+ readonly bundleName = "shop";
40
+ readonly sceneName = "ShopScene";
41
+ readonly depend = ["common"];
42
+ readonly autoRelease = true;
43
+ readonly logic = ShopLogic;
44
+ readonly data = ShopData;
45
+ readonly config = ShopAssetConfig;
46
+ }
47
+ ```
48
+
49
+ 业务组件命名必须包含 bundle 名,并以 `Logic/Data/Config/Sender/Handle` 结尾。`FrameworkBase.initializeDependencies()` 会按类名推断 bundle 和组件类型。
50
+
51
+ ## Logic
52
+
53
+ `Logic` 放业务流程,不持有复杂 UI 节点引用。
54
+
55
+ ```ts
56
+ export class ShopLogic extends FW.Logic {
57
+ initialize() {
58
+ // 初始化轻量状态,避免在构造阶段访问尚未加载的资源节点。
59
+ }
60
+
61
+ async refreshGoods() {
62
+ return this.invoke(
63
+ this.getSender<ShopSender>().sendGetGoods(),
64
+ "refreshGoods",
65
+ );
66
+ }
67
+ }
68
+ ```
69
+
70
+ 规范:
71
+
72
+ - 异步流程用 `this.invoke()` 包装。
73
+ - 业务数据写入 `this.getData<ShopData>()`。
74
+ - UI 通知用 `FW.Entry.evtMgr.dispatch()`。
75
+ - 不直接加载 prefab 打开 UI,应调用 `FW.Entry.layerMgr`。
76
+
77
+ ## Data
78
+
79
+ `Data` 保存业务状态,避免混入 UI 节点。
80
+
81
+ ```ts
82
+ export class ShopData extends FW.Data {
83
+ goods: GoodsItem[] = [];
84
+
85
+ setGoods(list: GoodsItem[]) {
86
+ this.goods = list || [];
87
+ FW.Entry.evtMgr.dispatch("SHOP_GOODS_CHANGED", this.goods);
88
+ }
89
+ }
90
+ ```
91
+
92
+ 规范:
93
+
94
+ - `Data` 不发起网络请求。
95
+ - `Data` 不操作 Cocos 节点。
96
+ - 对外提供明确方法,不鼓励外部到处直接改字段。
97
+
98
+ ## AssetConfig
99
+
100
+ 资源地址统一放在 Config。
101
+
102
+ ```ts
103
+ export class ShopAssetConfig extends FW.AssetConfig {
104
+ preLoad: FW.LoadConfig = {
105
+ prefab: {
106
+ ShopLayer: {
107
+ bundle: "shop",
108
+ path: "prefab/ShopLayer",
109
+ type: cc.Prefab,
110
+ priorityLoaded: true,
111
+ autoRelease: true,
112
+ },
113
+ },
114
+ };
115
+
116
+ demandLoad: FW.LoadConfig = {
117
+ texture: {
118
+ IconCoin: {
119
+ bundle: "shop",
120
+ path: "texture/icon_coin",
121
+ type: cc.Texture2D,
122
+ },
123
+ },
124
+ };
125
+ }
126
+ ```
127
+
128
+ 使用:
129
+
130
+ ```ts
131
+ const cfg = FW.Entry.getComponent<ShopAssetConfig>(ShopAssetConfig);
132
+ const prefab = await FW.Entry.resMgr.loadAsset<cc.Prefab>(
133
+ cfg.preLoad.prefab["ShopLayer"] as FW.AssetProperty,
134
+ );
135
+ ```
136
+
137
+ ## LayerController
138
+
139
+ UI 控制器示例:
140
+
141
+ ```ts
142
+ export class ShopLayerController extends FW.LayerController {
143
+ renderOrder = FW.SystemDefine.FWLayerRenderOrder.UI;
144
+ layerType = FW.SystemDefine.FWLayerType.REPEAT;
145
+ autoRelease = true;
146
+ isRepeatOpen = false;
147
+
148
+ get layerAssetProperty(): FW.AssetProperty {
149
+ return this.getConfig<ShopAssetConfig>().preLoad.prefab["ShopLayer"] as FW.AssetProperty;
150
+ }
151
+
152
+ onInit(args?: { tab?: string }) {
153
+ const closeBtn = this.find("CloseButton", this.layer.node);
154
+ this.cc(closeBtn, this.onCloseClick);
155
+ this.fw("SHOP_GOODS_CHANGED", this.renderGoods);
156
+ }
157
+
158
+ private onCloseClick = () => {
159
+ this.close();
160
+ };
161
+
162
+ private renderGoods = (goods: GoodsItem[]) => {
163
+ // 更新 UI
164
+ };
165
+ }
166
+ ```
167
+
168
+ 打开:
169
+
170
+ ```ts
171
+ await FW.Entry.layerMgr.openAsync<ShopLayerController>({
172
+ type: ShopLayerController,
173
+ args: { tab: "hot" },
174
+ });
175
+ ```
176
+
177
+ ## 资源加载
178
+
179
+ ```ts
180
+ const sf = await FW.Entry.resMgr.loadAsset<cc.SpriteFrame>({
181
+ bundle: "shop",
182
+ path: "texture/icon_coin",
183
+ type: cc.Texture2D,
184
+ });
185
+ ```
186
+
187
+ 注意:
188
+
189
+ - 传入 `cc.Texture2D` 时,`FWResManager` 默认会包装成 `cc.SpriteFrame`。
190
+ - 如果只要原始 Texture,使用 `loadAsset(assetProperty, true)`。
191
+ - 同步 `getAsset()` 只能用于已加载资源,否则会报错。
192
+
193
+ ## 事件注册
194
+
195
+ 优先使用控制器便捷方法:
196
+
197
+ ```ts
198
+ this.cc(this.find("BuyButton", this.layer.node), this.onBuyClick);
199
+ this.fw("SHOP_GOODS_CHANGED", this.renderGoods);
200
+ ```
201
+
202
+ 复杂事件:
203
+
204
+ ```ts
205
+ this.registerEvent({
206
+ CCEvent: [{
207
+ target: buyButton,
208
+ eventName: cc.Node.EventType.TOUCH_END,
209
+ responseInterval: 500,
210
+ cb: this.onBuyClick,
211
+ }],
212
+ FWEvent: [{
213
+ eventName: "SHOP_REFRESH",
214
+ responseInterval: 200,
215
+ cb: this.onRefresh,
216
+ }],
217
+ });
218
+ ```
219
+
220
+ 销毁时 LayerController 会清理自身事件,普通对象必须调用 `FW.Entry.evtMgr.targetOff(target)` 或通过框架管理生命周期。
221
+
222
+ ## 定时器
223
+
224
+ ```ts
225
+ const schedule = FW.Entry.timeMgr.scheduleOnce(() => {
226
+ this.refreshGoods();
227
+ }, 1, this);
228
+
229
+ FW.Entry.timeMgr.schedule(
230
+ () => this.tick(),
231
+ 0.5,
232
+ cc.macro.REPEAT_FOREVER,
233
+ this,
234
+ "shop_tick",
235
+ );
236
+
237
+ FW.Entry.timeMgr.unSchedule(this);
238
+ ```
239
+
240
+ 不要直接使用 `setTimeout`、`setInterval` 管理业务定时器。
241
+
242
+ ## Promise
243
+
244
+ ```ts
245
+ const proxy = FW.Entry.promiseMgr.execute<string>((resolve, reject, signal) => {
246
+ // 异步操作
247
+ signal.addEventListener("abort", () => {
248
+ // 清理
249
+ });
250
+ }, {
251
+ timeout: 10000,
252
+ retryCount: 3,
253
+ retryInterval: 2,
254
+ });
255
+
256
+ const result = await proxy.promise;
257
+ ```
258
+
259
+ 需要取消时:
260
+
261
+ ```ts
262
+ proxy.abort("layer closed");
263
+ ```
264
+
265
+ ## Socket
266
+
267
+ 业务 bundle 可注册 `Sender` 和 `Handle`:
268
+
269
+ ```ts
270
+ export class GameSender extends FW.Sender {
271
+ sendHeart() {
272
+ this.send(JSON.stringify({ cmd: "heart" }));
273
+ }
274
+
275
+ async onHeart(msg: any) {
276
+ return JSON.parse(msg).cmd === "heart";
277
+ }
278
+
279
+ async onBeforeSendingMessage(msg: any) {
280
+ return typeof msg === "string" ? msg : JSON.stringify(msg);
281
+ }
282
+
283
+ getProtocolKey(msg: any) {
284
+ return JSON.parse(msg).seq;
285
+ }
286
+ }
287
+ ```
288
+
289
+ ```ts
290
+ export class GameHandle extends FW.Handle {
291
+ async onBeforeReceivingMessage(event: MessageEvent) {
292
+ return JSON.parse(event.data);
293
+ }
294
+
295
+ onMessage(msg: any) {
296
+ FW.Entry.evtMgr.dispatch(`SOCKET_${msg.cmd}`, msg);
297
+ }
298
+
299
+ async onHeart(msg: any) {
300
+ return msg.cmd === "heart";
301
+ }
302
+
303
+ getProtocolKey(msg: any) {
304
+ return msg.seq;
305
+ }
306
+ }
307
+ ```
308
+
309
+ 创建连接走 `FW.Entry.socketMgr.createSocket()`,不要直接 new `WebSocket`。
@@ -0,0 +1,6 @@
1
+ {
2
+ "ver": "2.0.2",
3
+ "uuid": "6a4c7da4-306c-4eea-9d3b-aa499018e592",
4
+ "importer": "markdown",
5
+ "subMetas": {}
6
+ }