该包的插件角色是 data_consumer。它不持有仿真控制权,不导入仿真器 SDK,也不会 获得 backend 对象、宿主机路径或文件描述符。安装该包只会注册组件目录和运行时工厂; 只有 Run 明确启用 plugin://fastsim/record 时才会产生录制开销。

当前状态

0.3.12 将 Record 对齐到 FastSim Core >=0.1.0a36,<0.2fastsim-recording-format[images]==0.3.3,新增默认关闭的可选场景几何录制,并让 component schema 与当前 prepare 能力一致:不可关闭的全局 source、必须保留 incomplete、空 postprocessor 集合都会在编译期校验。 插件直接使用 Core 的精确 FinalizerRegistrationRequestFinalizerResultTerminalSemanticCut,只有消费完切面内全部已选数据源后才会提交文件,并把 required finalizer 结果返回 Core 终态协调器。真实 backend 的 DROID 录制与跨 backend Replay 由独立集成门禁继续跟踪。Core 提供可选 fluid.audit 时,Record 会订阅已接受的紧凑 发射批次,并写入 required fluid.emissions / fastsim-fluid-emission-batch/1 流;JSON 编码仍只发生在 writer worker 中。没有该可选服务的 Run 保持原有流集合与开销。

纯数值观测现在按仿真 Tick 原子聚合为一条 FSR 记录。场景即使公开 90 个数值通道, 在 120 Hz 下每秒也只产生 120 个队列项,而不是 10800 个。相机 RGB 与标定数据仍保留 独立通道记录和原有编码格式。

Core 0.1.0a33 还会提供生命周期与插件事件 Envelope 的实际便携字节大小。Record 直接使用该生产端一次计算的大小进行无损 critical lane 准入,不再把每条小事件都按 1 MiB 服务上限计费;搭配更早的兼容 Core 时仍保留安全的保守上限回退。

相机 RGB 现在默认使用 JPEG quality 92 和 4:4:4 色度采样,以显著降低普通仿真数据的 体积,同时保留高质量 RGB 信号。需要逐像素一致时,只需增加一行 encoding: png 即可 选择无损 PNG。两种编码都只在 Record worker 内执行,采集回调和物理线程不会编码图像。 深度、分割、法线及其他数值传感器通道不会进入 RGB JPEG 编码器。

默认 writer 使用 FSR binary 1.2structural-validation。它验证容器结构、规范化 metadata、精确 offset/length、sequence 与 generation 投影、index、footer、截断和原子 输出事务,但不会哈希或重新扫描 payload。这样录制过程与默认封存后读回都不再承担完整 payload 摘要开销。integrity: fastintegrity: strong 仍作为显式的防篡改兼容模式, 并要求 Core 使用 RunOutputPolicy(integrity="sha256")。Record 仍只产生 primary FSR; 视频、评测和上传不进入仿真与 Record 热线程。

0.3.1 只更新了包兼容关系;其录制热路径和持久化契约与 0.3.0 完全一致。

0.3.0 新增有序的逐通道 Observation 捕获和原生 FSR 相机流。在每个标定时段内,RGB、 内参、世界系平移和世界系四元数共享该时段对应的标定摘要,同时拥有各自的流内序号。 描述符漂移、required 样本缺失或标定契约非法都会明确失败。组件目录保留所有历史 Manifest 供旧 Lock 定位;新编译默认选择 0.3.12

标定状态按相机和 Runtime generation 分别维护。RGB 与外参可以在同一个 committed tick 内前向引用新的摘要,但匹配的内参必须在下一 tick、reset barrier 或 terminal cut 之前 完成声明。每条内参记录都会在 record metadata 中保存完整且有界的标定 profile,实际 K 矩阵仍由 payload 保存,因此离线 Replay 无需依赖 stream 的初始 attributes,就能重建 generation 到标定摘要和 profile 的对应关系。

安装

