web-map/docs/场景编辑器组件详细分析.md

52 KiB
Raw Permalink Blame History

场景编辑器组件详细分析

1. 组件概述

scene-editor.vue 是一个基于 Vue 3 的复杂场景编辑器组件,主要用于管理和编辑工业机器人的场景配置。该组件提供了完整的场景编辑功能,包括机器人管理、路径规划、区域设置等。

2. 架构图示分析

2.1 组件整体架构图

graph TB
    subgraph "Scene Editor Component"
        A[scene-editor.vue] --> B[EditorService]
        A --> C[RobotGroups]
        A --> D[PenGroups]
        A --> E[EditorToolbar]
        A --> F[Detail Cards]

        B --> B1[Meta2d Engine]
        B --> B2[Canvas Rendering]
        B --> B3[Event System]

        C --> C1[Robot Management]
        C --> C2[Group Operations]

        D --> D1[Points Management]
        D --> D2[Routes Management]
        D --> D3[Areas Management]

        E --> E1[Drawing Tools]
        E --> E2[Operations]

        F --> F1[Robot Detail]
        F --> F2[Point Detail]
        F --> F3[Route Detail]
        F --> F4[Area Detail]
    end

    subgraph "External Dependencies"
        G[Scene API]
        H[Robot API]
        I[Map API]
        J[File System]
    end

    A --> G
    A --> H
    B --> I
    A --> J

2.2 数据流架构图

sequenceDiagram
    participant U as User
    participant SE as SceneEditor
    participant ES as EditorService
    participant API as SceneAPI
    participant Canvas as Meta2d Canvas

    U->>SE: Load Scene
    SE->>API: getSceneById(id)
    API-->>SE: Scene Data
    SE->>ES: load(sceneData)
    ES->>Canvas: Render Elements

    U->>SE: Edit Element
    SE->>ES: updatePen/addArea/addPoint
    ES->>Canvas: Update Render
    ES->>SE: Emit Change Event
    SE->>SE: Update UI State

    U->>SE: Save Scene
    SE->>ES: save()
    ES-->>SE: JSON String
    SE->>API: saveSceneById(id, json)
    API-->>SE: Success Response

2.3 EditorService 内部架构图

graph LR
    subgraph "EditorService Core"
        A[Meta2d Base] --> B[Event System]
        A --> C[Canvas Layer]
        A --> D[State Management]

        B --> B1[Mouse Events]
        B --> B2[Change Events]
        B --> B3[RxJS Streams]

        C --> C1[Point Rendering]
        C --> C2[Route Rendering]
        C --> C3[Area Rendering]
        C --> C4[Robot Rendering]

        D --> D1[Current Selection]
        D --> D2[Robot Groups]
        D --> D3[Elements Cache]
    end

    subgraph "Rendering Pipeline"
        E[drawPoint] --> F[Canvas Context]
        G[drawLine] --> F
        H[drawArea] --> F
        I[drawRobot] --> F
    end

    C1 --> E
    C2 --> G
    C3 --> H
    C4 --> I

3. 核心功能分析

3.1 场景数据管理

  • 场景读取: 通过 getSceneById API 获取场景数据
  • 场景推送: 通过 pushSceneById API 将场景数据推送到数据库
  • 场景保存: 通过编辑器服务保存场景配置
  • 文件导入/导出: 支持 .scene 格式文件的导入导出

3.2 编辑器状态控制

  • 编辑模式切换: 通过 editable 状态控制编辑器的启用/禁用
  • 权限管理: 根据编辑状态显示不同的操作按钮和功能
  • 实时状态同步: 编辑状态变化时自动更新编辑器服务状态

3.3 三大管理区域

  • 机器人管理: 显示和管理场景中的机器人组和单个机器人
  • 库区管理: 管理各种类型的库区(仅显示库区类型的区域)
  • 高级组管理: 管理复杂的路径、点位、区域等元素

3.4 详情卡片系统

  • 动态卡片显示: 根据选中元素类型显示对应的详情卡片
  • 编辑/查看模式: 根据编辑状态显示编辑卡片或查看卡片
  • 悬浮定位: 卡片固定在右侧悬浮显示

4. 大场景渲染性能优化分析

4.1 性能瓶颈识别

4.1.1 主要性能问题

graph TD
    A[大场景性能问题] --> B[元素数量过多]
    A --> C[频繁重绘]
    A --> D[内存泄漏]
    A --> E[事件处理]

    B --> B1[点位: 1000+ 个]
    B --> B2[路线: 5000+ 条]
    B --> B3[区域: 100+ 个]
    B --> B4[机器人: 50+ 个]

    C --> C1[每次状态变更全量重绘]
    C --> C2[鼠标移动频繁触发]
    C --> C3[RxJS 防抖不足]

    D --> D1[大量 DOM 监听器]
    D --> D2[Canvas 上下文未释放]
    D --> D3[图片资源未缓存]

    E --> E1[hit-test 计算复杂]
    E --> E2[事件冒泡处理]

4.1.2 当前代码中的性能问题点

// 问题1: 频繁的全量数据更新
public readonly pens = useObservable<MapPen[]>(
  this.#change$$.pipe(
    filter((v) => v),
    debounceTime(100), // 防抖时间过短
    map(() => this.data().pens), // 每次返回全量数据
  ),
);

// 问题2: 复杂的过滤操作
const robots = computed<RobotInfo[]>(() =>
  editor.value.robots.filter(({ label }) =>
    label.includes(keyword.value) // 每次重新过滤全部数据
  )
);

// 问题3: 同步的大量元素创建
async #loadScenePoints(points?: StandardScenePoint[]): Promise<void> {
  if (!points?.length) return;
  await Promise.all( // 并发创建所有点位,可能导致界面卡顿
    points.map(async (v) => {
      // ... 创建逻辑
    }),
  );
}

4.2 性能优化策略

4.2.1 虚拟化渲染优化

// 优化建议1: 实现视口裁剪
class ViewportCulling {
  private viewport: Rect;
  private visibleElements: Map<string, MapPen> = new Map();

  updateViewport(viewport: Rect): void {
    this.viewport = viewport;
    this.updateVisibleElements();
  }

  private updateVisibleElements(): void {
    const elements = this.getAllElements();
    this.visibleElements.clear();

    elements.forEach((element) => {
      if (this.isInViewport(element)) {
        this.visibleElements.set(element.id, element);
      }
    });
  }

  private isInViewport(element: MapPen): boolean {
    const elementRect = this.getElementRect(element);
    return this.rectIntersects(this.viewport, elementRect);
  }
}

// 优化建议2: 层级渲染
class LayeredRenderer {
  private staticLayer: CanvasRenderingContext2D; // 静态elementos点位、区域
  private dynamicLayer: CanvasRenderingContext2D; // 动态elementos机器人、路径
  private uiLayer: CanvasRenderingContext2D; // UI层选中状态、工具提示

