该包的插件角色是 data_consumer。它不持有仿真控制权,不导入仿真器 SDK,也不会
获得 backend 对象、宿主机路径或文件描述符。安装该包只会注册组件目录和运行时工厂;
只有 Run 明确启用 plugin://fastsim/record 时才会产生录制开销。
当前状态
0.3.12 将 Record 对齐到 FastSim Core >=0.1.0a36,<0.2 和
fastsim-recording-format[images]==0.3.3,新增默认关闭的可选场景几何录制,并让 component schema 与当前 prepare
能力一致:不可关闭的全局 source、必须保留 incomplete、空 postprocessor 集合都会在编译期校验。
插件直接使用 Core 的精确 FinalizerRegistrationRequest、FinalizerResult 和
TerminalSemanticCut,只有消费完切面内全部已选数据源后才会提交文件,并把 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.2 与 structural-validation。它验证容器结构、规范化
metadata、精确 offset/length、sequence 与 generation 投影、index、footer、截断和原子
输出事务,但不会哈希或重新扫描 payload。这样录制过程与默认封存后读回都不再承担完整
payload 摘要开销。integrity: fast 和 integrity: 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 与录制格式包:
python -m pip install "fastsim>=0.1.0a36,<0.2" \
"fastsim-recording-format[images]==0.3.3" \
"fastsim-plugin-record==0.3.12"
从源码安装:
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 编译器会合并组件默认值:
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:
plugins:
record:
use: plugin://fastsim/record
config:
streams:
observations: {mode: none, include: {}}
高级配置
为 FSR Viewer 可选录制 3D 几何
默认不录制几何。只有需要在不启动仿真器的情况下使用 Viewer 的 3D 工作区时才开启:
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 保持无损:
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.rgb、cameras.overview.intrinsics、
cameras.overview.extrinsics.translation 和
cameras.overview.extrinsics.quaternion_xyzw 四条流。采样频率通过 Core 的
runtime.sensor_hz 和 rate_policy 配置,并在分配全局记录序号之前生效。因此本版本将
every_n_samples 固定为 1;大于 1 的值会在校验阶段失败,不会制造序号空洞。
省略 encoding 时默认就是 JPEG。只有需要 RGB 像素逐点无损时才配置 PNG:
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,优先保证持续录制吞吐。
当前 lifecycle、events 和 control 必须保持为 true,因为全局捕获 source mask 会在
插件启动前编译。0.3.8 component schema 会直接拒绝非空 postprocessor 和
preserve_incomplete: false,运行时检查仍作为纵深防御保留。
只有 numeric 或 media lane 中的所有流均为 optional 时,才允许使用
degrade_recording、drop_oldest 或 drop_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。
输出、校验与失败处理
目标输出事务顺序如下:
打开写 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 仓库根目录运行:
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:
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。