支持 Python 3.11 和 3.12。先安装匹配的 Core 与录制格式包:

bash
python -m pip install "fastsim>=0.1.0a36,<0.2" \
  "fastsim-recording-format[images]==0.3.3" \
  "fastsim-plugin-record==0.3.12"

从源码安装:

bash
python -m pip install /path/to/fastsim-next
python -m pip install /path/to/fastsim-plugins/packages/fastsim-recording-format
python -m pip install /path/to/fastsim-plugins/packages/fastsim-plugin-record

安装后会发布两个 entry point:

  • fastsim.components / fastsim.record:组件目录入口;
  • fastsim.plugins / fastsim.record:托管插件工厂入口。

最小配置

将插件加入普通的 FastSim v2 Run,FastSim 编译器会合并组件默认值:

yaml
schema: fastsim/2
name: recorded-run
backend: isaaclab

scenario:
  scene:
    robots:
      robot:
        use: robot://example/robot

plugins:
  record:
    use: plugin://fastsim/record

默认 Observation 模式是 all_numeric。如果场景没有声明 numeric channel,Record 不会 申请 Observation grant,也不会创建 Observation pump。若只录制生命周期、控制和插件 事件,可以明确关闭 Observation:

yaml
plugins:
  record:
    use: plugin://fastsim/record
    config:
      streams:
        observations: {mode: none, include: {}}

高级配置

为 FSR Viewer 可选录制 3D 几何

默认不录制几何。只有需要在不启动仿真器的情况下使用 Viewer 的 3D 工作区时才开启:

yaml
plugins:
  record:
    use: plugin://fastsim/record
    config:
      streams:
        geometry: {enabled: true, rate_hz: 30}

Record 只通过公共 scene.geometry@2 读取几何。它优先录制 visual geometry;后端没有 visual catalog 时才回退到 collision 或 planning geometry。网格资源会去重、分块并只保存 一次,rate_hz 只控制动态和运动学几何的 Transform 采样。静态几何不会逐帧重复,材质不会 写入几何资产集,流体粒子也不会进入这套几何数据。enabled: false 时不会读取 catalog、资源 或查询几何 Transform。

下面是一份当前可执行的完整配置。它录制一个必需的关节通道,并让所有 lane 保持无损:

yaml
plugins:
  record:
    use: plugin://fastsim/record
    config:
      integrity: structural
      output:
        name: episode.fsr
      streams:
        lifecycle: true
        events: true
        control: true
        observations:
          mode: explicit
          include:
            joints:
              channel: robot.joint_position
              required: true
              every_n_samples: 1
        cameras:
          overview:
            rgb: sensors.overview.camera.rgb
            intrinsics: sensors.overview.camera.intrinsics
            translation: sensors.overview.camera.extrinsics.translation
            quaternion_xyzw: sensors.overview.camera.extrinsics.quaternion_xyzw
            required: true
            every_n_samples: 1
            # 不写 encoding 和 quality 时,默认使用 JPEG quality 92。
      queues:
        critical:
          max_items: 4096
          max_bytes: 67108864
          overload: fail_run
        numeric:
          max_items: 2048
          max_bytes: 268435456
          overload: fail_run
        media:
          max_items: 64
          max_bytes: 536870912
          overload: fail_run
      batching:
        max_items: 256
        max_bytes: 16777216
        max_sim_duration_s: 1.0
      finalize:
        requested_timeout_s: 60
        preserve_incomplete: true
        postprocessors: {}

相机别名会在 FSR 中创建 cameras.overview.rgbcameras.overview.intrinsicscameras.overview.extrinsics.translationcameras.overview.extrinsics.quaternion_xyzw 四条流。采样频率通过 Core 的 runtime.sensor_hzrate_policy 配置,并在分配全局记录序号之前生效。因此本版本将 every_n_samples 固定为 1;大于 1 的值会在校验阶段失败,不会制造序号空洞。