  render(): void {
    this.renderStaticLayer(); // 仅在元素变更时重绘
    this.renderDynamicLayer(); // 频繁重绘
    this.renderUILayer(); // 交互时重绘
  }
}

4.2.2 数据结构优化

// 优化建议3: 使用空间索引
class SpatialIndex {
  private quadTree: QuadTree<MapPen>;

  insert(element: MapPen): void {
    const bounds = this.getElementBounds(element);
    this.quadTree.insert(element, bounds);
  }

  query(viewport: Rect): MapPen[] {
    return this.quadTree.query(viewport);
  }
}

// 优化建议4: 缓存计算结果
class RenderCache {
  private pathCache: Map<string, Path2D> = new Map();
  private imageCache: Map<string, HTMLImageElement> = new Map();

  getPath(element: MapPen): Path2D {
    const key = this.getPathKey(element);
    if (!this.pathCache.has(key)) {
      this.pathCache.set(key, this.createPath(element));
    }
    return this.pathCache.get(key)!;
  }
}

4.2.3 事件处理优化

// 优化建议5: 事件委托和节流
class EventOptimizer {
  private mouseThrottle = throttle(this.handleMouseMove.bind(this), 16); // 60fps

  setupEventListeners(): void {
    // 使用事件委托,减少监听器数量
    this.canvas.addEventListener('mousemove', this.mouseThrottle);
    this.canvas.addEventListener('click', this.handleClick);
  }

  private handleMouseMove(event: MouseEvent): void {
    // 只处理必要的鼠标移动事件
    const elements = this.spatialIndex.query(this.getMouseViewport(event));
    this.updateHoverState(elements);
  }
}

4.2.4 内存管理优化

// 优化建议6: 对象池模式
class ObjectPool<T> {
  private pool: T[] = [];

  acquire(): T {
    return this.pool.pop() || this.create();
  }

  release(obj: T): void {
    this.reset(obj);
    this.pool.push(obj);
  }
}

// 优化建议7: 及时清理资源
class ResourceManager {
  private observers: Set<() => void> = new Set();

  cleanup(): void {
    this.observers.forEach((cleanup) => cleanup());
    this.observers.clear();

    // 清理Canvas上下文
    this.clearCanvasContexts();

    // 清理图片缓存
    this.clearImageCache();
  }
}

4.3 具体优化实施建议

4.3.1 分批加载策略

// 建议实现: 分批加载大量元素
async loadSceneInBatches(scene: StandardScene): Promise<void> {
  const BATCH_SIZE = 100;

  // 分批加载点位
  if (scene.points?.length) {
    for (let i = 0; i < scene.points.length; i += BATCH_SIZE) {
      const batch = scene.points.slice(i, i + BATCH_SIZE);
      await this.loadPointsBatch(batch);
      await this.nextTick(); // 让出主线程
    }
  }

  // 分批加载路线
  if (scene.routes?.length) {
    for (let i = 0; i < scene.routes.length; i += BATCH_SIZE) {
      const batch = scene.routes.slice(i, i + BATCH_SIZE);
      await this.loadRoutesBatch(batch);
      await this.nextTick();
    }
  }
}

private nextTick(): Promise<void> {
  return new Promise(resolve => setTimeout(resolve, 0));
}

4.3.2 LODLevel of Detail优化

// 建议实现: 根据缩放级别调整渲染详度
class LODRenderer {
  private renderLevel = 0;

  updateRenderLevel(scale: number): void {
    if (scale > 2)
      this.renderLevel = 3; // 高详度
    else if (scale > 1)
      this.renderLevel = 2; // 中详度
    else if (scale > 0.5)
      this.renderLevel = 1; // 低详度
    else this.renderLevel = 0; // 最低详度
  }

  renderPoint(ctx: CanvasRenderingContext2D, pen: MapPen): void {
    switch (this.renderLevel) {
      case 0:
        this.renderPointSimple(ctx, pen);
        break;
      case 1:
        this.renderPointNormal(ctx, pen);
        break;
      case 2:
        this.renderPointDetailed(ctx, pen);
        break;
      case 3:
        this.renderPointHighDetail(ctx, pen);
        break;
    }
  }
}

5. 异步区域绘制深层原因分析

5.1 为什么使用 async/await

5.1.1 代码分析

// 关键代码段分析
public async addArea(p1: Point, p2: Point, type = MapAreaType.库区, id?: string) {
  // ... 前置逻辑
  const area = await this.addPen(pen, true, true, true);
  //             ↑ 这里是关键 - addPen 是异步的
  this.bottom(area);
}

public async addPoint(p: Point, type = MapPointType.普通点, id?: string): Promise<void> {
  // ... 创建pen对象
  await this.addPen(pen, false, true, true);
  //     ↑ 同样是异步调用
}

5.1.2 深层原因分析图

graph TD
    A[addArea/addPoint 调用] --> B[创建 MapPen 对象]
    B --> C[调用 addPen 方法]
    C --> D{addPen 为什么异步?}

    D --> E[Canvas 渲染队列]
    D --> F[DOM 更新时机]
    D --> G[图片资源加载]
    D --> H[动画系统集成]

    E --> E1[Canvas需要等待渲染完成]
    E --> E2[避免渲染冲突]
    E --> E3[批量更新优化]

    F --> F1[等待浏览器重绘]
    F --> F2[确保DOM状态同步]

    G --> G1[点位图标加载]
    G --> G2[机器人图片加载]
    G --> G3[主题相关资源]

    H --> H1[过渡动画]
    H --> H2[缩放动画]
    H --> H3[状态切换动画]

5.2 Meta2d 底层机制分析

5.2.1 addPen 异步的根本原因

// Meta2d 内部可能的实现机制 (推测)
class Meta2d {
  async addPen(pen: Pen, history?: boolean, render?: boolean, doEvent?: boolean): Promise<Pen> {
    // 1. 资源预加载 - 确保图片等资源准备就绪
    if (pen.image) {
      await this.loadImage(pen.image);
    }

    // 2. 渲染管道同步 - 等待当前渲染任务完成
    await this.renderQueue.nextTick();

    // 3. 添加到画布数据结构
    this.store.data.pens.push(pen);

    // 4. 计算布局和碰撞检测
    await this.calculateLayout(pen);

    // 5. 触发重绘
    if (render) {
      await this.render();
    }

    // 6. 触发事件
    if (doEvent) {
      this.emit('add', pen);
    }

    return pen;
  }

  private async loadImage(src: string): Promise<HTMLImageElement> {
    return new Promise((resolve, reject) => {
      const img = new Image();
      img.onload = () => resolve(img);
      img.onerror = reject;
      img.src = src;
    });
  }

  private async calculateLayout(pen: Pen): Promise<void> {
    // 复杂的布局计算可能需要多帧完成
    return new Promise((resolve) => {
      requestAnimationFrame(() => {
        this.updatePenBounds(pen);
        this.updateSpatialIndex(pen);
        resolve();
      });
    });
  }
}

