@fenglimg/cocos-state-controller 0.1.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.
Files changed (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +287 -0
  3. package/assets/script/controller/Capability.ts +100 -0
  4. package/assets/script/controller/Capability.ts.meta +10 -0
  5. package/assets/script/controller/CapabilityRegistry.ts +116 -0
  6. package/assets/script/controller/CapabilityRegistry.ts.meta +10 -0
  7. package/assets/script/controller/EnumPropRefMap.ts +232 -0
  8. package/assets/script/controller/EnumPropRefMap.ts.meta +10 -0
  9. package/assets/script/controller/NestedCtrlData.ts +199 -0
  10. package/assets/script/controller/NestedCtrlData.ts.meta +10 -0
  11. package/assets/script/controller/PrefabIntrospection.ts +151 -0
  12. package/assets/script/controller/PrefabIntrospection.ts.meta +10 -0
  13. package/assets/script/controller/Props.meta +13 -0
  14. package/assets/script/controller/StateControllerV2.ts +1957 -0
  15. package/assets/script/controller/StateControllerV2.ts.meta +10 -0
  16. package/assets/script/controller/StateEnumV2.ts +165 -0
  17. package/assets/script/controller/StateEnumV2.ts.meta +10 -0
  18. package/assets/script/controller/StateErrorManagerV2.ts +217 -0
  19. package/assets/script/controller/StateErrorManagerV2.ts.meta +10 -0
  20. package/assets/script/controller/StatePropHandlerV2.ts +316 -0
  21. package/assets/script/controller/StatePropHandlerV2.ts.meta +10 -0
  22. package/assets/script/controller/StatePropertyControlService.ts +148 -0
  23. package/assets/script/controller/StatePropertyControlService.ts.meta +10 -0
  24. package/assets/script/controller/StateSelectV2.ts +4542 -0
  25. package/assets/script/controller/StateSelectV2.ts.meta +10 -0
  26. package/assets/script/controller/capabilities/AutoSyncCapability.ts +30 -0
  27. package/assets/script/controller/capabilities/AutoSyncCapability.ts.meta +10 -0
  28. package/assets/script/controller/capabilities/EventCapability.ts +144 -0
  29. package/assets/script/controller/capabilities/EventCapability.ts.meta +10 -0
  30. package/assets/script/controller/capabilities/MigrationCapability.ts +94 -0
  31. package/assets/script/controller/capabilities/MigrationCapability.ts.meta +10 -0
  32. package/assets/script/controller/capabilities/MultiCtrlBindingCapability.ts +157 -0
  33. package/assets/script/controller/capabilities/MultiCtrlBindingCapability.ts.meta +10 -0
  34. package/assets/script/controller/capabilities/PropertyControlCapability.ts +124 -0
  35. package/assets/script/controller/capabilities/PropertyControlCapability.ts.meta +10 -0
  36. package/assets/script/controller/capabilities/RecordingCapability.ts +69 -0
  37. package/assets/script/controller/capabilities/RecordingCapability.ts.meta +10 -0
  38. package/assets/script/controller/capabilities/SelectedPageIdCapability.ts +88 -0
  39. package/assets/script/controller/capabilities/SelectedPageIdCapability.ts.meta +10 -0
  40. package/assets/script/controller/capabilities.meta +13 -0
  41. package/assets/script/controller/props/CtrlInspectorGroups.ts +138 -0
  42. package/assets/script/controller/props/CtrlInspectorGroups.ts.meta +10 -0
  43. package/assets/script/controller/props/SelectInspectorGroups.ts +104 -0
  44. package/assets/script/controller/props/SelectInspectorGroups.ts.meta +10 -0
  45. package/bin/csc.js +286 -0
  46. package/package.json +60 -0
  47. package/packages/state-controller-v2-panel/README.md +80 -0
  48. package/packages/state-controller-v2-panel/inspector-inject.js +917 -0
  49. package/packages/state-controller-v2-panel/inspector-probe.json +3767 -0
  50. package/packages/state-controller-v2-panel/lib/handlers.js +534 -0
  51. package/packages/state-controller-v2-panel/main.js +149 -0
  52. package/packages/state-controller-v2-panel/package.json +32 -0
  53. package/packages/state-controller-v2-panel/panel/build.js +23 -0
  54. package/packages/state-controller-v2-panel/panel/logic.js +1207 -0
  55. package/packages/state-controller-v2-panel/panel/styles.css +454 -0
  56. package/packages/state-controller-v2-panel/panel/template.html +296 -0
  57. package/packages/state-controller-v2-panel/scene-accessor.js +657 -0
  58. package/skills/cocos-state-controller/SKILL.md +28 -0
  59. package/skills/cocos-state-controller/refs/cli-usage.md +78 -0
  60. package/skills/cocos-state-controller/refs/editor-guide.md +127 -0
  61. package/skills/cocos-state-controller/refs/migrate.md +106 -0
  62. package/skills/cocos-state-controller/refs/upstream-pr.md +66 -0
  63. package/tools/migration/migrate-prefab-v1-to-v2.js +608 -0
  64. package/tools/state-controller-sync-manifest.json +33 -0
@@ -0,0 +1,232 @@
1
+ /**
2
+ * W6-2a EnumPropName ↔ propRef 双向映射 (内部边界用).
3
+ *
4
+ * W6-2a 内部 Recording 计算路径走 propRef 字符串 (与 W6-1 PrefabIntrospection.listTrackableProps
5
+ * 返回值同 shape), 但外部 API 签名仍是 EnumPropName 数字 (togglePropertyControl 等). 双 key 存储:
6
+ * - 内置 prop (能映射到 EnumPropName 数字) → ctrlData[ctrlId][state][数字 key]
7
+ * - 自定义 prop (无映射) → ctrlData[ctrlId][state][string propRef key]
8
+ *
9
+ * 映射数据源: assets/script/controller/StatePropHandler.ts 的 register(...) 调用. 每条 enum 映射到
10
+ * (getValue/setValue) 实际操作的 cc.Node 字段或 (cc.Component, fieldName).
11
+ *
12
+ * AMBIGUOUS 项 (复合字段, 无法一一映射): Position (x/y/z), Anchor (anchorX/anchorY),
13
+ * Size (width/height), GrayScale (材质 stub) — 这些 EnumPropName 仍走老路径 (写数字 key),
14
+ * 不在本映射表里. 自定义 prop 走 string key 路径不受影响.
15
+ */
16
+
17
+ import { EnumPropName } from "./StateEnumV2";
18
+
19
+ /**
20
+ * EnumPropName 数字 → propRef 字符串.
21
+ * 注意: 不含 AMBIGUOUS 项. listEnumMappablePropRefs() 用此表过滤已被内置路径覆盖的 propRef.
22
+ */
23
+ export const ENUM_TO_PROPREF: { [enumVal: number]: string } = {
24
+ // ---- cc.Node 内置字段 ----
25
+ [EnumPropName.Active]: "cc.Node.active",
26
+ [EnumPropName.Color]: "cc.Node.color",
27
+ [EnumPropName.Opacity]: "cc.Node.opacity",
28
+ [EnumPropName.Euler]: "cc.Node.eulerAngles",
29
+ [EnumPropName.Scale]: "cc.Node.scale",
30
+
31
+ // ---- cc.Label ----
32
+ [EnumPropName.LabelString]: "cc.Label.string",
33
+ [EnumPropName.LabelFontSize]: "cc.Label.fontSize",
34
+ [EnumPropName.LabelLineHeight]: "cc.Label.lineHeight",
35
+ [EnumPropName.LabelSpacingX]: "cc.Label.spacingX",
36
+ [EnumPropName.LabelWrapEnable]: "cc.Label.enableWrapText",
37
+ [EnumPropName.Font]: "cc.Label.font",
38
+
39
+ // ---- cc.LabelOutline ----
40
+ [EnumPropName.LabelOutlineColor]: "cc.LabelOutline.color",
41
+
42
+ // ---- cc.Sprite ----
43
+ [EnumPropName.SpriteFrame]: "cc.Sprite.spriteFrame",
44
+ [EnumPropName.SpriteFillRange]: "cc.Sprite.fillRange",
45
+
46
+ // ---- 其他单字段组件 ----
47
+ [EnumPropName.SliderProgress]: "cc.Slider.progress",
48
+ [EnumPropName.EditboxString]: "cc.EditBox.string",
49
+ [EnumPropName.ButtonInteractable]: "cc.Button.interactable",
50
+ [EnumPropName.ProgressBarProgress]: "cc.ProgressBar.progress",
51
+ [EnumPropName.ToggleIsChecked]: "cc.Toggle.isChecked",
52
+ [EnumPropName.RichTextString]: "cc.RichText.string",
53
+ [EnumPropName.ScrollViewEnabled]: "cc.ScrollView.enabled",
54
+ [EnumPropName.MaskEnabled]: "cc.Mask.enabled",
55
+
56
+ // ---- cc.Widget ----
57
+ [EnumPropName.WidgetEnabled]: "cc.Widget.enabled",
58
+ [EnumPropName.WidgetAlignMode]: "cc.Widget.alignMode",
59
+ [EnumPropName.WidgetIsAlignTop]: "cc.Widget.isAlignTop",
60
+ [EnumPropName.WidgetIsAlignBottom]: "cc.Widget.isAlignBottom",
61
+ [EnumPropName.WidgetIsAlignLeft]: "cc.Widget.isAlignLeft",
62
+ [EnumPropName.WidgetIsAlignRight]: "cc.Widget.isAlignRight",
63
+ [EnumPropName.WidgetIsAlignHorizontalCenter]: "cc.Widget.isAlignHorizontalCenter",
64
+ [EnumPropName.WidgetIsAlignVerticalCenter]: "cc.Widget.isAlignVerticalCenter",
65
+ [EnumPropName.WidgetTop]: "cc.Widget.top",
66
+ [EnumPropName.WidgetBottom]: "cc.Widget.bottom",
67
+ [EnumPropName.WidgetLeft]: "cc.Widget.left",
68
+ [EnumPropName.WidgetRight]: "cc.Widget.right",
69
+ [EnumPropName.WidgetHorizontalCenter]: "cc.Widget.horizontalCenter",
70
+ [EnumPropName.WidgetVerticalCenter]: "cc.Widget.verticalCenter",
71
+
72
+ // ---- AMBIGUOUS — 不在主映射表 (W6-2c2: 改走 AMBIGUOUS_ENUM_TO_PROPREF 单一 propRef 整体存) ----
73
+ // EnumPropName.Position (2): 老 PropHandler 行为 n.position = vec3 整体写, 用单一 propRef 'cc.Node.position'
74
+ // EnumPropName.Anchor (8): 老 PropHandler 行为 n.setAnchorPoint(vec2) 整体写, 用 'cc.Node.anchorPoint'
75
+ // EnumPropName.Size (9): 老 PropHandler 行为 n.setContentSize(size) 整体写, 用 'cc.Node.contentSize'
76
+ // EnumPropName.GrayScale (15): cocos 2.x 走材质 stub, 无单一字段, 进 LEGACY_DROPPED_ENUMS 静默丢
77
+ };
78
+
79
+ /**
80
+ * W6-2c2: AMBIGUOUS 复合 prop → propRef 整体存映射 (Position/Anchor/Size 三项).
81
+ *
82
+ * 设计依据 (StatePropHandler.ts): 老 PropHandler 对这三项都是整体读写
83
+ * - Position handler: `n.position = vec3` (整 Vec3)
84
+ * - Anchor handler: `n.setAnchorPoint(vec2)` (整 Vec2)
85
+ * - Size handler: `n.setContentSize(size)` (整 Size)
86
+ * 所以 W6-2c2 把这三项数据以"单一 propRef 整体存"方式落到 ctrlData, 跟老行为一致.
87
+ *
88
+ * 与 ENUM_TO_PROPREF 的区别: 内置 prop 36 项可以从 cc.Node[fieldName] 直接读,
89
+ * AMBIGUOUS 3 项是 cocos 复合字段无法走 fieldName 路径但能整体读写.
90
+ * 两表合并由 enumToPropRef() 提供.
91
+ */
92
+ export const AMBIGUOUS_ENUM_TO_PROPREF: { [enumVal: number]: string } = {
93
+ [EnumPropName.Position]: "cc.Node.position",
94
+ [EnumPropName.Anchor]: "cc.Node.anchorPoint",
95
+ [EnumPropName.Size]: "cc.Node.contentSize",
96
+ };
97
+
98
+ /**
99
+ * W6-2c2: 合并 helper — ENUM_TO_PROPREF 36 项 + AMBIGUOUS 3 项 = 39 项 EnumPropName → propRef 映射.
100
+ *
101
+ * 用于:
102
+ * - migrateLegacyCtrlData: 数字 key → string propRef key 迁移
103
+ * - StateSelectV2.readPropByEnum / writePropByEnum: 双 key 读写桥
104
+ *
105
+ * 未命中 (e.g. GrayScale=15) 返回 undefined, 调用方按需处理.
106
+ */
107
+ export function enumToPropRef(propType: number): string | undefined {
108
+ return ENUM_TO_PROPREF[propType] !== undefined
109
+ ? ENUM_TO_PROPREF[propType]
110
+ : AMBIGUOUS_ENUM_TO_PROPREF[propType];
111
+ }
112
+
113
+ /**
114
+ * propRef 字符串 → EnumPropName 数字 (反向). 用于 W6-2c 删 EnumPropName 时把老 number key
115
+ * 改写成 string key (本 task 不删, 仅占位提供反查能力).
116
+ *
117
+ * 含 36 条 (AMBIGUOUS 4 条不在内, 不影响内置 prop 走老路径).
118
+ */
119
+ export const PROPREF_TO_ENUM: { [propRef: string]: number } = (function () {
120
+ const out: { [k: string]: number } = {};
121
+ for (const k of Object.keys(ENUM_TO_PROPREF)) {
122
+ const num = Number(k);
123
+ if (Number.isFinite(num)) out[ENUM_TO_PROPREF[num]] = num;
124
+ }
125
+ // W6-2c2: AMBIGUOUS 3 项也加入反查 ('cc.Node.position' → Position 等)
126
+ for (const k of Object.keys(AMBIGUOUS_ENUM_TO_PROPREF)) {
127
+ const num = Number(k);
128
+ if (Number.isFinite(num)) out[AMBIGUOUS_ENUM_TO_PROPREF[num]] = num;
129
+ }
130
+ return out;
131
+ })();
132
+
133
+ /**
134
+ * 已经被 EnumPropName 老路径覆盖的 propRef 集合 (即在 ENUM_TO_PROPREF 值集合中).
135
+ * 用于 __preload 中筛选"剩余自定义 propRef"——从 listTrackableProps 结果减去这些, 剩下的
136
+ * 走 string key 路径接入.
137
+ */
138
+ export function isEnumMappedPropRef(propRef: string): boolean {
139
+ return PROPREF_TO_ENUM[propRef] !== undefined;
140
+ }
141
+
142
+ /**
143
+ * W6-2c1: 已废弃的 EnumPropName 数字常量列表 (无对应 propRef, 老 .fire 内若有该 enum 的数据应静默丢).
144
+ *
145
+ * - GrayScale (15): cocos 2.x 走材质 stub, 无单一字段; 已无运行时支持. 老场景里若历史误设的
146
+ * GrayScale 数据存在 _ctrlData 中, __preload 时由 migrateLegacyCtrlData 静默清掉.
147
+ *
148
+ * c2 加 ENUM_TO_PROPREF 36 项迁移路径 (number → string key) 时, 也优先检查此列表
149
+ * (避免误迁废弃 enum). c1 范围: 仅此一项, 不动 ENUM_TO_PROPREF / AMBIGUOUS.
150
+ */
151
+ export const LEGACY_DROPPED_ENUMS: number[] = [EnumPropName.GrayScale];
152
+
153
+ /**
154
+ * W6-axis-decomp X 方案: AMBIGUOUS 复合 propRef → 子项拆解函数.
155
+ *
156
+ * 设计动机: W6-2a 双轨设计 ("Position" 整体 propRef + cc.Node.x/y/z 子项 propRef) 在 cc.Node 复合字段上
157
+ * 存在固有冲突 — 用户排子项 cc.Node.x 只断 propRef 字符串路径, 但整体 "cc.Node.position" 路径仍跟踪 →
158
+ * 切 state 时 x 跟着 Vec3 整体被恢复. X 方案: 彻底废弃整体路径, 让 cc.Node.x/y/z 子项独立接入.
159
+ *
160
+ * 拆解三项 (其它 cc.Node 字段如 Euler/Color 仍走整体, 因为没拆分的对应子项 EnumPropName):
161
+ * - 'cc.Node.position' (Vec3) → x/y/z 三子项
162
+ * - 'cc.Node.anchorPoint' (Vec2) → anchorX/anchorY 两子项
163
+ * - 'cc.Node.contentSize' (Size) → width/height 两子项
164
+ *
165
+ * 调用方: migrateLegacyCtrlData — 检测 propData 内层有整体 Vec3/Vec2/Size 值时调拆解, 升级为多子项 key.
166
+ *
167
+ * 守卫: 值必须形似 Vec3 ({x,y,z}) / Vec2 ({x,y}) / Size ({width,height}). string/null/undefined 不动
168
+ * (e.g. W6.legacyMigration.test.ts 的 "stub-pos" 字符串 stub 不应被解构, 保持兼容).
169
+ */
170
+ export const AMBIGUOUS_DECOMPOSE: { [propRef: string]: (value: any) => Array<[string, any]> | null } = {
171
+ "cc.Node.position": (v: any) => {
172
+ if (!v || typeof v !== "object") return null;
173
+ if (typeof v.x !== "number" || typeof v.y !== "number" || typeof v.z !== "number") return null;
174
+ return [
175
+ ["cc.Node.x", v.x],
176
+ ["cc.Node.y", v.y],
177
+ ["cc.Node.z", v.z],
178
+ ];
179
+ },
180
+ "cc.Node.anchorPoint": (v: any) => {
181
+ if (!v || typeof v !== "object") return null;
182
+ if (typeof v.x !== "number" || typeof v.y !== "number") return null;
183
+ return [
184
+ ["cc.Node.anchorX", v.x],
185
+ ["cc.Node.anchorY", v.y],
186
+ ];
187
+ },
188
+ "cc.Node.contentSize": (v: any) => {
189
+ if (!v || typeof v !== "object") return null;
190
+ if (typeof v.width !== "number" || typeof v.height !== "number") return null;
191
+ return [
192
+ ["cc.Node.width", v.width],
193
+ ["cc.Node.height", v.height],
194
+ ];
195
+ },
196
+ // cocos 2.x cc.Node.scale 是 Vec3 (有 scaleX/scaleY/scaleZ 子项 getter/setter)
197
+ "cc.Node.scale": (v: any) => {
198
+ if (v == null) return null;
199
+ // scale 可能是 number (uniform scale) 或 Vec3
200
+ if (typeof v === "number") {
201
+ return [
202
+ ["cc.Node.scaleX", v],
203
+ ["cc.Node.scaleY", v],
204
+ ["cc.Node.scaleZ", v],
205
+ ];
206
+ }
207
+ if (typeof v !== "object") return null;
208
+ if (typeof v.x !== "number" || typeof v.y !== "number") return null;
209
+ return [
210
+ ["cc.Node.scaleX", v.x],
211
+ ["cc.Node.scaleY", v.y],
212
+ ["cc.Node.scaleZ", typeof v.z === "number" ? v.z : 1],
213
+ ];
214
+ },
215
+ // cocos 2.x cc.Node.eulerAngles 是 Vec3 (rotationX/rotationY 子项, z 不常用)
216
+ "cc.Node.eulerAngles": (v: any) => {
217
+ if (!v || typeof v !== "object") return null;
218
+ if (typeof v.x !== "number" || typeof v.y !== "number") return null;
219
+ return [
220
+ ["cc.Node.rotationX", v.x],
221
+ ["cc.Node.rotationY", v.y],
222
+ ];
223
+ },
224
+ };
225
+
226
+ /**
227
+ * W6-axis-decomp: 判断 propRef 是否是 AMBIGUOUS 整体 propRef (可拆解为子项).
228
+ * 用于 autoOptInCustomComponentProps 跳过整体 propRef 接入 (只接入 listTrackableProps 内的 x/y/z 子项).
229
+ */
230
+ export function isAmbiguousAggregatePropRef(propRef: string): boolean {
231
+ return AMBIGUOUS_DECOMPOSE[propRef] !== undefined;
232
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "ver": "1.1.0",
3
+ "uuid": "2fb11dc9-b4af-430c-84c0-518129982f13",
4
+ "importer": "typescript",
5
+ "isPlugin": false,
6
+ "loadPluginInWeb": true,
7
+ "loadPluginInNative": true,
8
+ "loadPluginInEditor": false,
9
+ "subMetas": {}
10
+ }
@@ -0,0 +1,199 @@
1
+ /**
2
+ * W6 数据模型 (新): 嵌套 ctrlData 结构 + 按 cocos type 分发的 clone/eq helper
3
+ *
4
+ * 与 W5 之前的 EnumPropName 表驱动模型并行存在 — W6-1 仅引入, W6-2 才切换。
5
+ *
6
+ * 设计要点:
7
+ * - TNestedProp: 内层 propRef (e.g. "cc.Sprite.spriteFrame") -> value
8
+ * - TNestedCtrl: 外层 ctrlId -> { $$default$$?, [state]: TNestedProp }
9
+ * - cloneValueByType / eqValueByType: 按 cocos type 分发, 与 StatePropHandler 的 eqVec3/eqColor 等等价
10
+ */
11
+
12
+ /** 单个 prop 的值集合 (propRef 字符串 -> any) */
13
+ export type TNestedProp = { [propRef: string]: any };
14
+
15
+ /**
16
+ * 单个控制器在一个挂载节点上的嵌套数据.
17
+ * $$default$$ — 节点首次接入控制器时拍下的"基准值"
18
+ * [stateIndex] — 各 state 下的 override
19
+ */
20
+ export type TNestedCtrlEntry = {
21
+ $$default$$?: TNestedProp
22
+ [state: number]: TNestedProp
23
+ };
24
+
25
+ /** 多控制器嵌套结构: ctrlId -> Entry */
26
+ export type TNestedCtrl = { [ctrlId: number]: TNestedCtrlEntry };
27
+
28
+ // ============================== 内省辅助 ==============================
29
+
30
+ /**
31
+ * 探测 cocos type, 返回标准化字符串. 用于 clone/eq 分发.
32
+ *
33
+ * cocos 2.x 在 getClassAttrs 里的 type 取值:
34
+ * - 函数构造器 (cc.Color/cc.Vec3/cc.Vec2/cc.Size/cc.Quat 等)
35
+ * - 字符串 "Number" / "String" / "Boolean" / "Object" / "Enum"
36
+ * - undefined (没标 type, 走值本身的运行时类型判定)
37
+ */
38
+ function isVecLike(t: any): t is "Vec2" | "Vec3" | "Color" | "Size" | "Quat" {
39
+ if (typeof t !== "function") return false;
40
+ const ccL = (globalThis as any).cc;
41
+ if (!ccL) return false;
42
+ return t === ccL.Vec3 || t === ccL.Vec2 || t === ccL.Color || t === ccL.Size || t === ccL.Quat;
43
+ }
44
+
45
+ // ============================== clone ==============================
46
+
47
+ /**
48
+ * 按 cocos type 分发深拷:
49
+ * - cc.Color/Vec3/Vec2/Size/Quat 走构造器深拷
50
+ * - 基础类型 (Number/String/Boolean) 直传
51
+ * - asset (cc.SpriteFrame/cc.Font 等) 引用直传
52
+ * - undefined/null 直传
53
+ */
54
+ export function cloneValueByType(value: any, cocosType: any): any {
55
+ if (value === undefined || value === null) return value;
56
+ const ccL = (globalThis as any).cc;
57
+ if (ccL && typeof cocosType === "function") {
58
+ if (cocosType === ccL.Color) {
59
+ const c = value as cc.Color;
60
+ return ccL.color(c.r, c.g, c.b, c.a);
61
+ }
62
+ if (cocosType === ccL.Vec3) {
63
+ const v = value as cc.Vec3;
64
+ return ccL.v3(v.x, v.y, v.z);
65
+ }
66
+ if (cocosType === ccL.Vec2) {
67
+ const v = value as cc.Vec2;
68
+ return ccL.v2(v.x, v.y);
69
+ }
70
+ if (cocosType === ccL.Size) {
71
+ const s = value as cc.Size;
72
+ return ccL.size(s.width, s.height);
73
+ }
74
+ if (ccL.Quat && cocosType === ccL.Quat) {
75
+ const q = value as cc.Quat;
76
+ return new ccL.Quat(q.x, q.y, q.z, q.w);
77
+ }
78
+ // 其余 function-typed (Asset 等) 引用直传
79
+ return value;
80
+ }
81
+ // W6-axis-decomp: cocosType 未知 (cc.Node 内置 native 字段在 listTrackableProps 返回 cocosType=undefined),
82
+ // 但值可能是 Color/Vec3/Vec2/Size/Quat 实例 — 此时也要深拷, 否则 baseline snapshot 和节点共享引用,
83
+ // 节点 mutate 时 baseline 跟着改 → diff 永远命中"等" → dirty 检测漏报 (反向 bug).
84
+ if (ccL && value && typeof value === "object") {
85
+ if (ccL.Color && value instanceof ccL.Color) {
86
+ const c = value as cc.Color;
87
+ return ccL.color(c.r, c.g, c.b, c.a);
88
+ }
89
+ if (ccL.Vec3 && value instanceof ccL.Vec3) {
90
+ const v = value as cc.Vec3;
91
+ return ccL.v3(v.x, v.y, v.z);
92
+ }
93
+ if (ccL.Vec2 && value instanceof ccL.Vec2) {
94
+ const v = value as cc.Vec2;
95
+ return ccL.v2(v.x, v.y);
96
+ }
97
+ if (ccL.Size && value instanceof ccL.Size) {
98
+ const s = value as cc.Size;
99
+ return ccL.size(s.width, s.height);
100
+ }
101
+ if (ccL.Quat && value instanceof ccL.Quat) {
102
+ const q = value as cc.Quat;
103
+ return new ccL.Quat(q.x, q.y, q.z, q.w);
104
+ }
105
+ }
106
+ // 基础类型 / 未知 type — 引用 (基础类型按值传) 直传
107
+ return value;
108
+ }
109
+
110
+ // ============================== eq ==============================
111
+
112
+ /**
113
+ * 按 cocos type 分发等值判定. 与 cloneValueByType 的 type 集合对齐.
114
+ *
115
+ * 规则:
116
+ * - 双侧均为 nil (undefined/null) 视为相等
117
+ * - 一侧 nil 视为不等
118
+ * - Color/Vec3/Vec2/Size/Quat 字段比
119
+ * - 其余走 strict ===
120
+ */
121
+ export function eqValueByType(a: any, b: any, cocosType: any): boolean {
122
+ const aNil = a === undefined || a === null;
123
+ const bNil = b === undefined || b === null;
124
+ if (aNil && bNil) return true;
125
+ if (aNil !== bNil) return false;
126
+
127
+ const ccL = (globalThis as any).cc;
128
+ if (ccL && typeof cocosType === "function") {
129
+ if (cocosType === ccL.Color) {
130
+ const ac = a as cc.Color;
131
+ const bc = b as cc.Color;
132
+ return ac.r === bc.r && ac.g === bc.g && ac.b === bc.b && ac.a === bc.a;
133
+ }
134
+ if (cocosType === ccL.Vec3) {
135
+ const av = a as cc.Vec3;
136
+ const bv = b as cc.Vec3;
137
+ return av.x === bv.x && av.y === bv.y && av.z === bv.z;
138
+ }
139
+ if (cocosType === ccL.Vec2) {
140
+ const av = a as cc.Vec2;
141
+ const bv = b as cc.Vec2;
142
+ return av.x === bv.x && av.y === bv.y;
143
+ }
144
+ if (cocosType === ccL.Size) {
145
+ const as_ = a as cc.Size;
146
+ const bs = b as cc.Size;
147
+ return as_.width === bs.width && as_.height === bs.height;
148
+ }
149
+ if (ccL.Quat && cocosType === ccL.Quat) {
150
+ const aq = a as cc.Quat;
151
+ const bq = b as cc.Quat;
152
+ return aq.x === bq.x && aq.y === bq.y && aq.z === bq.z && aq.w === bq.w;
153
+ }
154
+ }
155
+ // W6-axis-decomp: cocosType 未知 (e.g. cc.Node 内置 native 字段在 listTrackableProps 里 cocosType=undefined),
156
+ // 但若任一侧是 cc 复合对象 (Color/Vec3/Vec2/Size/Quat), 按该类型字段结构比 —— 另一侧可能是老 .fire
157
+ // 反序列化后的普通对象 (#U7) 或不同 frame/getter 的同值对象, strict !== 会引起 false-positive dirty.
158
+ if (ccL && a && b && typeof a === "object" && typeof b === "object") {
159
+ // #U7: 以"任一侧是 cc 实例"判定复合类型, 另一侧只要有同形字段即按字段比 (实例 vs 普通对象同值判等).
160
+ const instType = (x: any): string | null => {
161
+ if (ccL.Color && x instanceof ccL.Color) return "Color";
162
+ if (ccL.Quat && x instanceof ccL.Quat) return "Quat";
163
+ if (ccL.Vec3 && x instanceof ccL.Vec3) return "Vec3";
164
+ if (ccL.Vec2 && x instanceof ccL.Vec2) return "Vec2";
165
+ if (ccL.Size && x instanceof ccL.Size) return "Size";
166
+ return null;
167
+ };
168
+ const t = instType(a) || instType(b);
169
+ const has = (x: any, keys: string[]) => keys.every(k => typeof (x as any)[k] === "number");
170
+ if (t === "Color" && has(a, ["r", "g", "b"]) && has(b, ["r", "g", "b"])) {
171
+ return a.r === b.r && a.g === b.g && a.b === b.b && ((a.a ?? 255) === (b.a ?? 255));
172
+ }
173
+ if (t === "Quat" && has(a, [
174
+ "x", "y", "z", "w",
175
+ ]) && has(b, [
176
+ "x", "y", "z", "w",
177
+ ])) {
178
+ return a.x === b.x && a.y === b.y && a.z === b.z && a.w === b.w;
179
+ }
180
+ if (t === "Vec3" && has(a, ["x", "y", "z"]) && has(b, ["x", "y", "z"])) {
181
+ return a.x === b.x && a.y === b.y && a.z === b.z;
182
+ }
183
+ if (t === "Vec2" && has(a, ["x", "y"]) && has(b, ["x", "y"])) {
184
+ return a.x === b.x && a.y === b.y;
185
+ }
186
+ if (t === "Size" && has(a, ["width", "height"]) && has(b, ["width", "height"])) {
187
+ return a.width === b.width && a.height === b.height;
188
+ }
189
+ }
190
+ // #U4: NaN === NaN 在 JS 为 false → 两侧均为 NaN 视为相等, 避免持续 dirty 误判 + 重复写.
191
+ if (typeof a === "number" && typeof b === "number" && Number.isNaN(a) && Number.isNaN(b)) {
192
+ return true;
193
+ }
194
+ // 基础类型 / asset 引用 / 未知 — strict ===
195
+ return a === b;
196
+ }
197
+
198
+ // 抑制 isVecLike 未使用警告 (保留供 W6-2/W6-3 复用)
199
+ void isVecLike;
@@ -0,0 +1,10 @@
1
+ {
2
+ "ver": "1.1.0",
3
+ "uuid": "b9e67f2c-d4ef-49aa-b600-8c4a8de14a3d",
4
+ "importer": "typescript",
5
+ "isPlugin": false,
6
+ "loadPluginInWeb": true,
7
+ "loadPluginInNative": true,
8
+ "loadPluginInEditor": false,
9
+ "subMetas": {}
10
+ }
@@ -0,0 +1,151 @@
1
+ /**
2
+ * W6 数据模型基线 (基础设施): prefab 内省, 用于枚举一个节点上"可追踪"的 prop 集合.
3
+ *
4
+ * 走 cocos 2.4.x 引擎自己用的元数据:
5
+ * - (ctor as any).__props__: ctor 的 @property / native 字段名数组
6
+ * - cc.Class.Attr.getClassAttrs(ctor): 每个 prop 的 visible / serializable / type / hasGetter / hasSetter 等
7
+ *
8
+ * 输出 listTrackableProps(node) 返回 [{compName, propKey, propRef, cocosType, visible, serializable, readonly}].
9
+ *
10
+ * 过滤规则:
11
+ * - 跳过 _ 开头的内部存储字段 (cocos 2.x 用 `_xxx` + getter/setter 模式, 上层 `xxx` 才是 user-facing)
12
+ * - 跳过 attrs[propKey + "$_$visible"] === false 的字段 (cocos 明确标记不显示)
13
+ * - 跳过 SYSTEM_EXCLUDE 黑名单 (cc.Widget.target / _alignFlags / cc.Animation.defaultClip 等)
14
+ * - readonly = hasGetter === true 且 hasSetter !== true (只读 getter 不可写)
15
+ *
16
+ * 共存阶段: W6-1 仅引入, W6-2 切换到取代 PropHandlerManager.listRegisteredPropTypes 的扫描路径.
17
+ */
18
+
19
+ /** 单条可追踪 prop 描述 */
20
+ export interface TrackableProp {
21
+ /** 组件类名 (cc.Node / cc.Sprite / W6TestComp 等) */
22
+ compName: string
23
+ /** 字段名 (active / spriteFrame / heatLevel 等) */
24
+ propKey: string
25
+ /** propRef = compName + "." + propKey, 是 NestedCtrlData 的 key */
26
+ propRef: string
27
+ /** cocos getClassAttrs 返回的 type — 函数构造器 or "Number"/"String"/"Boolean"/"Object"/"Enum"/undefined */
28
+ cocosType: any
29
+ /** inspector 可见性 (false=不可见) */
30
+ visible: boolean
31
+ /** prefab 序列化标记 */
32
+ serializable: boolean
33
+ /** 只读 (getter only, 无 setter) */
34
+ readonly: boolean
35
+ }
36
+
37
+ /**
38
+ * 系统黑名单 — 这些 prop 即使可见也不参与 state 切换 (引擎内部状态 / 资源关联).
39
+ *
40
+ * 选取理由:
41
+ * - cc.Widget.target: 节点引用, prefab 实例化时不稳定
42
+ * - cc.Widget._alignFlags: 内部对齐 bitmask
43
+ * - cc.Animation.defaultClip / currentClip: 资源引用, 应通过 play() 切换
44
+ * - cc.ParticleSystem.file: 粒子配置资源
45
+ * - cc.AudioSource.clip: 音频资源
46
+ * - cc.Node.rotation / rotationX / rotationY: cocos 2.1.0 起废弃, 读写会触发 cc.warn (使用 angle / eulerAngles 替代)
47
+ * - cc.Node.name / cc.Node.uuid: 节点身份标识, 非视觉状态, 不应随 state 切换 (uuid 实例化时还会变)
48
+ */
49
+ export const SYSTEM_EXCLUDE: string[] = [
50
+ "cc.Widget.target",
51
+ "cc.Widget.alignFlags",
52
+ "cc.Animation.defaultClip",
53
+ "cc.Animation.currentClip",
54
+ "cc.ParticleSystem.file",
55
+ "cc.AudioSource.clip",
56
+ "cc.Node.rotation",
57
+ "cc.Node.rotationX",
58
+ "cc.Node.rotationY",
59
+ "cc.Node.name",
60
+ "cc.Node.uuid",
61
+ ];
62
+
63
+ const EXCLUDE_SET = new Set(SYSTEM_EXCLUDE);
64
+
65
+ /**
66
+ * 取构造器类名:
67
+ * - 内置 cc 类 (cc.Sprite/cc.Widget 等): cc.js.getClassName(ctor) 返回 "cc.Sprite"
68
+ * - 自定义 @ccclass 类: 返回 @ccclass("Name") 注册名
69
+ * - fallback: ctor.name
70
+ */
71
+ function getCompName(ctor: any): string {
72
+ const ccL = (globalThis as any).cc;
73
+ if (ccL && ccL.js && typeof ccL.js.getClassName === "function") {
74
+ const cn = ccL.js.getClassName(ctor);
75
+ if (typeof cn === "string" && cn.length > 0) return cn;
76
+ }
77
+ // cocos 2.x 也把 @ccclass 注册名写到 ctor.__classname__ (某些版本)
78
+ const cnAlt = (ctor as any).__classname__;
79
+ if (typeof cnAlt === "string" && cnAlt.length > 0) return cnAlt;
80
+ if (ctor && ctor.name) return ctor.name;
81
+ return "UnknownClass";
82
+ }
83
+
84
+ /**
85
+ * 枚举一个 ctor 上的可追踪 prop. 不递归父类 (cocos __props__ 已合并继承链).
86
+ */
87
+ function enumPropsForCtor(ctor: any, compName: string): TrackableProp[] {
88
+ const out: TrackableProp[] = [];
89
+ const props = (ctor as any).__props__ as string[] | undefined;
90
+ if (!Array.isArray(props)) return out;
91
+ const ccL = (globalThis as any).cc;
92
+ const attrs = ccL && ccL.Class && ccL.Class.Attr && ccL.Class.Attr.getClassAttrs
93
+ ? ccL.Class.Attr.getClassAttrs(ctor)
94
+ : {};
95
+ for (const propKey of props) {
96
+ // 跳过下划线内部字段
97
+ if (propKey.startsWith("_")) continue;
98
+ const propRef = `${compName}.${propKey}`;
99
+ // 跳过系统黑名单
100
+ if (EXCLUDE_SET.has(propRef)) continue;
101
+ const visAttr = attrs[`${propKey}$_$visible`];
102
+ // 跳过显式 visible:false
103
+ if (visAttr === false) continue;
104
+ const serAttr = attrs[`${propKey}$_$serializable`];
105
+ const typeAttr = attrs[`${propKey}$_$type`];
106
+ const hasGetter = attrs[`${propKey}$_$hasGetter`];
107
+ const hasSetter = attrs[`${propKey}$_$hasSetter`];
108
+ const readonly = hasGetter === true && hasSetter !== true;
109
+ out.push({
110
+ compName,
111
+ propKey,
112
+ propRef,
113
+ cocosType: typeAttr,
114
+ // visible 默认 true (undefined 视为可见), 已在上面排除了 false
115
+ visible: visAttr !== false,
116
+ // serializable 默认 true, 显式 false 才标 false
117
+ serializable: serAttr !== false,
118
+ readonly,
119
+ });
120
+ }
121
+ return out;
122
+ }
123
+
124
+ /**
125
+ * 列出节点上所有可追踪的 prop:
126
+ * - cc.Node 类的 user-facing 字段 (active/x/y/scale/color/opacity/anchorX 等)
127
+ * - 节点 _components 上每个 cc.Component 子类的 @property 字段
128
+ *
129
+ * 顺序: 先 cc.Node, 再按 _components 顺序.
130
+ */
131
+ export function listTrackableProps(node: cc.Node): TrackableProp[] {
132
+ if (!node) return [];
133
+ const ccL = (globalThis as any).cc;
134
+ const out: TrackableProp[] = [];
135
+
136
+ // 1) cc.Node 自身
137
+ out.push(...enumPropsForCtor(ccL.Node, "cc.Node"));
138
+
139
+ // 2) 节点上每个组件
140
+ const comps = (node as any)._components as cc.Component[] | undefined;
141
+ if (Array.isArray(comps)) {
142
+ for (const comp of comps) {
143
+ if (!comp) continue;
144
+ const ctor = (comp as any).constructor;
145
+ const compName = getCompName(ctor);
146
+ out.push(...enumPropsForCtor(ctor, compName));
147
+ }
148
+ }
149
+
150
+ return out;
151
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "ver": "1.1.0",
3
+ "uuid": "0630d1ba-daeb-49ee-b72d-31e05be3df89",
4
+ "importer": "typescript",
5
+ "isPlugin": false,
6
+ "loadPluginInWeb": true,
7
+ "loadPluginInNative": true,
8
+ "loadPluginInEditor": false,
9
+ "subMetas": {}
10
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "ver": "1.1.3",
3
+ "uuid": "44eae133-0ac8-44e5-a80f-b27d248cbdbb",
4
+ "importer": "folder",
5
+ "isBundle": false,
6
+ "bundleName": "",
7
+ "priority": 1,
8
+ "compressionType": {},
9
+ "optimizeHotUpdate": {},
10
+ "inlineSpriteFrames": {},
11
+ "isRemoteBundle": {},
12
+ "subMetas": {}
13
+ }