验证等级

可见原生 Isaac。 窗口中第一次运动执行旧 chunk 的 40 帧中的 20 帧后,机械臂 转而跟随新的运动。

前置条件

  • 同一 Python 3.12 环境中的 FastSim 0.1.0a6、UniRoboSim Core 0.10.0unirobosim-isaaclab==0.10.1
  • Isaac Lab 3.0 / Isaac Sim 6.0、NVIDIA GPU 和可用的显示环境。

为什么先暂停 runtime

仿真速度不保证与墙钟时间一致,因此 sleep 半秒不代表“执行了 20 个控制帧”。程序 暂停连续物理并调用 single_step(),让快慢机器得到同样结果。物理和控制都是 60 Hz,所以每次 step 恰好跨过一个控制边界。

代码流程

  1. FRAME_DT = 1.0 / 60.0 与配置控制频率一致。
  2. linear_path(...) 生成恰好 frames 行的双轴目标,包含起点和终点;它不依赖仿真器。
  3. 应用启动后 pause,但世界仍保留。
  4. old_chunk = submit_joints(..., preempt=True) 立即返回 operation handle,并把 当前 incumbent chunk 标记成允许后续替换。
  5. 循环读取 status().progress.applied_frames,single-step 到确实应用至少 20 个旧帧。
  6. new_chunk = submit_joints(...) 从同一 session 到达,在下一个安全控制边界替换 允许抢占的 incumbent。
  7. 继续 single-step,直到新 operation 进入终态。
  8. result() 证明旧状态为 preempted,新状态为 succeeded
  9. release() 显式清除两个已完成 operation 的保留记录。

新 path 第一行接近旧 path 执行 20 帧时的预期位置。真实 model plugin 应使用最新 observation 作为新起点,避免不期望的跳变。

preempt 参数描述的是“正在提交的这个 chunk”:True 表示以后到达的 chunk 可以 抢占它。因此必须给旧/incumbent chunk 设置,而不是给新/challenger chunk 设置。 希望始终可被新推理结果替换的 model-servo chunk 都应设置它。

运行

bash
cd demo/fundamentals/05_servo_preemption
fastsim config validate run.yaml --json
python main.py

预期结果:原生 Isaac 窗口打开;旧运动执行 20 帧后,在安全边界切换到新运动。终端类似:

text
Old chunk: preempted, applied=20/40
New chunk: succeeded, applied=40/40

如果 provider 记账在同一个安全边界跨过,旧 applied 数可能略高于 20,但必须小于 40,且状态必须为 preempted

常见错误

  • 新 chunk 排队而不是替换:旧 incumbent 提交时没有 preempt=True,所以它正确拒绝替换。
  • 旧 chunk 显示 succeeded:runtime 连续运行,提交新 chunk 前已消耗完 40 帧。
  • 自定义代码无限等待:外部 model/device 流程必须配置 timeout 或最大步数策略。

下一步

继续阅读 06 — RGB 场景相机截图