5.2.2 异步的必要性分析

sequenceDiagram
    participant User as User Action
    participant Service as EditorService
    participant Meta2d as Meta2d Engine
    participant Canvas as Canvas Context
    participant Browser as Browser

    User->>Service: addArea()
    Service->>Meta2d: addPen(pen)

    Note over Meta2d: 检查是否需要加载图片资源
    Meta2d->>Browser: 加载图片 (async)
    Browser-->>Meta2d: 图片加载完成

    Note over Meta2d: 等待渲染队列空闲
    Meta2d->>Meta2d: 添加到数据结构

    Note over Meta2d: 计算元素边界和布局
    Meta2d->>Canvas: 请求重绘
    Canvas->>Browser: requestAnimationFrame
    Browser-->>Canvas: 下一帧回调

    Meta2d-->>Service: 返回创建的元素
    Service->>Service: 调用 bottom() 设置层级

5.3 性能影响分析

5.3.1 异步的性能优势

// 优势1: 避免阻塞主线程
// 同步版本 (假设的问题版本)
public addAreaSync(p1: Point, p2: Point): void {
  const pen = this.createPen();
  this.addPenSync(pen); // 会阻塞主线程
  this.render();        // 立即渲染,可能导致卡顿
}

// 异步版本 (当前实现)
public async addArea(p1: Point, p2: Point): Promise<void> {
  const pen = this.createPen();
  await this.addPen(pen); // 非阻塞,允许其他任务执行
  this.bottom(pen);       // 确保pen已经正确添加后再操作
}

5.3.2 批量操作的性能考虑

// 当前的批量加载实现
async #loadSceneAreas(areas?: StandardSceneArea[]): Promise<void> {
  if (!areas?.length) return;
  await Promise.all( // 并发执行,但可能导致资源竞争
    areas.map(async (v) => {
      await this.addArea({ x: v.x, y: v.y }, { x: v.x + v.w, y: v.y + v.h }, v.type, v.id);
    }),
  );
}

// 优化建议: 控制并发数量
async #loadSceneAreasOptimized(areas?: StandardSceneArea[]): Promise<void> {
  if (!areas?.length) return;

  const CONCURRENT_LIMIT = 5; // 限制并发数量
  for (let i = 0; i < areas.length; i += CONCURRENT_LIMIT) {
    const batch = areas.slice(i, i + CONCURRENT_LIMIT);
    await Promise.all(
      batch.map(async (v) => {
        await this.addArea(
          { x: v.x, y: v.y },
          { x: v.x + v.w, y: v.y + v.h },
          v.type,
          v.id
        );
      })
    );
    // 每批次之间给主线程喘息时间
    await new Promise(resolve => setTimeout(resolve, 10));
  }
}

6. 技术架构分析

6.1 核心依赖关系

// 主要导入依赖
import { getSceneById, pushSceneById } from '@api/scene'; // 场景API
import { EditorService } from '@core/editor.service'; // 编辑器服务
import { decodeTextFile, downloadFile, selectFile, textToBlob } from '@core/utils'; // 工具函数

6.2 组件架构设计

6.2.1 状态管理

// 核心状态定义
const title = ref<string>(''); // 场景标题
const editable = ref<boolean>(false); // 编辑状态
const show = ref<boolean>(true); // 卡片显示状态
const current = ref<{ type: string; id: string }>(); // 当前选中元素
const container = shallowRef<HTMLDivElement>(); // 编辑器容器
const editor = shallowRef<EditorService>(); // 编辑器服务实例

6.2.2 依赖注入系统

const EDITOR_KEY = Symbol('editor-key');
provide(EDITOR_KEY, editor);

使用 Vue 3 的依赖注入机制,将编辑器服务注入到子组件中。

6.3 EditorService 核心服务分析

6.3.1 服务基础

export class EditorService extends Meta2d {
  // 继承自 Meta2d 图形引擎
  // 提供场景编辑的核心功能
}

6.3.2 核心方法

  • load(): 加载场景数据到编辑器
  • save(): 保存当前场景数据
  • setState(): 设置编辑器状态(可编辑/只读)
  • updateRobots(): 更新机器人数据
  • addArea(): 添加区域
  • deleteById(): 删除指定元素

6.4 API 接口设计

6.4.1 场景相关API

// 获取场景数据
export async function getSceneById(id: string): Promise<SceneDetail | null>;

// 推送场景到数据库
export async function pushSceneById(id: string): Promise<boolean>;

// 保存场景数据
export async function saveSceneById(id: string, json: string, png?: string): Promise<boolean>;

6.4.2 文件操作工具

// 文件选择
export async function selectFile(accept?: string, limit?: number): Promise<File | void>;

// 文件解码
export async function decodeTextFile(file: File): Promise<string | undefined>;

// 文本转二进制
export function textToBlob(text: string): Blob | undefined;

// 文件下载
export function downloadFile(url: string, name?: string): void;

7. 从零开发实现过程

7.1 第一步:创建基础组件结构

<script setup lang="ts">
// 1. 定义组件属性
type Props = {
  id: string; // 场景ID
};
const props = defineProps<Props>();

// 2. 基础状态管理
const title = ref<string>('');
const editable = ref<boolean>(false);
</script>

<template>
  <a-layout class="full">
    <!-- 头部区域 -->
    <a-layout-header class="p-16" style="height: 64px">
      <!-- 标题和操作按钮 -->
    </a-layout-header>

    <!-- 主体区域 -->
    <a-layout class="p-16">
      <!-- 左侧面板 -->
      <a-layout-sider :width="320">
        <!-- 选项卡内容 -->
      </a-layout-sider>

      <!-- 编辑器区域 -->
      <a-layout-content>
        <div ref="container" class="editor-container full"></div>
      </a-layout-content>
    </a-layout>
  </a-layout>
</template>

7.2 第二步:集成编辑器服务

// 1. 导入编辑器服务
import { EditorService } from '@core/editor.service';

// 2. 创建编辑器实例
const container = shallowRef<HTMLDivElement>();
const editor = shallowRef<EditorService>();

// 3. 组件挂载时初始化编辑器
onMounted(() => {
  editor.value = new EditorService(container.value!);
});

// 4. 设置依赖注入
const EDITOR_KEY = Symbol('editor-key');
provide(EDITOR_KEY, editor);

7.3 第三步:实现场景数据管理

// 1. 导入API
import { getSceneById, pushSceneById } from '@api/scene';

// 2. 读取场景数据
const readScene = async () => {
  const res = await getSceneById(props.id);
  title.value = res?.label ?? '';
  editor.value?.load(res?.json, editable.value);
};

// 3. 推送场景数据
const pushScene = async () => {
  const res = await pushSceneById(props.id);
  if (!res) return Promise.reject();
  message.success(t('场景推送成功'));
  return Promise.resolve();
};

