数据流与同步¶
本页说明一次 TongSIM Lite 调用如何从 Python 传到 Unreal 并返回,以及平台如何保证 线程安全、如何理解“同步”与“多帧任务”。
排查问题时可先看这里
- 调用“卡住”(通常是 reactor 在等待世界推进)
- 看到
UNAVAILABLE/DEADLINE_EXCEEDED - 重置/切关卡后 actor id 看起来“失效”
一次 RPC 的端到端链路¶
TongSIM Lite 的设计目标是:IO 异步化,但 世界逻辑在 Game Thread 上确定性执行。
Python(你的代码)
-> WorldContext.sync_run(coro)
-> AsyncLoop 线程(asyncio)
-> grpc.aio stub.SomeRpc(...)
-> network
-> UE gRPC 工作线程(仅处理 IO)
-> Channel<RpcEvent>
-> UTSGrpcSubsystem::Tick() [Game Thread]
-> handler / reactor
-> response
Unary 与 Reactor¶
Unary handler 在 Game Thread 上执行,并在同一帧内快速返回,例如:
DemoRLService/QueryStateDemoRLService/SpawnActorArenaService/ListArenas
Frame N:
- router 分发请求
- handler 执行(读写世界状态)
- 返回响应
当操作需要跨多帧完成时,会使用 reactor,例如:
DemoRLService/ResetLevelDemoRLService/NavigateToLocationArenaService/LoadArenaCaptureService/CaptureSnapshot
Frame N:
- reactor.onRequest() 接收并缓存参数
Frame N..M:
- reactor.Tick() 每帧推进任务
Frame M:
- reactor 满足完成条件后回包
为什么需要 reactor
Unreal 的 Gameplay / Streaming 都是按帧推进的。reactor 允许“等待完成”,但不会阻塞 Game Thread。
Arena 流式加载的数据流¶
Arena 系列接口建立在 UE 的 Streaming Level 上:
- Python 调用
ArenaService/LoadArena(level_asset_path, anchor, make_visible)。 - UE 通过
UTSArenaSubsystem::LoadArena(...)创建并加载 Streaming Level。 - Load reactor 周期性检查
UTSArenaSubsystem::IsArenaReady(...)。 - Arena 就绪后返回
arena_id(ObjectId.guid中的 FGuid)。
Anchor 定义局部坐标系
Arena 提供 LocalToWorld / WorldToLocal,便于在稳定的 arena-local 坐标系下编写客户端逻辑。
Level 重置 / 地图切换(Travel)¶
某些流程会重置大部分甚至整个世界。例如 DemoRLService/ResetLevel 会触发地图切换,并等待新世界就绪。
实际影响包括:
- 旧的 Actor 可能被销毁
- Actor GUID 注册表会在 World 初始化后重建
- 客户端侧的缓存应视为失效
将 reset 视为“边界”
如果你跨 reset 保存了 id/流/状态,请确保能检测失效并重新发现所需对象。
这里的“同步”到底指什么¶
- UE 侧:请求都在 Game Thread 执行;耗时任务用 reactor 逐帧推进。
- Python 侧:你可以写同步代码(
sync_run),底层由异步 loop 负责调度。 - 并发:可以同时发起多个 RPC,但对世界状态的修改会被 Game Thread 路由器串行化处理。
:material-checklist: 实用建议¶
- 尽量将请求拆小、明确(避免每帧都“全量查询”)。
- 对长任务设置超时(导航、Streaming、采集等)。
- 大数据(图像/体素)要关注消息大小;Python 端在
src/tongsim/connection/grpc/core.py将单次收发限制配置为 100MB。
下一步: 系统架构概览