省略 encoding 时默认就是 JPEG。只有需要 RGB 像素逐点无损时才配置 PNG:

yaml
cameras:
  overview:
    # 通道与标定字段保持不变。
    rgb: sensors.overview.camera.rgb
    intrinsics: sensors.overview.camera.intrinsics
    translation: sensors.overview.camera.extrinsics.translation
    quaternion_xyzw: sensors.overview.camera.extrinsics.quaternion_xyzw
    required: true
    every_n_samples: 1
    encoding: png

JPEG 的 quality 可选,必须是 1 到 100 的整数,默认值为 92。PNG 始终无损,因此出现 quality 会直接报配置错误。FSR 流会明确记录 media type、payload codec、图像尺寸、 色彩空间、是否有损、quality、JPEG 色度策略和 PNG 无损压缩级别,离线读取器无需猜测 实际表示。PNG 使用无损 level 1,优先保证持续录制吞吐。

当前 lifecycleeventscontrol 必须保持为 true,因为全局捕获 source mask 会在 插件启动前编译。0.3.8 component schema 会直接拒绝非空 postprocessor 和 preserve_incomplete: false,运行时检查仍作为纵深防御保留。

只有 numeric 或 media lane 中的所有流均为 optional 时,才允许使用 degrade_recordingdrop_oldestdrop_newest。critical lane 以及包含任意 required 流的 lane 必须使用 fail_run

异步与性能语义

Record 使用三个有界 lane:

Lane 内容 过载规则
critical 生命周期、控制审计、控制结果、插件事件 fail_run
numeric 选中的非媒体 Observation 无损,或明确声明的 optional 丢弃
media 已选 RGB 相机信封 无损,或明确声明的 optional 丢弃

每类数据源都有独立 pump,已选 Observation 共用一条有序的逐通道订阅。pump 只校验已带 统一序号与时钟戳的不可变信封,并调用非等待的 try_publish;它不会编码、压缩、哈希、访问文件或等待 worker。生命周期、插件事件和 Observation 均从全局录制序号 1 申请原子的“历史 + 实时”订阅,因此 Record 的 prepare 之前已经发布的数据不会丢失。Control 会在控制源插件启动前订阅。

组件 Manifest 通过 recording_sources 将 Record 声明为唯一的有序捕获 owner。Core 只为 选中的 Observation channel 和 generation barrier 分配连续的 capture_sequence;Record 使用它校验数据源连续性和 terminal fence。独立的实时 channel_sequence 仅保留为诊断 元数据,不参与持久化连续性判断。

唯一的 worker 线程持有不透明写 lease,按照全局序号合并三个 lane,在 worker 内只编码一次并 执行有界批写。采集路径只传递不可变紧凑 RGB 缓冲区;JPEG/PNG 转换及其所需的 worker 侧 缓冲分配只会在队列准入之后发生。以下任意阈值达到时都会 flush:

  • item 数量;
  • 实际编码后的 payload 字节数;
  • 同一 generation 内,已排序记录的仿真时间跨度。

generation 变化会结束当前 batch,finalize 会强制 flush 尚未达到阈值的尾部 batch。 慢盘或停滞的输出不能阻塞仿真发布路径;required 队列过载会被锁存为致命录制错误。仅在 启用 Record 时存在的轻量 monitor 会把 pump、lane 或 worker 的锁存故障转换成一次普通 Core 结束请求。如果已有其他控制源先请求结束,required finalizer 仍会报告故障,避免错误成功。

finalizer 摘要会报告 payload 字节数、批写/收尾/回读耗时,以及每个实际启用 lane 的容量、 当前深度、高水位、入队/出队、丢失、失败和降级计数。这些值来自已有 lane 计数器和 batch 边界,不会增加逐 tick 计时。未选择 Observation 和相机时,Record 不创建 numeric/media lane,也不会轮询 Observation。

输出、校验与失败处理

目标输出事务顺序如下:

