xviz 架构设计方案

24 阅读3分钟

整体分层架构

┌─────────────────────────────────────────────────────────────────┐
│                        应用层 (Application)                      │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐          │
│  │  WebSocket   │  │  HTTP Server │  │  File Server │          │
│  │   Service    │  │              │  │              │          │
│  └──────┬───────┘  └──────────────┘  └──────────────┘          │
└─────────┼────────────────────────────────────────────────────────┘
          │
          ▼
┌─────────────────────────────────────────────────────────────────┐
│                    输出层 (Output Layer)                         │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │               XVIZFormatWriter                            │  │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────┐               │  │
│  │  │   JSON   │  │  Binary  │  │ Protobuf │               │  │
│  │  │  Format  │  │   GLB    │  │  Format  │               │  │
│  │  └──────────┘  └──────────┘  └──────────┘               │  │
│  └──────────────────────────────────────────────────────────┘  │
└─────────┼────────────────────────────────────────────────────────┘
          │
          ▼
┌─────────────────────────────────────────────────────────────────┐
│                   构建层 (Builder Layer)                         │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │              Builder Service                              │  │
│  │                                                           │  │
│  │  ┌───────────────────────┐  ┌───────────────────────┐   │  │
│  │  │  XVIZMetadataBuilder  │  │    XVIZBuilder        │   │  │
│  │  │  (初始化时调用一次)    │  │  (每帧都调用)         │   │  │
│  │  │                       │  │                       │   │  │
│  │  │  - stream()           │  │  - pose()             │   │  │
│  │  │  - category()         │  │  - primitive()        │   │  │
│  │  │  - type()             │  │  - polyline()         │   │  │
│  │  │  - streamStyle()      │  │  - polygon()          │   │  │
│  │  │  - getMetadata()      │  │  - points()           │   │  │
│  │  │                       │  │  - getMessage()       │   │  │
│  │  └───────────────────────┘  └───────────────────────┘   │  │
│  └──────────────────────────────────────────────────────────┘  │
└─────────┼────────────────────────────────────────────────────────┘
          │
          ▼
┌─────────────────────────────────────────────────────────────────┐
│                   转换层 (Transform Layer)                       │
│                                                                  │
│  ┌────────────────────────────────────────────────────────┐    │
│  │                    Dispatcher                           │    │
│  │  - 收集所有 Parser 的输出                               │    │
│  │  - 按 streamName 分组                                  │    │
│  │  - 调用 Builder Service                                │    │
│  └────────────┬───────────────────────────────────────────┘    │
│               │                                                 │
│               ▼                                                 │
│  ┌───────────────────────────────────────────────────────┐    │
│  │                  Parser (插件化)                       │    │
│  │                                                        │    │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────┐           │    │
│  │  │  Lane    │  │ Obstacle │  │   Point  │   ...     │    │
│  │  │  Parser  │  │  Parser  │  │  Cloud   │           │    │
│  │  └──────────┘  └──────────┘  │  Parser  │           │    │
│  │                               └──────────┘           │    │
│  │  输入:中间格式                                       │    │
│  │  输出:XVIZ 数据结构                                  │    │
│  │  作用:转换为符合 XVIZ 协议的数据                     │    │
│  └────────────┬──────────────────────────────────────────┘    │
│               │                                                 │
│               ▼                                                 │
│  ┌───────────────────────────────────────────────────────┐    │
│  │                 Formatter (插件化)                     │    │
│  │                                                        │    │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────┐           │    │
│  │  │  Lane    │  │ Obstacle │  │   Pose   │   ...     │    │
│  │  │ Formatter│  │ Formatter│  │ Formatter│           │    │
│  │  └──────────┘  └──────────┘  └──────────┘           │    │
│  │                                                        │    │
│  │  输入:原始 ROS 消息                                   │    │
│  │  输出:中间格式 (normalPrivateData, latched...)       │    │
│  │  作用:业务逻辑转换、坐标变换、数据聚合                │    │
│  └────────────┬──────────────────────────────────────────┘    │
└───────────────┼──────────────────────────────────────────────────┘
                │
                ▼