// 4. 监听场景ID变化
watch(
  () => props.id,
  () => readScene(),
  { immediate: true, flush: 'post' },
);

7.4 第四步:实现文件导入导出

// 1. 导入工具函数
import { decodeTextFile, downloadFile, selectFile, textToBlob } from '@core/utils';

// 2. 导入场景文件
const importScene = async () => {
  const file = await selectFile('.scene');
  if (!file?.size) return;
  const json = await decodeTextFile(file);
  editor.value?.load(json, editable.value);
};

// 3. 导出场景文件
const exportScene = () => {
  const json = editor.value?.save();
  if (!json) return;
  const blob = textToBlob(json);
  if (!blob?.size) return;
  const url = URL.createObjectURL(blob);
  downloadFile(url, `${title.value || 'unknown'}.scene`);
  URL.revokeObjectURL(url);
};

7.5 第五步:集成管理组件

<template>
  <a-layout-sider :width="320">
    <a-tabs type="card">
      <!-- 机器人管理 -->
      <a-tab-pane key="1" :tab="$t('机器人')">
        <RobotGroups
          v-if="editor"
          :token="EDITOR_KEY"
          :sid="id"
          :editable="editable"
          :current="current?.id"
          @change="selectRobot"
          show-group-edit
        />
      </a-tab-pane>

      <!-- 库区管理 -->
      <a-tab-pane key="2" :tab="$t('库区')">
        <PenGroups v-if="editor" :token="EDITOR_KEY" :current="current?.id" only-area1 />
      </a-tab-pane>

      <!-- 高级组管理 -->
      <a-tab-pane key="3" :tab="$t('高级组')">
        <PenGroups v-if="editor" :token="EDITOR_KEY" :current="current?.id" />
      </a-tab-pane>
    </a-tabs>
  </a-layout-sider>
</template>

7.6 第六步:实现选中元素监听

// 1. 监听编辑器选中元素
watch(
  () => editor.value?.selected.value[0],
  (v) => {
    const pen = editor.value?.getPenById(v);
    if (pen?.id) {
      current.value = { type: pen.name as 'point' | 'line' | 'area', id: pen.id };
      return;
    }
    if (current.value?.type === 'robot') return;
    current.value = undefined;
  },
);

// 2. 计算选中元素类型
const isRobot = computed(() => current.value?.type === 'robot');
const isPoint = computed(() => current.value?.type === 'point');
const isRoute = computed(() => current.value?.type === 'line');
const isArea = computed(() => current.value?.type === 'area');

// 3. 机器人选择处理
const selectRobot = (id: string) => {
  current.value = { type: 'robot', id };
  editor.value?.inactive();
};

7.7 第七步:添加工具栏和详情卡片

<template>
  <!-- 工具栏 -->
  <div v-if="editable" class="toolbar-container">
    <EditorToolbar v-if="editor" :token="EDITOR_KEY" :id="id" />
  </div>

  <!-- 详情卡片 -->
  <template v-if="current?.id">
    <a-float-button style="top: 80px; right: 16px" shape="square" @click="show = !show">
      <template #icon><i class="icon detail" /></template>
    </a-float-button>

    <div v-if="show" class="card-container">
      <RobotDetailCard v-if="isRobot" :token="EDITOR_KEY" :current="current.id" />

      <template v-if="isPoint">
        <PointEditCard v-if="editable" :token="EDITOR_KEY" :id="current.id" />
        <PointDetailCard v-else :token="EDITOR_KEY" :current="current.id" />
      </template>

      <!-- 其他卡片类型... -->
    </div>
  </template>
</template>

8. 子组件详细分析

8.1 RobotGroups 组件

功能: 管理机器人组和单个机器人 核心特性:

  • 机器人组的增删改查
  • 机器人的添加、注册、移除
  • 批量操作支持(全选、批量移除)
  • 搜索过滤功能

关键实现:

// 机器人列表获取
const robots = computed<RobotInfo[]>(() => editor.value.robots.filter(({ label }) => label.includes(keyword.value)));

// 批量选择管理
const selected = reactive<Set<RobotInfo['id']>>(new Set());
const selectAll = (checked: boolean) => {
  if (checked) {
    robots.value.forEach(({ id }) => selected.add(id));
  } else {
    selected.clear();
  }
};

8.2 PenGroups 组件

功能: 管理点位、路线、区域等绘制元素 核心特性:

  • 分类显示不同类型的元素(点位、路线、区域)
  • 支持筛选特定类型(如仅显示库区)
  • 搜索过滤功能
  • 点击选中功能

关键实现:

// 点位列表
const points = computed<MapPen[]>(() =>
  editor.value.points.value.filter(({ label }) => label?.includes(keyword.value)),
);

// 区域列表(按类型分组)
const areas = computed<MapPen[]>(() => editor.value.areas.value.filter(({ label }) => label?.includes(keyword.value)));

8.3 EditorToolbar 组件

功能: 提供编辑工具栏 核心特性:

  • 区域添加工具(库区、互斥区、非互斥区)
  • 场景保存功能
  • 撤销/重做操作
  • 删除操作

关键实现:

// 区域添加模式
const mode = ref<MapAreaType>();
watch(editor.value.mouseBrush, (v) => {
  if (!mode.value) return;
  const [p1, p2] = v ?? [];
  if (isEmpty(p1) || isEmpty(p2)) return;
  editor.value.addArea(p1, p2, mode.value);
  mode.value = undefined;
});

9. 样式设计分析

9.1 布局结构

  • 头部: 固定高度64px包含标题和操作按钮
  • 主体: 左侧面板320px宽度右侧编辑器自适应
  • 工具栏: 固定在底部中央,悬浮显示
  • 详情卡片: 固定在右侧320px宽度悬浮显示

9.2 核心样式

.editor-container {
  background-color: transparent !important;
}

.toolbar-container {
  position: fixed;
  bottom: 40px;
  left: 50%;
  z-index: 100;
  transform: translateX(-50%);
}

.card-container {
  position: fixed;
  top: 80px;
  right: 64px;
  z-index: 100;
  width: 320px;
  height: calc(100% - 96px);
  overflow: visible;
  pointer-events: none;

  & > * {
    pointer-events: all;
  }
}

10. 维护和调试指南

10.1 常见问题排查

问题1: 场景数据加载失败

排查步骤:

  1. 检查 props.id 是否正确传入
  2. 检查 getSceneById API 是否正常响应
  3. 检查编辑器服务是否正确初始化

问题2: 编辑器功能异常

排查步骤:

  1. 检查 container 元素是否正确获取
  2. 检查 EditorService 是否正确实例化
  3. 检查依赖注入是否正常工作

问题3: 文件导入导出失败

排查步骤:

  1. 检查工具函数是否正确导入
  2. 检查文件格式是否正确
  3. 检查浏览器兼容性