text
打开写 lease -> 写入/flush/关闭 -> seal
  -> 打开同一事务的不透明回读 lease
  -> worker 执行结构校验,并通过 seek 跳过 payload
  -> 关闭回读 lease
  -> confirm_validation(同一个 lease, 不可变 receipt)
  -> commit

校验 receipt 包含 validator schema/profile、已校验字节数、策略 integrity、可选 content SHA-256、FSR footer 身份和 completion 身份。默认 structural receipt 不含内容摘要;显式 fast/strong 模式会在封存后惰性计算一次整文件摘要,并且只能在 Core SHA-256 输出策略下 提交。发生损坏、无法解释的全局序号空洞、缺少终止切面、编码结果超过 入队上界、worker 超时或回读 lease 身份不一致时,均不会尝试 commit。

允许丢失的 optional 数据会写为精确有序的 GapRecord,并将主文件标记为 degraded; required 数据丢失是致命错误。成功 finalize 前调用 close 会中止输出事务。是否保留以及 如何发布 .incomplete 临时文件由可信 Core RunOutputPolicy 控制,而不是由插件选择宿主 路径。当前配置中的 preserve_incomplete 必须保持为 true,且不会覆盖部署策略。

Host 在绝对截止时间取消 finalizer 时,Record 会取消 pump 与 writer,但不会调用公开的无界 transaction abort。Core 负责在统一截止时间内撤销 lease 并隔离 incomplete 文件,从而避免 停滞文件系统把终态清理变成无期限等待。

验证

fastsim-plugins 仓库根目录运行:

bash
PYTHONPATH=/path/to/fastsim-next/src:\
packages/fastsim-recording-format/src:\
packages/fastsim-plugin-record/src \
python -m pytest packages/fastsim-plugin-record/tests -q

python -m ruff check \
  packages/fastsim-plugin-record/src \
  packages/fastsim-plugin-record/tests

cd packages/fastsim-plugin-record
PYTHONPATH=/path/to/fastsim-next/src:../fastsim-recording-format/src:src \
python -m mypy --config-file pyproject.toml
python -m build

DROID 关节状态功能门禁必须使用独立的短录制,不能复用只采 position 的性能 A/B。源 Run 配置必须像 acceptance/configs/record-droid-joint-state-explicit.yaml 一样显式选择两个 channel:

bash
PYTHONPATH=packages/fastsim-recording-format/src \
python acceptance/scripts/validate_record_joint_state.py \
  --config /path/to/exact-functional-run.yaml \
  --fsr /path/to/committed-functional-run.fsr \
  --report /path/to/joint-state-acceptance.json

该门禁不会把 manifest descriptor 当成样本证据:position 与 velocity 的真实 record count 都必须大于零,而且每条解码样本都必须满足锁定的 DROID entity、float64 dtype、13 维 shape、单位、字节长度和有限值契约。

当前测试覆盖有界过载与精确 gap、并发丢失记账、并发 source 乱序到达、全局有序合并、 逐通道 reset barrier、默认 JPEG 与显式 PNG 的 worker 编码、旧 raw-RGB 解码兼容、 RGB/内参/平移/四元数完整 FSR 输出、 关闭相机时零 media lane、reset generation 定位、四类 batch flush、慢盘和停滞写入、仅 worker 编码、真实 Core source 的启动历史回放、精确 终止切面、正常/失败/取消/停止四类结束、required finalizer 优先级、finalizer 超时、捕获故障 传播、prepare 全阶段故障清理、损坏回读、精确 lease 身份、stop/close 幂等、双 Python 源码 测试、隔离 Wheel 依赖解析、pip check、安装态 entry point 发现和源码隔离。真实仿真器 Record 与跨 backend Replay 证据属于集成 backend 验收阶段,不由上述包级门禁代替。

许可证与作者

作者:Hofee lexhofee@gmail.com

仓库许可证确定前,本包暂用 LicenseRef-Pending