┌─────────────────────────────────────────────────────────────────┐
│                   预处理层 (Preprocess Layer)                    │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │              Loader / Event Emitter                       │  │
│  │                                                           │  │
│  │  - subscribe(topic, callback)                            │  │
│  │  - preformat(data)                                       │  │
│  │  - emit(topic, data)                                     │  │
│  │  - Worker 线程通信                                       │  │
│  └──────────────────────────────────────────────────────────┘  │
└─────────┼────────────────────────────────────────────────────────┘
          │
          ▼
┌─────────────────────────────────────────────────────────────────┐
│                   数据源层 (Data Source Layer)                   │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │                  Reader / Parser                          │  │
│  │                                                           │  │
│  │  ┌────────────┐                                          │  │
│  │  │  ROS Bag   │                                          │  │
│  │  │  Reader    │  - readAt(timestamp)                     │  │
│  │  │            │  - 按 topic 过滤                         │  │
│  │  │            │  - 时间同步                              │  │
│  │  └────────────┘                                          │  │
│  │                                                           │  │
│  │  输入:bag 文件路径                                       │  │
│  │  输出:原始 ROS 消息 (topic, message, timestamp)         │  │
│  └──────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘

数据流转图

ROS Bag ↓ Reader/Loader (读取原始消息) ↓ Preformat (预处理) ↓ Loader Subscribe (订阅) ↓ 【Formatter】← 第一次转换 输入:ROS 原始消息 输出:中间格式(你们公司自定义) ↓ 【Parser】← 第二次转换 输入:Formatter 的中间格式 输出:符合 XVIZ 的数据结构 ↓ Dispatch (派发) ↓ Builder Service ← 使用 XVIZBuilder 将 Parser 的输出添加到 XVIZBuilder ↓ XVIZFormatWriter 写入文件或发送到前端

┌─────────────┐
│  ROS Bag    │
│   File      │
└──────┬──────┘
       │
       ▼
┌─────────────────────────────────────────────────────────┐
│  Reader Layer                                            │
│  ┌────────────────────────────────────────────────┐    │
│  │ reader.readAt(timestamp)                       │    │
│  │   ↓                                            │    │
│  │ { topic, message_type, message, timestamp }    │    │
│  └────────────────────────────────────────────────┘    │
└───────────────────────┬─────────────────────────────────┘
                        │
                        ▼
┌─────────────────────────────────────────────────────────┐
│  Preprocess Layer                                        │
│  ┌────────────────────────────────────────────────┐    │
│  │ preformat(data)                                │    │
│  │   ↓                                            │    │
│  │ topicEventEmitter.emit(topic, data)            │    │
│  │   ↓                                            │    │
│  │ loader.subscribe(topic, callback)              │    │
│  │   ↓                                            │    │
│  │ worker.postMessage({ rawData, ... })           │    │
│  └────────────────────────────────────────────────┘    │
└───────────────────────┬─────────────────────────────────┘
                        │
                        ▼
┌─────────────────────────────────────────────────────────┐
│  Transform Layer (Worker 线程)                           │
│                                                          │
│  ┌────────────────────────────────────────────────┐    │
│  │ Formatter                                      │    │
│  │   输入: { topic, message }                     │    │
│  │   处理: 业务逻辑转换                            │    │
│  │   输出: {                                      │    │
│  │           normalPrivateData: {...},           │    │
│  │           latchedPrivateData: {...},          │    │
│  │           latchedSharedData: {...}            │    │
│  │         }                                      │    │
│  └────────────────┬───────────────────────────────┘    │
│                   │                                     │
│                   ▼                                     │
│  ┌────────────────────────────────────────────────┐    │
│  │ Parser                                         │    │
│  │   输入: privateData (Formatter 的输出)         │    │
│  │   处理: 转换为 XVIZ 数据结构                   │    │
│  │   输出: {                                      │    │
│  │           polylines: [{                        │    │
│  │             vertices: [...],                   │    │
│  │             base: { object_id, style }         │    │
│  │           }]                                   │    │
│  │         }                                      │    │
│  └────────────────┬───────────────────────────────┘    │
│                   │                                     │
│                   ▼                                     │
│  ┌────────────────────────────────────────────────┐    │
│  │ Dispatcher                                     │    │
│  │   dispatch(builderData, pluginConfig)          │    │
│  │   ↓                                            │    │
│  │   builderDatas[streamName] = {                 │    │
│  │     builderData,                               │    │
│  │     pluginConfig                               │    │
│  │   }                                            │    │
│  └────────────────────────────────────────────────┘    │
└───────────────────────┬─────────────────────────────────┘
                        │
                        ▼