10.2 性能优化建议

  1. 使用 shallowRef: 对于大对象使用 shallowRef 避免深度响应式
  2. 组件懒加载: 使用 v-if 控制组件渲染时机
  3. 事件防抖: 对于频繁触发的事件(如搜索)使用防抖
  4. 内存管理: 及时清理事件监听器和定时器

10.3 扩展开发指南

添加新的元素类型

  1. EditorService 中添加对应的管理方法
  2. PenGroups 组件中添加新的分组
  3. 创建对应的详情卡片组件
  4. 在主组件中添加类型判断逻辑

添加新的工具

  1. EditorToolbar 组件中添加工具按钮
  2. EditorService 中实现对应功能
  3. 处理工具状态管理和交互逻辑

11. 总结

这个场景编辑器组件是一个功能完整、架构清晰的复杂组件,主要特点:

  1. 模块化设计: 通过子组件分离不同功能模块
  2. 服务化架构: 核心逻辑封装在 EditorService 中
  3. 响应式状态管理: 使用 Vue 3 的响应式系统管理复杂状态
  4. 依赖注入: 通过 provide/inject 实现服务共享
  5. 文件操作: 完整的文件导入导出功能
  6. 用户体验: 良好的交互设计和视觉反馈
  7. 异步机制: 合理使用异步操作确保渲染性能
  8. 性能优化: 针对大场景提供多层次优化策略

对于维护和扩展这个组件,需要重点关注:

  • EditorService 的 API 设计和实现
  • 各子组件之间的通信机制
  • 状态管理的一致性
  • 性能优化和内存管理
  • 异步操作的正确处理

12. 高性能技术栈替代方案分析

12.1 技术栈对比总览

graph LR
    subgraph "当前方案"
        A[Meta2d + Canvas 2D] --> A1[性能瓶颈]
        A1 --> A2[大场景卡顿]
        A1 --> A3[内存占用高]
        A1 --> A4[CPU密集计算]
    end

    subgraph "高性能替代方案"
        B[WebGL方案] --> B1[GPU加速]
        C[WebAssembly方案] --> C1[原生性能]
        D[混合方案] --> D1[WebGL + WASM]
        E[Web Workers] --> E1[多线程计算]
    end

    A --> B
    A --> C
    A --> D
    A --> E

12.2 WebGL高性能渲染方案

12.2.1 推荐库选择

1. PixiJS (推荐指数: )

// PixiJS 实现高性能场景编辑器
import * as PIXI from 'pixi.js';

class HighPerformanceSceneEditor {
  private app: PIXI.Application;
  private viewport: Viewport;
  private spatialHash: SpatialHash;
  private cullingSystem: CullingSystem;

  constructor(container: HTMLElement) {
    // 创建高性能应用实例
    this.app = new PIXI.Application({
      width: container.clientWidth,
      height: container.clientHeight,
      antialias: true,
      backgroundColor: 0x1099bb,
      powerPreference: 'high-performance', // 强制使用独立显卡
      hello: false, // 禁用PIXI欢迎信息
    });

    // 启用批量渲染和几何体缓存
    this.app.renderer.plugins.batch.setMaxTextures(32);
    this.setupViewport();
    this.initOptimizations();
  }

  private setupViewport(): void {
    // 使用 pixi-viewport 实现高性能视口
    this.viewport = new Viewport({
      screenWidth: this.app.screen.width,
      screenHeight: this.app.screen.height,
      worldWidth: 10000,
      worldHeight: 10000,
      interaction: this.app.renderer.plugins.interaction,
    });

    this.viewport.drag().pinch().wheel().decelerate().clampZoom({ minScale: 0.1, maxScale: 5 });
  }

  private initOptimizations(): void {
    // 1. 启用视锥剔除
    this.cullingSystem = new CullingSystem(this.viewport);

    // 2. 空间哈希优化
    this.spatialHash = new SpatialHash(100); // 网格大小100px

    // 3. 对象池
    this.initObjectPools();

    // 4. LOD系统
    this.initLODSystem();
  }

  // 批量添加大量元素 - 性能优化版本
  async addElementsBatch(elements: SceneElement[]): Promise<void> {
    const BATCH_SIZE = 1000;
    const batches = this.chunkArray(elements, BATCH_SIZE);

    for (const batch of batches) {
      // 使用 Graphics 批量绘制
      const graphics = new PIXI.Graphics();

      batch.forEach((element) => {
        this.drawElementToGraphics(graphics, element);
        this.spatialHash.insert(element);
      });

      this.viewport.addChild(graphics);

      // 让出控制权避免阻塞UI
      await this.nextFrame();
    }
  }

  private drawElementToGraphics(graphics: PIXI.Graphics, element: SceneElement): void {
    graphics.beginFill(element.color);
    switch (element.type) {
      case 'point':
        graphics.drawCircle(element.x, element.y, element.radius);
        break;
      case 'area':
        graphics.drawRect(element.x, element.y, element.width, element.height);
        break;
      case 'route':
        graphics.moveTo(element.x1, element.y1);
        graphics.lineTo(element.x2, element.y2);
        break;
    }
    graphics.endFill();
  }
}

// 视锥剔除系统
class CullingSystem {
  private viewport: Viewport;
  private visibleElements: Set<PIXI.DisplayObject> = new Set();

  constructor(viewport: Viewport) {
    this.viewport = viewport;
    viewport.on('moved', () => this.updateVisibility());
    viewport.on('zoomed', () => this.updateVisibility());
  }

  updateVisibility(): void {
    const bounds = this.viewport.getVisibleBounds();

    this.viewport.children.forEach((child) => {
      const elementBounds = child.getBounds();
      const isVisible = this.boundsIntersect(bounds, elementBounds);

      if (isVisible && !child.visible) {
        child.visible = true;
        this.visibleElements.add(child);
      } else if (!isVisible && child.visible) {
        child.visible = false;
        this.visibleElements.delete(child);
      }
    });
  }

  private boundsIntersect(a: PIXI.Rectangle, b: PIXI.Rectangle): boolean {
    return !(a.x + a.width < b.x || b.x + b.width < a.x || a.y + a.height < b.y || b.y + b.height < a.y);
  }
}

性能提升预期:

  • 渲染性能: 10-50倍提升 (GPU加速)
  • 内存使用: 减少60-80% (批量渲染)
  • 帧率: 60 FPS 稳定 (支持10000+元素)

2. Konva.js (推荐指数: )

// Konva.js 高性能实现
import Konva from 'konva';

class KonvaSceneEditor {
  private stage: Konva.Stage;
  private staticLayer: Konva.Layer;
  private dynamicLayer: Konva.Layer;
  private transformer: Konva.Transformer;

  constructor(container: HTMLElement) {
    this.stage = new Konva.Stage({
      container: container,
      width: container.clientWidth,
      height: container.clientHeight,
    });

    this.setupLayers();
    this.enableOptimizations();
  }

