A minimal ROS-free robotics framework in Rust — typed pub/sub, a deterministic sim clock, record/replay, a lock-free shared-memory ring, and TCP. No DDS, no parameter server, no launch XML. About 3 k lines of framework code.
The GIF is a 3 s pendulum swing. A sim node owns the clock and publishes
/joint_states + /clock; a rec node writes a .gdr bag. Replaying that bag
onto a fresh domain reproduces every payload bit-exactly (CI gate).
Still: docs/demo_frame.png.
| Metric | Result |
|---|---|
In-process pub/sub, 64-byte rkyv Blob64, N=20 000 |
p50 420 ns · p99 530 ns · 2.17 M/s |
| Shared-memory ring (two domains, same host) | p50 710 ns · p99 1.9 µs · 1.16 M/s |
| TCP localhost, length-prefixed frames | p50 22.7 µs · p99 38.7 µs · 46.2 k/s |
iceoryx2 ipc::Service u64 (zero-copy POD, same box) |
p50 250 ns · 3.37 M/s |
zenoh same-session 64-byte put |
p50 290 ns · 3.12 M/s |
| Record 1 500 pendulum steps + replay | bit-exact payloads |
| Pendulum demo, 1 500 × 2 ms steps | 5 ms wall (no render) |
iceoryx2 wins raw IPC latency — it is a zero-copy POD path with years of work
behind it, and the bench used an 8-byte u64 versus girder's 64-byte archived
struct. girder is ~0.6× that in-process and ~0.35× on SHM, with a
typed rkyv message layer, a sim clock, and bags in the same crate. That is the
product: enough middleware for a small robot team, not a DDS replacement.
Methodology and a copy-paste table: docs/benchmarks.md.
Re-run with cargo run --release -p girder-bench.
cargo test --release -p girder
cargo run --release -p girder-demo --bin pendulum
cargo run --release -p girder-demo --bin pendulum -- --render # ffmpeg
cargo run --release -p girder-cli -- --helpuse girder::{Domain, Qos, msg::Blob64};
let domain = Domain::builder("robot").simulated_clock().build()?;
let tx = domain.node("arm").advertise::<Blob64>("/ping", Qos::STREAMING)?;
let mut rx = domain.node("log").subscribe::<Blob64>("/ping", Qos::STREAMING)?;
tx.publish(&Blob64::with_seq(1))?;
assert_eq!(rx.try_recv()?.unwrap().msg.seq, 1);CLI (the rosbag / ros2 topic hz replacements):
girder record -o run.gdr --topic /joint_states --ty JointState --shm --domain robot
girder replay run.gdr --rate 1.0 --shm --domain robot
girder top --shm --domain robotcrates/girder core: Domain, Node, clock, bus, SHM, TCP, bags
crates/girder-cli `girder record|replay|top|topics`
crates/girder-mujoco sim bridge: JointState out, effort in, sim-owned clock
crates/girder-bevy Bevy ECS plugin: Transform ↔ girder::msg::Transform
crates/girder-demo pendulum GIF + bit-exact replay
crates/girder-bench latency/throughput vs iceoryx2 and zenoh
assets/pendulum.xml tiny MJCF used by the demo and the MuJoCo test
- Typed topics, tiny QoS. A topic is a name + an rkyv message type. QoS is history depth, latch, and max payload size. Overflow drops the oldest sample and counts it. There is no deadline / durability / liveliness matrix.
- The sim owns time.
Clock::simulated()starts at t = 0.tick(dt)advances it; a real-time factor of 0 runs as fast as the caller. Followers can subscribe to/clock(ClockMsg) and callClock::follow. - One bus, three transports. In-process is a per-subscriber queue.
Same-host uses a seqlock ring in
/dev/shm(lock-free readers, a short spinlock on the write side — Mara Bos's atomics book as the reference). Cross-host is length-prefixed frames over TCP (GIRDmagic). QUIC reuses that codec behind--features quic(self-signed, LAN-only). - Bags are boring on purpose.
.gdris append-only rkyv + timestamps + an FNV checksum per record. Replay atrate = 0is as-fast-as-possible and is what CI uses for bit-exactness. - Bridges prove the seam, not the simulator.
girder-mujocosteps a model and publishesJointState.girder-bevyisbevy_ecsonly — no renderer — so a site twin can share poses without dragging wgpu into the bus crate.
- No discovery beyond a TCP bind/connect and a named SHM domain. No mDNS.
- No request/reply, no parameters, no TF tree. Publish/subscribe and time are the v0.1 surface.
- SHM slots are fixed-size (
Qos::max_bytes, default 4 KiB). Oversize payloads are an error, not a fragmented write. - TCP reconnect exists as a bounded backlog on the writer; there is no session persistence across process death.
- iceoryx2 and zenoh are benchmark targets, not pluggable backends (parked for post-v0.1).
- The pendulum grasp of "a robot on girder" is one hinge. A UR5e + mobile base on the same bus is the next demo, not this one.
Apache-2.0. See LICENSE.