┌─────────────────────────────────────────────────────────┐
│  Builder Layer                                           │
│  ┌────────────────────────────────────────────────┐    │
│  │ builderService.onBuildMessage({                │    │
│  │   buildMessage,  // Parser 的输出              │    │
│  │   pluginConfig,                                │    │
│  │   frameTimestamp,                              │    │
│  │   frameBuilder   // XVIZBuilder 实例           │    │
│  │ })                                             │    │
│  │   ↓                                            │    │
│  │ frameBuilder                                   │    │
│  │   .primitive(streamName)                       │    │
│  │   .polyline(vertices)                          │    │
│  │   .id(object_id)                               │    │
│  │   .style(style)                                │    │
│  │   ↓                                            │    │
│  │ frameBuilder.getMessage()                      │    │
│  └────────────────────────────────────────────────┘    │
└───────────────────────┬─────────────────────────────────┘
                        │
                        ▼
┌─────────────────────────────────────────────────────────┐
│  Output Layer                                            │
│  ┌────────────────────────────────────────────────┐    │
│  │ XVIZFormatWriter                               │    │
│  │   ↓                                            │    │
│  │ writer.writeMetadata(metadata)                 │    │
│  │ writer.writeMessage(xvizMessage)               │    │
│  │   ↓                                            │    │
│  │ 序列化为 JSON / Binary / Protobuf              │    │
│  └────────────────────────────────────────────────┘    │
└───────────────────────┬─────────────────────────────────┘
                        │
                        ▼
┌─────────────────────────────────────────────────────────┐
│  Application Layer                                       │
│  ┌────────────────────────────────────────────────┐    │
│  │ WebSocket / HTTP / File                        │    │
│  │   ↓                                            │    │
│  │ 发送到前端 / 写入文件                           │    │
│  └────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────┘

插件化架构

┌─────────────────────────────────────────────────────────┐
│                  Plugin Manager                          │
│                                                          │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐             │
│  │ Plugin A │  │ Plugin B │  │ Plugin C │   ...       │
│  │ (Lane)   │  │(Obstacle)│  │ (Pose)   │             │
│  │          │  │          │  │          │             │
│  │ ┌──────┐ │  │ ┌──────┐ │  │ ┌──────┐ │             │
│  │ │Format│ │  │ │Format│ │  │ │Format│ │             │
│  │ │-ter  │ │  │ │-ter  │ │  │ │-ter  │ │             │
│  │ └──┬───┘ │  │ └──┬───┘ │  │ └──┬───┘ │             │
│  │    │     │  │    │     │  │    │     │             │
│  │    ▼     │  │    ▼     │  │    ▼     │             │
│  │ ┌──────┐ │  │ ┌──────┐ │  │ ┌──────┐ │             │
│  │ │Parser│ │  │ │Parser│ │  │ │Parser│ │             │
│  │ └──────┘ │  │ └──────┘ │  │ └──────┘ │             │
│  │          │  │          │  │          │             │
│  │ Config:  │  │ Config:  │  │ Config:  │             │
│  │ - topic  │  │ - topic  │  │ - topic  │             │
│  │ - stream │  │ - stream │  │ - stream │             │
│  │ - style  │  │ - style  │  │ - style  │             │
│  └──────────┘  └──────────┘  └──────────┘             │
└─────────────────────────────────────────────────────────┘

完整架构流程

┌─────────────────────────────────────────────────────────────┐
│                    第一步:Reader/Loader                     │
│         读取 bag 文件,获取原始 ROS 消息                      │
└──────────────────┬──────────────────────────────────────────┘
                   │
                   ▼
    ┌──────────────────────────────────┐
    │  reader.readAt() 按时间戳读取    │
    │  messageQueue[topic].push({      │
    │    topic, message_type,          │
    │    message, timestamp            │
    │  })                              │
    └──────────────┬───────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────────────────────────┐