  private setupLayers(): void {
    // 静态层:点位、区域 (低频更新)
    this.staticLayer = new Konva.Layer({
      listening: false, // 禁用事件监听提升性能
    });

    // 动态层:机器人、选中状态 (高频更新)
    this.dynamicLayer = new Konva.Layer();

    this.stage.add(this.staticLayer);
    this.stage.add(this.dynamicLayer);
  }

  private enableOptimizations(): void {
    // 1. 启用缓存
    this.staticLayer.cache();

    // 2. 优化批量更新
    this.stage.batchDraw = true;

    // 3. 视口裁剪
    this.enableViewportCulling();

    // 4. 事件委托
    this.setupEventDelegation();
  }

  // 批量添加元素
  addElementsBatch(elements: SceneElement[]): void {
    const group = new Konva.Group();

    elements.forEach((element) => {
      const shape = this.createElement(element);
      group.add(shape);
    });

    this.staticLayer.add(group);
    this.staticLayer.batchDraw(); // 批量绘制
  }

  private enableViewportCulling(): void {
    this.stage.on('dragmove wheel', () => {
      this.updateVisibleElements();
    });
  }

  private updateVisibleElements(): void {
    const viewport = this.getViewportBounds();

    this.staticLayer.children.forEach((child) => {
      const bounds = child.getClientRect();
      child.visible(this.boundsIntersect(viewport, bounds));
    });

    this.staticLayer.batchDraw();
  }
}

12.2.2 Three.js 3D扩展方案

// 为未来3D场景编辑做准备
import * as THREE from 'three';

class ThreeJSSceneEditor {
  private scene: THREE.Scene;
  private camera: THREE.OrthographicCamera;
  private renderer: THREE.WebGLRenderer;
  private instancedMeshes: Map<string, THREE.InstancedMesh> = new Map();

  constructor(container: HTMLElement) {
    this.setupRenderer(container);
    this.setupScene();
    this.enableInstancing(); // 实例化渲染优化
  }

  private setupRenderer(container: HTMLElement): void {
    this.renderer = new THREE.WebGLRenderer({
      antialias: true,
      powerPreference: 'high-performance',
    });

    this.renderer.setSize(container.clientWidth, container.clientHeight);
    this.renderer.setPixelRatio(window.devicePixelRatio);

    // 启用高性能渲染选项
    this.renderer.shadowMap.enabled = true;
    this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
    container.appendChild(this.renderer.domElement);
  }

  // 实例化渲染 - 支持数万个相同元素
  private enableInstancing(): void {
    const pointGeometry = new THREE.CircleGeometry(1, 8);
    const pointMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });

    // 创建实例化网格支持10000个点位
    const pointInstances = new THREE.InstancedMesh(pointGeometry, pointMaterial, 10000);

    this.instancedMeshes.set('points', pointInstances);
    this.scene.add(pointInstances);
  }

  // 批量更新实例位置
  updatePointsPositions(points: Point[]): void {
    const instancedMesh = this.instancedMeshes.get('points');
    if (!instancedMesh) return;

    const matrix = new THREE.Matrix4();

    points.forEach((point, index) => {
      matrix.setPosition(point.x, point.y, 0);
      instancedMesh.setMatrixAt(index, matrix);
    });

    instancedMesh.instanceMatrix.needsUpdate = true;
  }
}

12.3 WebAssembly超高性能方案

12.3.1 Rust + WebAssembly实现

// src/scene_engine.rs
use wasm_bindgen::prelude::*;
use web_sys::CanvasRenderingContext2d;

#[wasm_bindgen]
pub struct SceneEngine {
    elements: Vec<SceneElement>,
    spatial_grid: SpatialGrid,
    viewport: Viewport,
}

#[wasm_bindgen]
pub struct SceneElement {
    id: u32,
    x: f64,
    y: f64,
    element_type: ElementType,
    visible: bool,
}

#[wasm_bindgen]
impl SceneEngine {
    #[wasm_bindgen(constructor)]
    pub fn new() -> SceneEngine {
        SceneEngine {
            elements: Vec::with_capacity(100000), // 预分配大容量
            spatial_grid: SpatialGrid::new(100.0), // 100px网格
            viewport: Viewport::new(),
        }
    }

    // 批量添加元素 - 零拷贝操作
    #[wasm_bindgen]
    pub fn add_elements_batch(&mut self, elements_ptr: *const u32, count: usize) {
        unsafe {
            let elements_slice = std::slice::from_raw_parts(elements_ptr, count * 4);

            for chunk in elements_slice.chunks(4) {
                let element = SceneElement {
                    id: chunk[0],
                    x: f64::from_bits(chunk[1] as u64),
                    y: f64::from_bits(chunk[2] as u64),
                    element_type: ElementType::from_u32(chunk[3]),
                    visible: true,
                };

                self.spatial_grid.insert(&element);
                self.elements.push(element);
            }
        }
    }

    // 高性能视锥剔除
    #[wasm_bindgen]
    pub fn update_visibility(&mut self, viewport_x: f64, viewport_y: f64,
                           viewport_width: f64, viewport_height: f64) -> Vec<u32> {
        let mut visible_ids = Vec::new();

        // 使用空间网格快速查询
        let candidates = self.spatial_grid.query(
            viewport_x, viewport_y, viewport_width, viewport_height
        );

        for element_id in candidates {
            if let Some(element) = self.elements.get_mut(*element_id as usize) {
                element.visible = true;
                visible_ids.push(element.id);
            }
        }

        visible_ids
    }

    // 并行计算路径
    #[wasm_bindgen]
    pub fn calculate_paths_parallel(&self, start_points: &[u32],
                                  end_points: &[u32]) -> Vec<f64> {
        use rayon::prelude::*;

        start_points.par_iter()
            .zip(end_points.par_iter())
            .map(|(start, end)| {
                self.calculate_shortest_path(*start, *end)
            })
            .flatten()
            .collect()
    }
}

// 高性能空间网格
pub struct SpatialGrid {
    cell_size: f64,
    cells: std::collections::HashMap<(i32, i32), Vec<u32>>,
}

impl SpatialGrid {
    pub fn new(cell_size: f64) -> Self {
        Self {
            cell_size,
            cells: std::collections::HashMap::new(),
        }
    }

    pub fn insert(&mut self, element: &SceneElement) {
        let cell_x = (element.x / self.cell_size) as i32;
        let cell_y = (element.y / self.cell_size) as i32;

        self.cells.entry((cell_x, cell_y))
            .or_insert_with(Vec::new)
            .push(element.id);
    }

