ice-entity-designer 0.10.0 → 0.12.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.
@@ -54,6 +54,21 @@ export default class FlowDesigner {
54
54
  private __redoStack;
55
55
  private __historyEnabled;
56
56
  private __maxHistory;
57
+ /**
58
+ * 历史栈**按留存字节**的封顶(默认 32 MB)。
59
+ *
60
+ * 为什么不能只按条数封顶:历史里存的是**整份文档快照**,而快照大小随文档规模涨 ——
61
+ * 真机实测(2026-09-21,水务编辑器用本应用 API 造 2,020 个符号 + 2,020 条管线):
62
+ * 100 条快照 ≈ **874 MB**(编辑器堆 932.8 MB 里 94% 是它),`resetHistory()` 之后只剩 58.4 MB。
63
+ * 按字节封顶后:小文档仍然能撤销很多步(条数上限照旧 100),大文档则自动收敛到几十步 ——
64
+ * 用户能感知的是"撤销变浅了一点",而不是"标签页吃掉 1 GB"。
65
+ */
66
+ private __maxHistoryBytes;
67
+ /** undo / redo 两栈里**留存的字符串字节数**(快照是字符串,长度即字节量级)。 */
68
+ private __historyBytes;
69
+ /** 批量改动嵌套深度(见 `beginBatch`):>0 时批内不记历史,批末统一记"批前状态"。 */
70
+ private __batchDepth;
71
+ private __batchStart;
57
72
  private __listeners;
58
73
  private __mousedownHandler;
59
74
  /** 画布拖拽节点时会话:BEFORE_MOVE 记一次历史,mouseup 结束会话 */
@@ -91,6 +106,19 @@ export default class FlowDesigner {
91
106
  constructor(ice: ICE);
92
107
  get nodes(): any[];
93
108
  get edges(): any[];
109
+ /**
110
+ * 判"这是不是本设计器认的**节点** / **连线**"(选中、更新、删除、快照收集都用它)。
111
+ *
112
+ * 默认认 `FlowNode` / `FlowEdge`**及其子类**;**不是它们分支的领域图元**要在子类里覆盖
113
+ * (水务的 `WaterSymbol extends ICEGroup`、`WaterPipe extends FlowEdge` 就是这种情况,
114
+ * 见 `WaterProcessDesigner`)。
115
+ *
116
+ * 2026-09-21 修:原先这些地方比的是**精确 `typeId`**,于是领域图元在
117
+ * 「点选 / `updateNode` / `updateEdge`」四处**静默失效**(不报错、只是没反应)——
118
+ * 实测既有 `water-editor.html` 点符号选不中就是这个根因。
119
+ */
120
+ protected isNodeComponent(component: any): boolean;
121
+ protected isEdgeComponent(component: any): boolean;
94
122
  /**
95
123
  * 递归展开场景树(容器型节点 —— 如 BPMN 的池/泳道 —— 里面的子节点同样算流程的一部分)。
96
124
  *
@@ -258,7 +286,47 @@ export default class FlowDesigner {
258
286
  captureHistory(): void;
259
287
  /** 清空撤销/重做栈(用于「载入示例」这类不该占用历史的初始化操作) */
260
288
  resetHistory(): void;
289
+ /**
290
+ * **批量改动**:批内不逐条记历史,整批算**一条**(撤销时整批回退)。
291
+ *
292
+ * 什么时候用:程序化建图 / 导入 / 一次性铺一批图元。不用它的话每个图元都会
293
+ * **整份序列化一次**,实测 2,020 个符号花 **58.5 秒**(≈29 ms/个),历史里还留下 100 份大快照。
294
+ *
295
+ * ```ts
296
+ * designer.beginBatch();
297
+ * for (const item of docs) designer.createSymbol(...);
298
+ * designer.endBatch();
299
+ * ```
300
+ */
301
+ beginBatch(): void;
302
+ endBatch(): void;
303
+ /**
304
+ * **历史预算**(给宿主 / 测试用):条数上限与**留存字节**上限。
305
+ *
306
+ * 默认 100 条 / 32 MB。大文档(几万个图元)下快照很大,字节上限会先把老条目挤掉 ——
307
+ * 这是刻意的取舍:宁可"撤销浅一点",也不要"标签页吃掉 1 GB"(实测 2,020 符号 × 100 条 = 874 MB)。
308
+ */
309
+ setHistoryBudget(budget?: {
310
+ maxEntries?: number;
311
+ maxBytes?: number;
312
+ }): void;
313
+ /** 当前历史占用的规模(诊断 / 断言用):`{ undo, redo, bytes }`。 */
314
+ getHistoryStats(): {
315
+ undo: number;
316
+ redo: number;
317
+ bytes: number;
318
+ };
261
319
  protected __captureHistory(): void;
320
+ /** 入栈 + 双重封顶(条数 + 留存字节)+ 清空 redo。 */
321
+ private __pushHistory;
322
+ /**
323
+ * 重算两栈的留存字节。
324
+ *
325
+ * 为什么"重算"而不是"加减":`undo` / `redo` 里快照在两栈之间搬家,还有回放失败的回滚分支 ——
326
+ * 加减法只要漏一条分支就会越记越偏,而重算是 O(栈深) 的字符串长度求和(≤100 条),
327
+ * 属于"慢一点但永远对"的那一类。这个量只用于**封顶判断**,不进任何热路径(每帧)。
328
+ */
329
+ private __recountHistory;
262
330
  private __applySnapshot;
263
331
  /**
264
332
  * 撤销:先记账(把当前状态压入 redo、从 undo 弹出目标),再回放目标快照,
@@ -268,5 +336,12 @@ export default class FlowDesigner {
268
336
  undo(): void;
269
337
  redo(): void;
270
338
  private __handleMouseDown;
339
+ /**
340
+ * 这个组件算不算"可以被点选的领域图元"(选中判据的唯一入口,领域包可覆盖)。
341
+ *
342
+ * 默认认 `FlowNode` / `FlowEdge` 及其子类;**不是它们分支的领域图元**要在子类里补
343
+ * (水务的 `WaterSymbol extends ICEGroup`、`WaterPipe extends FlowEdge` 就是这么回事)。
344
+ */
345
+ protected isSelectableComponent(component: any): boolean;
271
346
  dispose(): void;
272
347
  }
@@ -54,6 +54,21 @@ export default class FlowDesigner {
54
54
  private __redoStack;
55
55
  private __historyEnabled;
56
56
  private __maxHistory;
57
+ /**
58
+ * 历史栈**按留存字节**的封顶(默认 32 MB)。
59
+ *
60
+ * 为什么不能只按条数封顶:历史里存的是**整份文档快照**,而快照大小随文档规模涨 ——
61
+ * 真机实测(2026-09-21,水务编辑器用本应用 API 造 2,020 个符号 + 2,020 条管线):
62
+ * 100 条快照 ≈ **874 MB**(编辑器堆 932.8 MB 里 94% 是它),`resetHistory()` 之后只剩 58.4 MB。
63
+ * 按字节封顶后:小文档仍然能撤销很多步(条数上限照旧 100),大文档则自动收敛到几十步 ——
64
+ * 用户能感知的是"撤销变浅了一点",而不是"标签页吃掉 1 GB"。
65
+ */
66
+ private __maxHistoryBytes;
67
+ /** undo / redo 两栈里**留存的字符串字节数**(快照是字符串,长度即字节量级)。 */
68
+ private __historyBytes;
69
+ /** 批量改动嵌套深度(见 `beginBatch`):>0 时批内不记历史,批末统一记"批前状态"。 */
70
+ private __batchDepth;
71
+ private __batchStart;
57
72
  private __listeners;
58
73
  private __mousedownHandler;
59
74
  /** 画布拖拽节点时会话:BEFORE_MOVE 记一次历史,mouseup 结束会话 */
@@ -91,6 +106,19 @@ export default class FlowDesigner {
91
106
  constructor(ice: ICE);
92
107
  get nodes(): any[];
93
108
  get edges(): any[];
109
+ /**
110
+ * 判"这是不是本设计器认的**节点** / **连线**"(选中、更新、删除、快照收集都用它)。
111
+ *
112
+ * 默认认 `FlowNode` / `FlowEdge`**及其子类**;**不是它们分支的领域图元**要在子类里覆盖
113
+ * (水务的 `WaterSymbol extends ICEGroup`、`WaterPipe extends FlowEdge` 就是这种情况,
114
+ * 见 `WaterProcessDesigner`)。
115
+ *
116
+ * 2026-09-21 修:原先这些地方比的是**精确 `typeId`**,于是领域图元在
117
+ * 「点选 / `updateNode` / `updateEdge`」四处**静默失效**(不报错、只是没反应)——
118
+ * 实测既有 `water-editor.html` 点符号选不中就是这个根因。
119
+ */
120
+ protected isNodeComponent(component: any): boolean;
121
+ protected isEdgeComponent(component: any): boolean;
94
122
  /**
95
123
  * 递归展开场景树(容器型节点 —— 如 BPMN 的池/泳道 —— 里面的子节点同样算流程的一部分)。
96
124
  *
@@ -258,7 +286,47 @@ export default class FlowDesigner {
258
286
  captureHistory(): void;
259
287
  /** 清空撤销/重做栈(用于「载入示例」这类不该占用历史的初始化操作) */
260
288
  resetHistory(): void;
289
+ /**
290
+ * **批量改动**:批内不逐条记历史,整批算**一条**(撤销时整批回退)。
291
+ *
292
+ * 什么时候用:程序化建图 / 导入 / 一次性铺一批图元。不用它的话每个图元都会
293
+ * **整份序列化一次**,实测 2,020 个符号花 **58.5 秒**(≈29 ms/个),历史里还留下 100 份大快照。
294
+ *
295
+ * ```ts
296
+ * designer.beginBatch();
297
+ * for (const item of docs) designer.createSymbol(...);
298
+ * designer.endBatch();
299
+ * ```
300
+ */
301
+ beginBatch(): void;
302
+ endBatch(): void;
303
+ /**
304
+ * **历史预算**(给宿主 / 测试用):条数上限与**留存字节**上限。
305
+ *
306
+ * 默认 100 条 / 32 MB。大文档(几万个图元)下快照很大,字节上限会先把老条目挤掉 ——
307
+ * 这是刻意的取舍:宁可"撤销浅一点",也不要"标签页吃掉 1 GB"(实测 2,020 符号 × 100 条 = 874 MB)。
308
+ */
309
+ setHistoryBudget(budget?: {
310
+ maxEntries?: number;
311
+ maxBytes?: number;
312
+ }): void;
313
+ /** 当前历史占用的规模(诊断 / 断言用):`{ undo, redo, bytes }`。 */
314
+ getHistoryStats(): {
315
+ undo: number;
316
+ redo: number;
317
+ bytes: number;
318
+ };
261
319
  protected __captureHistory(): void;
320
+ /** 入栈 + 双重封顶(条数 + 留存字节)+ 清空 redo。 */
321
+ private __pushHistory;
322
+ /**
323
+ * 重算两栈的留存字节。
324
+ *
325
+ * 为什么"重算"而不是"加减":`undo` / `redo` 里快照在两栈之间搬家,还有回放失败的回滚分支 ——
326
+ * 加减法只要漏一条分支就会越记越偏,而重算是 O(栈深) 的字符串长度求和(≤100 条),
327
+ * 属于"慢一点但永远对"的那一类。这个量只用于**封顶判断**,不进任何热路径(每帧)。
328
+ */
329
+ private __recountHistory;
262
330
  private __applySnapshot;
263
331
  /**
264
332
  * 撤销:先记账(把当前状态压入 redo、从 undo 弹出目标),再回放目标快照,
@@ -268,5 +336,12 @@ export default class FlowDesigner {
268
336
  undo(): void;
269
337
  redo(): void;
270
338
  private __handleMouseDown;
339
+ /**
340
+ * 这个组件算不算"可以被点选的领域图元"(选中判据的唯一入口,领域包可覆盖)。
341
+ *
342
+ * 默认认 `FlowNode` / `FlowEdge` 及其子类;**不是它们分支的领域图元**要在子类里补
343
+ * (水务的 `WaterSymbol extends ICEGroup`、`WaterPipe extends FlowEdge` 就是这么回事)。
344
+ */
345
+ protected isSelectableComponent(component: any): boolean;
271
346
  dispose(): void;
272
347
  }
@@ -79,3 +79,5 @@ export { registerDesignerTypes, designerTypeIds } from './utils/register-types.m
79
79
  * 运行时仍然是宿主提供的那一份 ice-render 模块,不会产生第二份内核。
80
80
  */
81
81
  export * from 'ice-render';
82
+ export { default as WaterVirtualDoc, ITEM_SYMBOL, ITEM_LABEL, ITEM_PIPE } from './virtual/WaterVirtualDoc.mjs';
83
+ export type { WaterVirtualDocOptions } from './virtual/WaterVirtualDoc.mjs';
@@ -79,3 +79,5 @@ export { registerDesignerTypes, designerTypeIds } from './utils/register-types';
79
79
  * 运行时仍然是宿主提供的那一份 ice-render 模块,不会产生第二份内核。
80
80
  */
81
81
  export * from 'ice-render';
82
+ export { default as WaterVirtualDoc, ITEM_SYMBOL, ITEM_LABEL, ITEM_PIPE } from './virtual/WaterVirtualDoc';
83
+ export type { WaterVirtualDocOptions } from './virtual/WaterVirtualDoc';
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Copyright (c) 2022 大漠穷秋.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+ import type { VirtualChildSource, VirtualChildView } from 'ice-render';
9
+ /** 条目类型 */
10
+ export declare const ITEM_SYMBOL = 0;
11
+ export declare const ITEM_LABEL = 1;
12
+ export declare const ITEM_PIPE = 2;
13
+ /** 文档类型键(序列化时写进容器的 `virtual.type`,反序列化按它找工厂)。 */
14
+ export declare const WATER_VIRTUAL_DOC_TYPE = "ied:water-virtual-doc";
15
+ export interface WaterVirtualDocOptions {
16
+ /** 符号个数(默认 20000)。标注与管线按它派生。 */
17
+ symbols?: number;
18
+ /** 栅格列数(默认按世界长宽比算)。 */
19
+ cols?: number;
20
+ worldW?: number;
21
+ worldH?: number;
22
+ /** 每个符号都配一条到右邻的管线(默认 true)。 */
23
+ pipes?: boolean;
24
+ }
25
+ export declare class WaterVirtualDoc implements VirtualChildSource {
26
+ readonly count: number;
27
+ /** 文档类型键:反序列化时引擎按它找回工厂(见文件末尾的注册)。 */
28
+ readonly documentType = "ied:water-virtual-doc";
29
+ /** 符号个数(下标 0..symbolCount-1 是符号,接着是标注,最后是管线) */
30
+ readonly symbolCount: number;
31
+ version: number;
32
+ /** ---- 列存 ---- */
33
+ readonly type: Uint8Array;
34
+ readonly x: Float32Array;
35
+ readonly y: Float32Array;
36
+ readonly w: Float32Array;
37
+ readonly h: Float32Array;
38
+ /** 符号/标注的 kind 索引(TEXT 条目用不到) */
39
+ readonly kind: Uint8Array;
40
+ /** 标注 → 它属于哪个符号(符号自己存 -1) */
41
+ readonly labelOf: Int32Array;
42
+ /** 管线两端(指向符号下标)与介质 / 管径 */
43
+ readonly pipeFrom: Int32Array;
44
+ readonly pipeTo: Int32Array;
45
+ readonly pipeMedium: Uint8Array;
46
+ readonly pipeDn: Uint16Array;
47
+ /** 已物化(批量层让位)标记:**契约要求**——`materialize` 里必须置位 */
48
+ readonly done: Uint8Array;
49
+ readonly worldW: number;
50
+ readonly worldH: number;
51
+ /** 位号 / 名称的字符串表(按符号下标算:`P-000123` / `水泵`) */
52
+ tagOf(i: number): string;
53
+ nameOf(i: number): string;
54
+ private readonly ice;
55
+ private readonly cols;
56
+ private readonly rows;
57
+ private readonly cell;
58
+ private readonly gw;
59
+ private readonly gh;
60
+ private head;
61
+ private nodePrim;
62
+ private nodeNext;
63
+ private nodeCount;
64
+ private readonly sprites;
65
+ /** 每 kind 的 SVG 片段(第一次导出时惰性生成;复合符号导出的正确形态是 def + use)。 */
66
+ private readonly svgDefs;
67
+ private readonly scratch;
68
+ private labelSyncPad;
69
+ /** 绑定的虚拟层(`paint` 里做标注的窗口同步要用它)。 */
70
+ private layer;
71
+ /** 上一次批量落墨的窗口(页面 / 探针读) */
72
+ lastView: VirtualChildView | null;
73
+ /** 是否在 `paint` 里自动做"标注窗口同步"(默认开;关掉可以自己驱动) */
74
+ syncLabelsOnPaint: boolean;
75
+ constructor(ice: any, options?: WaterVirtualDocOptions);
76
+ /** 建/重建空间索引(构造期 + 反序列化贴完编辑之后各一次)。 */
77
+ rebuildIndex(): void;
78
+ private __addNode;
79
+ boxAt(i: number, out: Float64Array): void;
80
+ /** 窗口遍历:网格候选 + 精确盒相交(标注也在内 —— 窗口同步要用它)。 */
81
+ forEachInBox(x0: number, y0: number, x1: number, y1: number, visit: (i: number) => void): void;
82
+ private windowStamp;
83
+ private readonly windowSeen;
84
+ /** 退化条目(末行的自连管线、零尺寸):不进索引、不落墨。 */
85
+ private __degenerate;
86
+ hitTest(lx: number, ly: number): number;
87
+ private __hitPrecise;
88
+ private __segDist;
89
+ paint(ctx: any, view: VirtualChildView): boolean;
90
+ /** 绑定虚拟层:标注的物化 / 回收要用引擎的助手(幂等物化 + 回收)。 */
91
+ attachLayer(layer: any): void;
92
+ /**
93
+ * **窗口内标注的物化 / 回收**(混合策略的应用侧循环)。
94
+ *
95
+ * 为什么标注不批量画:符号的位号是**每个实例都不同**的字符串,烤成精灵等于每个符号一张图;
96
+ * 而物化成真 `ICEText` 之后,引擎的**文本位图缓存**会把"同字号同内容的文字"复用起来
97
+ * (见引擎的 `ObjectCache`),窗口里几百个标注的实际成本远低于逐帧 `fillText`。
98
+ */
99
+ syncLabels(view?: VirtualChildView): void;
100
+ /**
101
+ * **文档的唯一写入口**(P2 第 3 条):undo/redo、属性面板这类程序化写都走它。
102
+ * 返回 false = 补丁不认识(引擎会原样返回 false,调用方自己决定怎么办)。
103
+ */
104
+ applyPatch(index: number, patch: Record<string, any>): boolean;
105
+ /**
106
+ * **物化组件被改动后的回流**(P2 第 3 条):引擎在用户拖完 / 属性面板改完后回调到这里,
107
+ * 文档立刻跟着变(不必等存盘)。
108
+ */
109
+ onChildPatched(index: number, patch: Record<string, any>): void;
110
+ /** 符号下标 → 它的标注下标(-1 = 这个文档没给标注留位)。 */
111
+ labelIndexOfSymbol(symbolIndex: number): number;
112
+ private __strokePipe;
113
+ /** 每次 paint 真正落墨的条目数(探针用) */
114
+ drawCalls: number;
115
+ /**
116
+ * 造一个真组件(**不挂树** —— 引擎的 `materializeVirtualChild` 负责挂进虚拟层)。
117
+ *
118
+ * 契约:必须在这里把 `done[i]` 置位,否则批量层会再画一遍。
119
+ */
120
+ materialize(i: number): any;
121
+ /** 窗口内标注的物化 / 回收(应用侧的"混合策略"循环;引擎只提供两个助手)。 */
122
+ windowItems(view: VirtualChildView, visit: (i: number) => void): void;
123
+ /** 管线的世界盒(正交路由:横 → 竖 → 横) */
124
+ private pipeBox;
125
+ /** 文档占用的"真数据"字节数(探针 / 反馈报告用) */
126
+ get bytes(): number;
127
+ /**
128
+ * **文档载荷**:只存"参数 + 少量编辑"(不存整份列存)。
129
+ *
130
+ * 为什么不是整份列存:列存是**确定性生成**的(参数一样 → 内容一样),所以载荷只需要
131
+ * `params` + `edits`(被拖过的那几个符号的坐标)。实测一份 6 万条目的文档载荷约几 KB,
132
+ * 而不是 3MB 的列存 —— 快照文件因此仍然是"可读、可 diff"的。
133
+ */
134
+ serializeDocument(): any;
135
+ /** 编辑过的符号(`[下标, x, y]`):物化组件被拖动后与列存的差异。 */
136
+ private readonly edits;
137
+ /** 物化组件的引用(写回与差异检测用):下标 → 组件。 */
138
+ readonly liveComponents: Map<number, any>;
139
+ /** 把已物化组件的坐标与列存对齐(拖过的写回列存,没动的记进 `edits`)。 */
140
+ syncEditsFromComponents(): void;
141
+ private recordEdit;
142
+ /** 由载荷重建(工厂用)。列存按参数确定性重建,再把 `edits` 贴回去。 */
143
+ static fromPayload(payload: any, ice: any): WaterVirtualDoc;
144
+ /** 文档包围盒(导出 / 适应视图用)。 */
145
+ documentBounds(out: Float64Array): boolean;
146
+ /**
147
+ * **全量 SVG 导出**(P2 第 1 条):把整份文档写进 sink。
148
+ *
149
+ * 复合符号的矢量表示用"每 kind 一份 def + 每个实例一条 `<use>`":def 由引擎的
150
+ * `exportSvg([模板组件])` 生成(**与画布同源**,不会两套几何),2 万个符号因此只有
151
+ * 21 份 def + 2 万条 use。标注与管线直接写(它们是纯文本 / 折线)。
152
+ */
153
+ paintToSvg(sink: any): boolean;
154
+ /** 用引擎导出**一个模板组件**的 SVG 片段(去掉 `<svg>` 外壳与 `<defs>`)。 */
155
+ private __svgFragmentOfKind;
156
+ /**
157
+ * 每种 kind 渲染一张离屏位图(**rs=2**,缩放贴图时不至于太糊)。
158
+ *
159
+ * 为什么走精灵而不是共享 `Path2D`:IED 的 `WaterSymbol` 是**复合组件**(矩形 + 栅条 / 曝气盘 / 刮泥桥…),
160
+ * 它的外观不是一条路径 —— 组件的离屏位图才是它的正确"批量表示"。
161
+ */
162
+ private __mintSprites;
163
+ }
164
+ export default WaterVirtualDoc;
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Copyright (c) 2022 大漠穷秋.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+ import type { VirtualChildSource, VirtualChildView } from 'ice-render';
9
+ /** 条目类型 */
10
+ export declare const ITEM_SYMBOL = 0;
11
+ export declare const ITEM_LABEL = 1;
12
+ export declare const ITEM_PIPE = 2;
13
+ /** 文档类型键(序列化时写进容器的 `virtual.type`,反序列化按它找工厂)。 */
14
+ export declare const WATER_VIRTUAL_DOC_TYPE = "ied:water-virtual-doc";
15
+ export interface WaterVirtualDocOptions {
16
+ /** 符号个数(默认 20000)。标注与管线按它派生。 */
17
+ symbols?: number;
18
+ /** 栅格列数(默认按世界长宽比算)。 */
19
+ cols?: number;
20
+ worldW?: number;
21
+ worldH?: number;
22
+ /** 每个符号都配一条到右邻的管线(默认 true)。 */
23
+ pipes?: boolean;
24
+ }
25
+ export declare class WaterVirtualDoc implements VirtualChildSource {
26
+ readonly count: number;
27
+ /** 文档类型键:反序列化时引擎按它找回工厂(见文件末尾的注册)。 */
28
+ readonly documentType = "ied:water-virtual-doc";
29
+ /** 符号个数(下标 0..symbolCount-1 是符号,接着是标注,最后是管线) */
30
+ readonly symbolCount: number;
31
+ version: number;
32
+ /** ---- 列存 ---- */
33
+ readonly type: Uint8Array;
34
+ readonly x: Float32Array;
35
+ readonly y: Float32Array;
36
+ readonly w: Float32Array;
37
+ readonly h: Float32Array;
38
+ /** 符号/标注的 kind 索引(TEXT 条目用不到) */
39
+ readonly kind: Uint8Array;
40
+ /** 标注 → 它属于哪个符号(符号自己存 -1) */
41
+ readonly labelOf: Int32Array;
42
+ /** 管线两端(指向符号下标)与介质 / 管径 */
43
+ readonly pipeFrom: Int32Array;
44
+ readonly pipeTo: Int32Array;
45
+ readonly pipeMedium: Uint8Array;
46
+ readonly pipeDn: Uint16Array;
47
+ /** 已物化(批量层让位)标记:**契约要求**——`materialize` 里必须置位 */
48
+ readonly done: Uint8Array;
49
+ readonly worldW: number;
50
+ readonly worldH: number;
51
+ /** 位号 / 名称的字符串表(按符号下标算:`P-000123` / `水泵`) */
52
+ tagOf(i: number): string;
53
+ nameOf(i: number): string;
54
+ private readonly ice;
55
+ private readonly cols;
56
+ private readonly rows;
57
+ private readonly cell;
58
+ private readonly gw;
59
+ private readonly gh;
60
+ private head;
61
+ private nodePrim;
62
+ private nodeNext;
63
+ private nodeCount;
64
+ private readonly sprites;
65
+ /** 每 kind 的 SVG 片段(第一次导出时惰性生成;复合符号导出的正确形态是 def + use)。 */
66
+ private readonly svgDefs;
67
+ private readonly scratch;
68
+ private labelSyncPad;
69
+ /** 绑定的虚拟层(`paint` 里做标注的窗口同步要用它)。 */
70
+ private layer;
71
+ /** 上一次批量落墨的窗口(页面 / 探针读) */
72
+ lastView: VirtualChildView | null;
73
+ /** 是否在 `paint` 里自动做"标注窗口同步"(默认开;关掉可以自己驱动) */
74
+ syncLabelsOnPaint: boolean;
75
+ constructor(ice: any, options?: WaterVirtualDocOptions);
76
+ /** 建/重建空间索引(构造期 + 反序列化贴完编辑之后各一次)。 */
77
+ rebuildIndex(): void;
78
+ private __addNode;
79
+ boxAt(i: number, out: Float64Array): void;
80
+ /** 窗口遍历:网格候选 + 精确盒相交(标注也在内 —— 窗口同步要用它)。 */
81
+ forEachInBox(x0: number, y0: number, x1: number, y1: number, visit: (i: number) => void): void;
82
+ private windowStamp;
83
+ private readonly windowSeen;
84
+ /** 退化条目(末行的自连管线、零尺寸):不进索引、不落墨。 */
85
+ private __degenerate;
86
+ hitTest(lx: number, ly: number): number;
87
+ private __hitPrecise;
88
+ private __segDist;
89
+ paint(ctx: any, view: VirtualChildView): boolean;
90
+ /** 绑定虚拟层:标注的物化 / 回收要用引擎的助手(幂等物化 + 回收)。 */
91
+ attachLayer(layer: any): void;
92
+ /**
93
+ * **窗口内标注的物化 / 回收**(混合策略的应用侧循环)。
94
+ *
95
+ * 为什么标注不批量画:符号的位号是**每个实例都不同**的字符串,烤成精灵等于每个符号一张图;
96
+ * 而物化成真 `ICEText` 之后,引擎的**文本位图缓存**会把"同字号同内容的文字"复用起来
97
+ * (见引擎的 `ObjectCache`),窗口里几百个标注的实际成本远低于逐帧 `fillText`。
98
+ */
99
+ syncLabels(view?: VirtualChildView): void;
100
+ /**
101
+ * **文档的唯一写入口**(P2 第 3 条):undo/redo、属性面板这类程序化写都走它。
102
+ * 返回 false = 补丁不认识(引擎会原样返回 false,调用方自己决定怎么办)。
103
+ */
104
+ applyPatch(index: number, patch: Record<string, any>): boolean;
105
+ /**
106
+ * **物化组件被改动后的回流**(P2 第 3 条):引擎在用户拖完 / 属性面板改完后回调到这里,
107
+ * 文档立刻跟着变(不必等存盘)。
108
+ */
109
+ onChildPatched(index: number, patch: Record<string, any>): void;
110
+ /** 符号下标 → 它的标注下标(-1 = 这个文档没给标注留位)。 */
111
+ labelIndexOfSymbol(symbolIndex: number): number;
112
+ private __strokePipe;
113
+ /** 每次 paint 真正落墨的条目数(探针用) */
114
+ drawCalls: number;
115
+ /**
116
+ * 造一个真组件(**不挂树** —— 引擎的 `materializeVirtualChild` 负责挂进虚拟层)。
117
+ *
118
+ * 契约:必须在这里把 `done[i]` 置位,否则批量层会再画一遍。
119
+ */
120
+ materialize(i: number): any;
121
+ /** 窗口内标注的物化 / 回收(应用侧的"混合策略"循环;引擎只提供两个助手)。 */
122
+ windowItems(view: VirtualChildView, visit: (i: number) => void): void;
123
+ /** 管线的世界盒(正交路由:横 → 竖 → 横) */
124
+ private pipeBox;
125
+ /** 文档占用的"真数据"字节数(探针 / 反馈报告用) */
126
+ get bytes(): number;
127
+ /**
128
+ * **文档载荷**:只存"参数 + 少量编辑"(不存整份列存)。
129
+ *
130
+ * 为什么不是整份列存:列存是**确定性生成**的(参数一样 → 内容一样),所以载荷只需要
131
+ * `params` + `edits`(被拖过的那几个符号的坐标)。实测一份 6 万条目的文档载荷约几 KB,
132
+ * 而不是 3MB 的列存 —— 快照文件因此仍然是"可读、可 diff"的。
133
+ */
134
+ serializeDocument(): any;
135
+ /** 编辑过的符号(`[下标, x, y]`):物化组件被拖动后与列存的差异。 */
136
+ private readonly edits;
137
+ /** 物化组件的引用(写回与差异检测用):下标 → 组件。 */
138
+ readonly liveComponents: Map<number, any>;
139
+ /** 把已物化组件的坐标与列存对齐(拖过的写回列存,没动的记进 `edits`)。 */
140
+ syncEditsFromComponents(): void;
141
+ private recordEdit;
142
+ /** 由载荷重建(工厂用)。列存按参数确定性重建,再把 `edits` 贴回去。 */
143
+ static fromPayload(payload: any, ice: any): WaterVirtualDoc;
144
+ /** 文档包围盒(导出 / 适应视图用)。 */
145
+ documentBounds(out: Float64Array): boolean;
146
+ /**
147
+ * **全量 SVG 导出**(P2 第 1 条):把整份文档写进 sink。
148
+ *
149
+ * 复合符号的矢量表示用"每 kind 一份 def + 每个实例一条 `<use>`":def 由引擎的
150
+ * `exportSvg([模板组件])` 生成(**与画布同源**,不会两套几何),2 万个符号因此只有
151
+ * 21 份 def + 2 万条 use。标注与管线直接写(它们是纯文本 / 折线)。
152
+ */
153
+ paintToSvg(sink: any): boolean;
154
+ /** 用引擎导出**一个模板组件**的 SVG 片段(去掉 `<svg>` 外壳与 `<defs>`)。 */
155
+ private __svgFragmentOfKind;
156
+ /**
157
+ * 每种 kind 渲染一张离屏位图(**rs=2**,缩放贴图时不至于太糊)。
158
+ *
159
+ * 为什么走精灵而不是共享 `Path2D`:IED 的 `WaterSymbol` 是**复合组件**(矩形 + 栅条 / 曝气盘 / 刮泥桥…),
160
+ * 它的外观不是一条路径 —— 组件的离屏位图才是它的正确"批量表示"。
161
+ */
162
+ private __mintSprites;
163
+ }
164
+ export default WaterVirtualDoc;
@@ -60,7 +60,15 @@ export declare class WaterPipe extends FlowEdge {
60
60
  export default class WaterProcessDesigner extends FlowDesigner {
61
61
  constructor(ice: any);
62
62
  get nodes(): any[];
63
+ /**
64
+ * 选中判据:水务的符号不是 `FlowNode` 的分支(`WaterSymbol extends ICEGroup`),
65
+ * 所以必须在这里把它(与管线)补进"可点选"的集合 —— 否则点符号选不中,
66
+ * 属性面板永远显示不出来(2026-09-21 实测确认的既有缺陷,与虚拟化无关)。
67
+ */
68
+ protected isSelectableComponent(component: any): boolean;
63
69
  get edges(): any[];
70
+ protected isNodeComponent(component: any): boolean;
71
+ protected isEdgeComponent(component: any): boolean;
64
72
  /** 建一个符号(处理单元 / 设备 / 边界) */
65
73
  createSymbol(kind: WaterSymbolKind, props?: any): any;
66
74
  /** 建一段管线(要标介质;管径缺了会给 warning) */
@@ -60,7 +60,15 @@ export declare class WaterPipe extends FlowEdge {
60
60
  export default class WaterProcessDesigner extends FlowDesigner {
61
61
  constructor(ice: any);
62
62
  get nodes(): any[];
63
+ /**
64
+ * 选中判据:水务的符号不是 `FlowNode` 的分支(`WaterSymbol extends ICEGroup`),
65
+ * 所以必须在这里把它(与管线)补进"可点选"的集合 —— 否则点符号选不中,
66
+ * 属性面板永远显示不出来(2026-09-21 实测确认的既有缺陷,与虚拟化无关)。
67
+ */
68
+ protected isSelectableComponent(component: any): boolean;
63
69
  get edges(): any[];
70
+ protected isNodeComponent(component: any): boolean;
71
+ protected isEdgeComponent(component: any): boolean;
64
72
  /** 建一个符号(处理单元 / 设备 / 边界) */
65
73
  createSymbol(kind: WaterSymbolKind, props?: any): any;
66
74
  /** 建一段管线(要标介质;管径缺了会给 warning) */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "author": "大漠穷秋",
3
3
  "name": "ice-entity-designer",
4
- "version": "0.10.0",
4
+ "version": "0.12.0",
5
5
  "description": "An entity designer based on ice-render.",
6
6
  "repository": {
7
7
  "type": "git",
@@ -59,7 +59,7 @@
59
59
  "types:watch": "npm run types:check -- --watch"
60
60
  },
61
61
  "peerDependencies": {
62
- "ice-render": "^4.0.0",
62
+ "ice-render": "^4.2.0",
63
63
  "react": "^18 || ^19",
64
64
  "react-dom": "^18 || ^19"
65
65
  },
@@ -101,7 +101,7 @@
101
101
  "eslint-plugin-react-hooks": "4.6.0",
102
102
  "http-server": "14.1.1",
103
103
  "husky": "8.0.3",
104
- "ice-render": "^4.0.0",
104
+ "ice-render": "^4.2.0",
105
105
  "jest": "29.7.0",
106
106
  "jest-environment-jsdom": "29.7.0",
107
107
  "lint-staged": "15.5.2",