Unreal Server¶
In TongSIM Lite, the “server” is the Unreal Engine process you run (Editor PIE or a packaged build). It owns the simulation world and hosts a gRPC server that executes requests safely on the UE game thread.
Default endpoint
The gRPC server binds to 0.0.0.0:5726 by default. The port is defined in unreal/Plugins/TongSimGrpc/Source/TongosGrpc/Private/TSGrpcSubsystem.cpp.
UE modules you will touch¶
TongSIM Lite’s UE-side code is primarily split into two plugin groups:
| Plugin group | Location | What it provides |
|---|---|---|
| TongSimCore | unreal/Plugins/TongSimCore |
Simulation utilities (arena streaming, capture cameras, voxelization) |
| TongSimGrpc | unreal/Plugins/TongSimGrpc |
gRPC server runtime + service implementations |
gRPC runtime & threading model¶
The core design principle is:
Network IO happens on gRPC worker threads, but all gameplay/world mutation happens on the UE game thread.
gRPC worker threads (IO)
-> Channel<RpcEvent>
-> UTSGrpcSubsystem::Tick() [Game Thread]
-> RpcRouter::handle()
-> Unary handler OR Reactor (multi-frame)
UTSGrpcSubsystem (server lifecycle + routing)¶
UTSGrpcSubsystem is a UGameInstanceSubsystem and tickable object that:
- Starts the gRPC server (
tongos::RpcServer) duringInitialize() - Receives incoming RPC events through a thread-safe channel
- Dispatches those events each frame on the game thread (
UpdateRpcRouter()/Tick())
Key files:
unreal/Plugins/TongSimGrpc/Source/TongosGrpc/Public/TSGrpcSubsystem.hunreal/Plugins/TongSimGrpc/Source/TongosGrpc/Private/TSGrpcSubsystem.cpp
Why this matters
If you add a new API, keep the same rule: don’t access UWorld from gRPC worker threads.
Actor ID mapping (FGuid ↔ Actor)¶
To let Python refer to UE objects consistently, TongSIM Lite maintains a registry:
- Unreal
Actor⇄ generatedFGuid FGuidis serialized intoObjectId.guid(16 bytes, little-endian layout for the first fields)
UTSGrpcSubsystem:
- Scans actors after world initialization
- Registers new actors on spawn
- Marks IDs as destroyed on
EndPlay/OnDestroyed
This is what enables APIs like “get actor state by id” and “destroy actor by id”.
Services and their UE handlers¶
TongSIM Lite implements multiple gRPC services. Each service is wired by registering method names (for example "/tongsim_lite.demo_rl.DemoRLService/QueryState") to either a unary handler or a reactor.
| Service | Proto | UE subsystem | Notes |
|---|---|---|---|
| Arena | protobuf/tongsim_lite_protobuf/arena.proto |
UArenaGrpcSubsystem |
Multi-level streaming + anchor transforms |
| DemoRL | protobuf/tongsim_lite_protobuf/demo_rl.proto |
UDemoRLSubsystem |
Spawn/query/move/navigation utilities |
| Voxel | protobuf/tongsim_lite_protobuf/voxel.proto |
UDemoRLSubsystem |
Voxel queries via TSVoxelGridFuncLib |
| Capture | protobuf/tongsim_lite_protobuf/capture.proto |
UCaptureGrpcSubsystem |
RGB/depth capture cameras + snapshots |
Key files:
unreal/Plugins/TongSimGrpc/Source/TongSimProto/Private/DemoRL/ArenaGrpcSubsystem.cppunreal/Plugins/TongSimGrpc/Source/TongSimProto/Private/DemoRL/DemoRLSubsystem.cppunreal/Plugins/TongSimGrpc/Source/TongSimProto/Private/Capture/CaptureGrpcSubsystem.cpp
Reactors (multi-frame request handlers)¶
Some tasks cannot complete within a single frame, such as:
- Streaming a level in/out (arena load/reset/destroy)
- Long-running movement (move-towards, navmesh navigation)
- Waiting for a capture frame to be ready
For these, TongSIM Lite uses reactors:
onRequest(...)captures parameters and starts the taskTick(...)advances the task each frame- When ready, the reactor finishes and returns a response to the client
This keeps behavior deterministic with the game loop and avoids blocking the game thread.
Extending the UE server¶
- Define the request/response in
protobuf/tongsim_lite_protobuf/*.proto. - Regenerate code for Python and UE (see
scripts/generate_pb2.pyfor Python; UE uses generated headers undertongsim_lite_protobuf/*.pb.h). - Implement a unary handler or reactor in
unreal/Plugins/TongSimGrpc/Source/TongSimProto. - Register the method name via
UTSGrpcSubsystem::RegisterUnaryHandler/RegisterReactor.
- Confirm you are in PIE (Editor) and the server is listening on the expected port.
- Add UE logs in the corresponding subsystem (DemoRL/Arena/Capture).
- Check whether the call is unary (should return immediately) or reactor (waits over multiple frames).
Next: Python Client