    pub fn query(&self, x: f64, y: f64, width: f64, height: f64) -> Vec<&u32> {
        let mut results = Vec::new();

        let start_x = (x / self.cell_size) as i32;
        let start_y = (y / self.cell_size) as i32;
        let end_x = ((x + width) / self.cell_size) as i32;
        let end_y = ((y + height) / self.cell_size) as i32;

        for cell_x in start_x..=end_x {
            for cell_y in start_y..=end_y {
                if let Some(elements) = self.cells.get(&(cell_x, cell_y)) {
                    results.extend(elements.iter());
                }
            }
        }

        results
    }
}

12.3.2 TypeScript集成层

// TypeScript 集成 WebAssembly
import init, { SceneEngine } from './pkg/scene_engine';

class WasmSceneEditor {
  private wasmEngine: SceneEngine;
  private canvas: HTMLCanvasElement;
  private ctx: CanvasRenderingContext2D;
  private sharedBuffer: SharedArrayBuffer;

  async init(container: HTMLElement): Promise<void> {
    // 初始化 WebAssembly 模块
    await init();
    this.wasmEngine = new SceneEngine();

    this.setupCanvas(container);
    this.setupSharedMemory();
  }

  private setupSharedMemory(): void {
    // 使用 SharedArrayBuffer 实现零拷贝数据传输
    this.sharedBuffer = new SharedArrayBuffer(1024 * 1024 * 4); // 4MB
  }

  // 批量添加元素 - 超高性能
  addElementsBatch(elements: SceneElement[]): void {
    // 将数据写入共享内存
    const view = new Uint32Array(this.sharedBuffer);
    let offset = 0;

    elements.forEach((element) => {
      view[offset++] = element.id;
      view[offset++] = this.doubleToUint32(element.x);
      view[offset++] = this.doubleToUint32(element.y);
      view[offset++] = element.type;
    });

    // 调用 WASM 函数处理
    this.wasmEngine.add_elements_batch(view.byteOffset, elements.length);
  }

  // 高性能渲染循环
  private renderLoop = (): void => {
    // WASM 计算可见性
    const visibleIds = this.wasmEngine.update_visibility(
      this.viewport.x,
      this.viewport.y,
      this.viewport.width,
      this.viewport.height,
    );

    // 渲染可见元素
    this.renderVisibleElements(visibleIds);

    requestAnimationFrame(this.renderLoop);
  };

  private doubleToUint32(value: number): number {
    const buffer = new ArrayBuffer(8);
    new Float64Array(buffer)[0] = value;
    return new Uint32Array(buffer)[0];
  }
}

// 性能监控
class PerformanceMonitor {
  private frameCount = 0;
  private lastTime = performance.now();

  update(): void {
    this.frameCount++;
    const now = performance.now();

    if (now - this.lastTime >= 1000) {
      console.log(`FPS: ${this.frameCount}`);
      console.log(`Memory: ${(performance as any).memory?.usedJSHeapSize / 1024 / 1024}MB`);

      this.frameCount = 0;
      this.lastTime = now;
    }
  }
}

12.4 Web Workers多线程优化

12.4.1 多线程架构设计

// 主线程
class MultiThreadSceneEditor {
  private renderWorker: Worker;
  private calculationWorker: Worker;
  private dataWorker: Worker;
  private offscreenCanvas: OffscreenCanvas;

  constructor(container: HTMLElement) {
    this.setupWorkers();
    this.setupOffscreenCanvas(container);
  }

  private setupWorkers(): void {
    // 渲染工作线程
    this.renderWorker = new Worker('./workers/render.worker.js');

    // 计算工作线程
    this.calculationWorker = new Worker('./workers/calculation.worker.js');

    // 数据处理工作线程
    this.dataWorker = new Worker('./workers/data.worker.js');

    this.setupWorkerCommunication();
  }

  private setupOffscreenCanvas(container: HTMLElement): void {
    const canvas = document.createElement('canvas');
    canvas.width = container.clientWidth;
    canvas.height = container.clientHeight;
    container.appendChild(canvas);

    // 传输canvas控制权给worker
    this.offscreenCanvas = canvas.transferControlToOffscreen();

    this.renderWorker.postMessage(
      {
        type: 'init',
        canvas: this.offscreenCanvas,
      },
      [this.offscreenCanvas],
    );
  }

  // 并行处理大量数据
  async processElementsBatch(elements: SceneElement[]): Promise<void> {
    const chunkSize = Math.ceil(elements.length / 3);

    // 并行处理
    const promises = [
      this.processChunk(elements.slice(0, chunkSize), 0),
      this.processChunk(elements.slice(chunkSize, chunkSize * 2), 1),
      this.processChunk(elements.slice(chunkSize * 2), 2),
    ];

    await Promise.all(promises);
  }

  private processChunk(chunk: SceneElement[], workerId: number): Promise<void> {
    return new Promise((resolve) => {
      const worker = [this.dataWorker, this.calculationWorker, this.renderWorker][workerId];

      worker.postMessage({
        type: 'process',
        data: chunk,
        chunkId: workerId,
      });

      worker.addEventListener('message', (e) => {
        if (e.data.type === 'processed' && e.data.chunkId === workerId) {
          resolve();
        }
      });
    });
  }
}

// 渲染工作线程 (render.worker.ts)
class RenderWorker {
  private ctx: OffscreenCanvasRenderingContext2D;
  private elementsBuffer: Float32Array;

  constructor() {
    self.addEventListener('message', this.handleMessage.bind(this));
  }

  private handleMessage(e: MessageEvent): void {
    switch (e.data.type) {
      case 'init':
        this.ctx = e.data.canvas.getContext('2d');
        break;

      case 'render':
        this.renderFrame(e.data.elements, e.data.viewport);
        break;

      case 'updateElements':
        this.elementsBuffer = new Float32Array(e.data.buffer);
        break;
    }
  }

  private renderFrame(elements: Float32Array, viewport: Viewport): void {
    this.ctx.clearRect(0, 0, viewport.width, viewport.height);

    // 批量渲染优化
    this.ctx.save();
    this.ctx.translate(-viewport.x, -viewport.y);

    // 使用 ImageData 直接操作像素
    const imageData = this.ctx.createImageData(viewport.width, viewport.height);
    const data = imageData.data;

    // 高性能像素级渲染
    for (let i = 0; i < elements.length; i += 4) {
      const x = elements[i];
      const y = elements[i + 1];
      const color = elements[i + 2];
      const type = elements[i + 3];

      this.drawPixel(data, x, y, color, viewport);
    }

    this.ctx.putImageData(imageData, viewport.x, viewport.y);
    this.ctx.restore();

    // 通知主线程渲染完成
    self.postMessage({ type: 'frameRendered' });
  }
}

12.5 性能对比表

方案 渲染性能 内存使用 开发复杂度 兼容性 推荐场景
当前Meta2d <1000元素
PixiJS 1000-10000元素
Three.js 3D场景/复杂效果
WebAssembly >10000元素
Web Workers CPU密集计算

12.6 迁移实施建议

