> **本文回答**：vjmapext 渲染桶（hot / cold / dynamic）原理 + 实体如何进哪个桶。

## 一、什么是渲染桶

`MapCadLayer` 内部把实体按**状态**分成 3 个桶——每个桶是一条独立的 GeoJSON source（`mapcad-hot` / `mapcad-cold` / `mapcad-dynamic`），样式表按 `_active` 属性区分高亮。feature `properties` 里的实体回链键是 **`_entity_id`**（另有 `_entity_type`/`_role`/`_meta`），调试用 `map.getSource('mapcad-cold')._data.features` 直读比 `querySourceFeatures`（走瓦片管线，异步生成）更稳：

| 桶 | 何时进 | 特点 |
|---|---|---|
| **cold**（冷） | 普通实体：未选中、未高亮、非 dynamic | 绝大多数实体在这；桶脏时整桶 `setData` 重传 |
| **hot**（热） | **被选中（isSelected）或高亮（isHighlighted）的实体** | 桶很小，选中态单独走 `_active` 高亮样式 |
| **dynamic**（动态） | 设了 `entity.dynamic = true`（且未选中） | 专为高频变化实体准备的小桶 |

> 注意：三桶都是 GeoJSON source，**没有 `DYNAMIC_DRAW` 级区别**（`DYNAMIC_DRAW` 是 FX instancing 的内部细节）；`dynamic` 的收益是"小桶重传便宜"，不是特殊 buffer 类型。

## 二、流转规则

每次增量更新（`_incrementalUpdate`）都按同一条规则重新分类：

```
store.isSelected(id) || store.isHighlighted(id)  →  hot
否则 entity.dynamic === true                     →  dyn
否则                                             →  cold
```

- `addEntity` → 默认进 cold（除非一进来就被选中 / dynamic）
- **选中实体** → 升入 hot；**取消选中** → 降回 cold / dyn
- `changed()` 的实体 → 从原桶摘除、重新编码、按上表重新分桶；所在桶标脏 → 下一帧该桶 `setData` 重传
- `styleChanged()` 走 `updateStyleProps` 快路径：只重算样式属性，不重编码几何（桶照样标脏重传）

**没有"hot 静置 N 秒后自动降级"机制**——hot 完全由选中/高亮状态决定，取消选中即离开。

## 三、什么时候用 dynamic

当实体**每帧都要变位置 / 大小**（比如飞线、闪烁、路径跟随）：

```js
const poly = new vjmapext.PolylineEnt(pts);
poly.dynamic = true;
mapcadLayer.addEntity(poly);

setInterval(() => {
  const vs = newPoints();
  poly.bulgePoints = vs.map(([x, y]) => ({ point: new vjmapext.Point2D(x, y), bulge: 0 }));
  poly.changed();           // 高效：只改 dynamic buffer
}, 16);
```

如果不设 dynamic，cold 桶每次都会整桶 rebuild，1 万实体下一帧卡死。

## 四、hot 桶 = 选中/高亮桶

`isSelected || isHighlighted` 为 true 的实体进 hot 桶，feature 打上 `_active = "true"`，由样式表里的 `_active` 过滤器渲染成高亮样式。

- 用户 click 选中 → 实体升入 hot，立刻显示选中态
- 取消选中 / `store.setSelection([])` → 降回 cold（或 dyn）
- hot 桶始终很小 → 选中态变化的重传代价极低

> 选中几百上千实体时 hot 桶同样会变大——超大选集的高亮渲染成本与选集大小成正比。

## 五、查看当前桶分布（调试）

`renderPipeline` 无公开的桶统计 API；可在浏览器 devtools 里查看实例的 `_hotFeatures` / `_coldFeatures` / `_dynFeatures` 集合大小（私有字段，仅供调试，勿依赖）。

如果 hot 一直很大（上千）→ 意味着有"隐形"反复变化的实体，需排查。

## 六、性能对比（测试数字仅供参考）

| 场景 | cold 桶 | hot 桶 | dynamic 桶 |
|---|---|---|---|
| 1 万实体静态显示 | 60 FPS | - | - |
| 加 1 条新实体 | 60 → 59 FPS（rebuild 一次） | 60 FPS | 60 FPS |
| 每帧改 100 条颜色 | 30 FPS（每帧 rebuild） | 50 FPS | 60 FPS |
| 每帧改 100 条几何 | 5 FPS | 20 FPS | 58 FPS |

## 七、什么时候**不**要设 dynamic

- 实体只是**偶尔**变（比如用户点一次改一次颜色）→ 默认就好
- 实体数 < 100 且都会动 → 默认 cold 也能 60 FPS

## 八、cold 桶批量加的最优做法

```js
// 正确：直接循环——addEntity 只标脏，重绘按 requestAnimationFrame 合并成一帧
for (const p of data) mapcadLayer.addEntity(toEnt(p));

// 注意：不要在循环里穿插 await / 异步，会把添加摊到多个帧
```

详见 [batch-rendering](batch-rendering.md) 和 [batch/entity-bulk-add](../batch/entity-bulk-add.md)。

## 九、常见坑

1. **期望实体高频变化但没设 dynamic** → 每帧 rebuild 整个 cold buffer，几千实体下 FPS 断崖。
2. **所有实体都设 dynamic** → dynamic 桶膨胀，每帧都要走完整编码路径，反而不如 cold。
3. **忘记 `deleteEntity` 的动态实体**：实体一直在 store 和 dyn 桶里，每帧随 dyn source `setData` 上传；不存在"slot 溢出"，纯粹是白白吃内存和带宽。
4. **标脏后画面不更新**：确认确实调了 `changed()`/`styleChanged()`；只改字段不标脏不会触发重绘。
5. **dynamic + styleChanged**：同样有效且更省——`updateStyleProps` 原地改缓存 feature 的样式属性，连 `toGeoJSON` 重编码都跳过；只改样式时优先用它而不是 `changed()`。
6. **一个"毒实体"拖死整条管线**：store 里任一实体的点字段被赋 `{x,y}`/数组字面量（而非 `Point2D` 实例）时，`_fullRebuild` → `renderCache.getOrCompute` → `toGeoJSON`/`clone` 抛 `clone is not a function`，**整帧渲染中断——三个桶全部清空，且页面无业务层报错**（异常发生在 rAF 回调里，仅见 pageerror）。更糟的是 `store.delete(id)` 也会崩：`undoManager.recordDelete` → `entity.clone()` 同样抛错，毒实体连正常删除都走不通。应急清除：先 `store._undoManager = null` 绕过 undo 快照再 `store.delete(id)`（删完恢复引用），或定位后整体 `fromJSON` 重建。
7. **挂载时机**：`addControl` 要么在 map 'load' 之前，要么在 `map.loaded()` 为真时（官方示例是 `await map.onLoad()` 后紧接着挂，这两种都安全）。onLoad 之后又等了别的异步、地图正加载新瓦片时才挂，`loaded()` 为 false 而 'load' 不再发 → onAdd 的 `loaded()?doConnect():on('load',doConnect)` 两路落空 → 桶永远不建、画面静默空。补救：`map.fire('load')` 重发事件。另注意 `removeControl` 会 `removeSourcesAndLayers` 拆掉**共享**的 mapcad-* 桶——同一 map 挂第二个实例再移除，会把第一个的桶也清掉且不重建。

## 相关

- dynamic 详细 → [dynamic-entities](dynamic-entities.md)
- 样式快路径 → [style-only-fast-path](style-only-fast-path.md)
- 批量 → [batch-rendering](batch-rendering.md)
