唯杰地图 logo唯杰地图
VJMAP
EN

batch-lag

vjmapext/references/trouble/batch-lag.md 4.1 KB139 行
原文

本文回答:批量 addEntity / setData 卡顿 —— 没合并 dirty / 没 bulk。

一、症状

  • 循环 addEntity 1 万条 → 页面卡死 3~5 秒
  • 循环改 GeoJSON feature → FPS 跌到 5
  • 改颜色改透明度 → 每次都有感明显卡

二、根因:每条操作都触发一次重渲染

for (const d of data) {
  mapcadLayer.addEntity(new vjmapext.PointEnt(d));
  // 每次 addEntity:标脏 + 写空间索引 + 发 entity:added 事件
}

注意 RAF 合并保证渲染只发生一次——1 万次循环的真实开销在 JS 侧(实体构造、toGeoJSON 编码、空间索引插入),不是 1 万次渲染。卡的根因是单次循环量大,不是"没合并渲染"。

三、解法:直接循环(无 bulk API,RAF 自动合并)

// ✅ 直接循环即可——addEntity 只标脏,重绘按 requestAnimationFrame 合并到下一帧
for (const d of data) {
  mapcadLayer.addEntity(new vjmapext.PointEnt(d));
}

要点:循环内不要穿插 await(会把添加摊到多帧);要一步撤销可包 undoManager.beginTransaction()/endTransaction()。

四、GeoJSON 批量

// ❌
for (const f of newFeatures) {
  fc.features.push(f);
  src.setData(fc);        // 每次重排索引
}

// ✅
fc.features.push(...newFeatures);
src.setData(fc);          // 一次

五、大量样式变化(只改颜色)

// ❌ 慢:changed() 全量
for (const e of ents) {
  e.color = 0xff0000;
  e.changed();
}

// ✅ 快:styleChanged()
for (const e of ents) {
  e.color = 0xff0000;
  e.styleChanged();
}

六、FX 批量

// 两者性能相当:都只写 CPU 影子缓冲,下一帧合并成一次 GPU 上传
for (const p of points) fx.addPoint('fire', { cad: p, size: 30 });
fx.addPoints('fire', points.map(p => ({ cad: p, size: 30 })));   // 写法更简洁

FX 卡顿多半不是"逐个 add"造成的,而是实例数/预设档位过高:看 fx.getMetrics() 的 live 数,降 fx.setQuality('lite'),海量同形点改 fx.symbolImage。

七、流式加载(不阻塞 UI)

1000 条还 OK,10 万条 bulk 也可能让 UI 卡 1~2 秒。分批:

const BATCH = 500;
let idx = 0;

function loadBatch() {
  const end = Math.min(idx + BATCH, data.length);
  for (; idx < end; idx++) {
    mapcadLayer.addEntity(toEnt(data[idx]));
  }
  if (idx < data.length) requestAnimationFrame(loadBatch);
}
loadBatch();

每帧 500 条,用户看到数据"流入"而非一卡。

八、Web Worker 预处理

大批量 CSV/JSON 解析放 Worker:

// worker.js
self.onmessage = e => {
  const text = e.data;
  const ents = parseCSV(text).map(r => ({ type: 'Point', coords: [r.x, r.y] }));
  self.postMessage(ents);
};

// main
const w = new Worker('worker.js');
w.postMessage(csvText);
w.onmessage = e => {
  for (const d of e.data) mapcadLayer.addEntity(toEnt(d));
};

九、诊断流程

  1. F12 → Performance → 录制 bulk 加载过程
  2. 看主线程火焰图里最大的块
  3. 如果是 rebuild bucket / buffer upload 多次 → 检查是不是每帧都在改实体
  4. 如果是 encode / setData 多次 → 没合并
  5. 如果是 parseJSON / forEach → 考虑 Worker

十、和 dynamic 的关系

批量添加和 dynamic 解决不同问题:

  • 一次加载 1 万静态实体 → 直接循环 addEntity(RAF 合并渲染)+ 不 dynamic
  • 持续改 100 条位置 → dynamic

十一、常见坑

  1. 循环里穿插 await:把添加摊到多帧、拉长总耗时;同步循环一把加完。
  2. 循环 await 大量异步:每条都 await 网络 → 串行非常慢;用 Promise.all 并发。
  3. GeoJSON source 频繁 setData:source.setData 每次重排索引;先合并成完整 FeatureCollection 再设。
  4. 改了 100 条几何 + 100 条只改色:分开处理,几何用 changed(),样式用 styleChanged()。

相关