12.6.1 渐进式迁移策略

阶段1: 立即优化 (1-2周)

// 在现有Meta2d基础上添加PixiJS渲染层
class HybridSceneEditor extends EditorService {
  private pixiRenderer: PIXI.Application;
  private usePixiForLargeScene = false;

  constructor(container: HTMLDivElement) {
    super(container);
    this.initPixiRenderer(container);
  }

  private initPixiRenderer(container: HTMLDivElement): void {
    this.pixiRenderer = new PIXI.Application({
      width: container.clientWidth,
      height: container.clientHeight,
      transparent: true,
      powerPreference: 'high-performance',
    });

    // 叠加在Meta2d之上
    container.appendChild(this.pixiRenderer.view);
    this.pixiRenderer.view.style.position = 'absolute';
    this.pixiRenderer.view.style.pointerEvents = 'none';
  }

  public override async load(map?: string, editable = false): Promise<void> {
    await super.load(map, editable);

    // 检查元素数量,决定使用哪个渲染器
    const totalElements = this.points.value.length + this.routes.value.length + this.areas.value.length;

    if (totalElements > 1000) {
      console.log('🚀 切换到PixiJS高性能渲染');
      this.usePixiForLargeScene = true;
      this.migrateToPixi();
    }
  }

  private migrateToPixi(): void {
    // 隐藏Meta2d渲染层
    this.canvas.canvas.style.opacity = '0.1';

    // 使用PixiJS渲染大量元素
    this.renderWithPixi();
  }
}

阶段2: 核心重构 (1-2月)

// 完全基于PixiJS的新实现
class NextGenSceneEditor {
  private app: PIXI.Application;
  private sceneContainer: PIXI.Container;
  private quadTree: QuadTree;
  private instanceManager: InstanceManager;

  constructor(container: HTMLElement) {
    this.setupPixiApp(container);
    this.setupOptimizations();
  }

  private setupOptimizations(): void {
    // 1. 四叉树空间索引
    this.quadTree = new QuadTree(0, 0, 10000, 10000);

    // 2. 实例化管理器
    this.instanceManager = new InstanceManager();

    // 3. 批量渲染系统
    this.setupBatchRenderer();

    // 4. LOD系统
    this.setupLODSystem();
  }

  // 支持10万+元素的批量添加
  async addMassiveElements(elements: SceneElement[]): Promise<void> {
    console.time('MassiveElementsAdd');

    // 使用实例化渲染
    const instancedElements = this.instanceManager.createInstances(elements);

    // 批量添加到四叉树
    elements.forEach((element) => {
      this.quadTree.insert(element);
    });

    // 添加到场景
    instancedElements.forEach((instance) => {
      this.sceneContainer.addChild(instance);
    });

    console.timeEnd('MassiveElementsAdd');
    console.log(`✅ 成功添加 ${elements.length} 个元素`);
  }
}

阶段3: WebAssembly增强 (2-3月)

// 添加WebAssembly计算核心
class UltimateSceneEditor extends NextGenSceneEditor {
  private wasmCore: SceneEngineWasm;
  private sharedBuffer: SharedArrayBuffer;

  async init(): Promise<void> {
    await super.init();

    // 初始化WASM核心
    this.wasmCore = await SceneEngineWasm.init();

    // 设置共享内存
    this.setupSharedMemory();
  }

  // 百万级元素支持
  async loadMegaScene(sceneData: MegaSceneData): Promise<void> {
    console.log(`🔥 加载超大场景: ${sceneData.elements.length} 个元素`);

    // WASM并行处理
    const processed = await this.wasmCore.processMegaScene(this.sharedBuffer, sceneData.elements.length);

    // PixiJS渲染
    await this.renderProcessedElements(processed);

    console.log('🎉 超大场景加载完成性能提升100倍+');
  }
}

12.7 实施成本效益分析

12.7.1 开发成本

interface MigrationCost {
  timeWeeks: number;
  complexity: 'Low' | 'Medium' | 'High';
  riskLevel: 'Low' | 'Medium' | 'High';
  performanceGain: string;
}

const migrationOptions: Record<string, MigrationCost> = {
  pixiJSMigration: {
    timeWeeks: 4,
    complexity: 'Medium',
    riskLevel: 'Low',
    performanceGain: '10-50x渲染性能提升',
  },

  webAssemblyCore: {
    timeWeeks: 8,
    complexity: 'High',
    riskLevel: 'Medium',
    performanceGain: '100x+计算性能提升',
  },

  webWorkersParallel: {
    timeWeeks: 3,
    complexity: 'Medium',
    riskLevel: 'Low',
    performanceGain: '多核心并行处理',
  },

  hybridApproach: {
    timeWeeks: 2,
    complexity: 'Low',
    riskLevel: 'Low',
    performanceGain: '保持兼容性的性能提升',
  },
};

12.7.2 推荐实施路径

🎯 推荐方案: PixiJS + Web Workers混合架构

// 最佳性价比方案
class RecommendedSceneEditor {
  private pixiApp: PIXI.Application;
  private calculationWorker: Worker;
  private renderingOptimized = true;

  // 优势:
  // ✅ 10-50倍性能提升
  // ✅ 4周开发周期
  // ✅ 低风险
  // ✅ 向后兼容
  // ✅ 支持1万+元素

  constructor(container: HTMLElement) {
    console.log('🚀 启用推荐高性能方案');
    this.initOptimizedRenderer(container);
  }

  async benchmark(): Promise<PerformanceReport> {
    const elementCounts = [100, 1000, 5000, 10000, 50000];
    const results: PerformanceReport = {};

    for (const count of elementCounts) {
      const elements = this.generateTestElements(count);

      console.time(`Render ${count} elements`);
      await this.addElementsBatch(elements);
      console.timeEnd(`Render ${count} elements`);

      results[count] = {
        fps: this.measureFPS(),
        memory: this.measureMemory(),
        renderTime: performance.now(),
      };
    }

    return results;
  }
}

12.8 总结建议

基于您的需求和现有技术栈,我建议采用以下分阶段实施策略

🔥 立即实施 (高优先级)

  1. PixiJS渲染层: 4周内实现性能提升10-50倍
  2. Web Workers计算: 并行处理复杂计算
  3. 视口裁剪优化: 只渲染可见元素

中期规划 (中优先级)

  1. WebAssembly核心: 处理超大规模场景
  2. 四叉树空间索引: 优化元素查找
  3. LOD渲染系统: 根据缩放级别调整详度

🚀 长期愿景 (低优先级)

  1. Three.js 3D扩展: 支持3D场景编辑
  2. GPU计算着色器: 最极致的性能优化
  3. WebXR支持: VR/AR场景编辑

这样的技术栈升级可以让您的场景编辑器:

  • 支持10万+元素的超大场景
  • 保持60 FPS稳定渲染
  • 减少**80%**的内存占用
  • 提供更好的用户体验