│              第二步:Preformat + Event Emitter               │
│            预处理消息并通过事件发射                          │
└──────────────────┬──────────────────────────────────────────┘
                   │
                   ▼
    ┌──────────────────────────────────┐
    │ Object.keys(messageQueue)        │
    │   .forEach((topic) => {          │
    │     const topicEventData =       │
    │       preformat(data)            │
    │     this.topicEventEmitter       │
    │       .emit(topic, topicEventData)│
    │   })                             │
    └──────────────┬───────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────────────────────────┐
│              第三步:Loader Subscribe                        │
│          订阅 topic,获取 rawData                            │
└──────────────────┬──────────────────────────────────────────┘
                   │
                   ▼
    ┌──────────────────────────────────┐
    │ this.loader.subscribe(topic,     │
    │   (rawData) => {                 │
    │     this.worker.postMessage({    │
    │       rawData,                   │
    │       privateData,               │
    │       sharedData                 │
    │     })                           │
    │   })                             │
    └──────────────┬───────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────────────────────────┐
│              第四步:Formatter(在 Worker 中)                │
│     将 rawData 格式化为中间数据格式                           │
└──────────────────┬──────────────────────────────────────────┘
                   │
                   ▼
    ┌──────────────────────────────────┐
    │ plugins.forEach(                 │
    │   (plugin) => plugin.activate()  │
    │ )                                │
    │                                  │
    │ const { topic, message } =       │
    │   rawData                        │
    │                                  │
    │ this.formatters[topic]({         │
    │   topic, message,                │
    │   lastLatchedPrivateData         │
    │ })                               │
    │                                  │
    │ // 输出:                        │
    │ {                                │
    │   normalPrivateData,             │
    │   latchedPrivateData,            │
    │   latchedSharedData              │
    │ }                                │
    └──────────────┬───────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────────────────────────┐
│              第五步:Parser(在 Worker 中)                   │
│     解析中间数据,输出符合 XVIZ 格式的数据                     │
└──────────────────┬──────────────────────────────────────────┘
                   │
                   ▼
    ┌──────────────────────────────────┐
    │ await Promise.all(               │
    │   plugins.map(                   │
    │     (plugin) => plugin.parse()   │
    │   )                              │
    │ )                                │
    │                                  │
    │ // Parser 输出符合 XVIZ 的数据   │dispatch(builderData,            │
    │   pluginConfig)                  │
    └──────────────┬───────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────────────────────────┐
│              第六步:Dispatch + Builder Service              │
│     使用 XVIZBuilder 构建最终 XVIZ 消息                       │
└──────────────────┬──────────────────────────────────────────┘
                   │
                   ▼
    ┌──────────────────────────────────┐
    │ builderDatas[streamName] = {     │
    │   builderData,                   │
    │   pluginConfig                   │
    │ }                                │
    │                                  │
    │ builderService.onBuildMessage({  │
    │   buildMessage: builderData,     │
    │   pluginConfig,                  │
    │   frameTimestamp,                │
    │   frameBuilder  ← XVIZBuilder!   │
    │ })                               │
    └──────────────┬───────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────────────────────────┐
│              第七步:XVIZFormatWriter                        │
│     写入文件或发送到前端                                      │
└──────────────────┬──────────────────────────────────────────┘
                   │
                   ▼
    ┌──────────────────────────────────┐
    │ const sink = new FileSink(path)  │
    │ let writer = new XVIZFormatWriter│
    │   (sink, {                       │
    │     format: XVIZ_FORMAT.BINARY_PBE│
    │   })                             │
    │                                  │
    │ writer.writeMetadata(metadata)   │
    │ writer.writeMessage(...)         │
    └──────────────────────────────────┘

关键设计模式

1. 管道模式 (Pipeline Pattern)

Reader → Preprocess → Formatter → Parser → Builder → Writer

2. 插件模式 (Plugin Pattern)

pluginManager.register('lane', {
  formatter: laneFormatter,
  parser: laneParser,
  config: { ... }
})

3. 适配器模式 (Adapter Pattern)

ROS 格式 → [Formatter 适配] → 中间格式
中间格式 → [Parser 适配] → XVIZ 格式

4. 发布-订阅模式 (Pub-Sub Pattern)

loader.subscribe(topic, callback)
eventEmitter.emit(topic, data)

5. 建造者模式 (Builder Pattern)

XVIZBuilder
  .pose('/pose')
  .primitive('/lanes')
  .getMessage()