Skip to main content
ClaudeWave
Skill200 repo starsupdated 4d ago

gz-ecs-overview

Concise reference for the gz-sim Entity-Component-System architecture — how Entities, Components, Systems, the ECM, the Server, and SimulationRunner fit together. Trigger when the user asks how gz-sim is organized, where to add code, or how the simulation loop runs.

Install in Claude Code
Copy
git clone --depth 1 https://github.com/harunkurtdev/ros2-claude-code-template /tmp/gz-ecs-overview && cp -r /tmp/gz-ecs-overview/.claude/skills/gz-ecs-overview ~/.claude/skills/gz-ecs-overview
Then start a new Claude Code session; the skill loads automatically.

SKILL.md

# gz-sim ECS architecture in 5 minutes

```
                ┌──────────────────────────────────────────┐
                │                 Server                   │
                │  (one process, owns N runners)           │
                └──────┬───────────────────────────────────┘
                       │
            ┌──────────▼──────────────────────────┐
            │         SimulationRunner            │
            │  - drives the iteration loop        │
            │  - owns the EventManager            │
            │  - owns the EntityComponentManager  │
            │  - owns the System list             │
            └──────────┬──────────────────────────┘
                       │ every step
   ┌───────────────────┼───────────────────────────────────────┐
   ▼                   ▼                   ▼                   ▼
Configure*       PreUpdate*           Update*             PostUpdate*
(once on load)   (mutates ECM)        (mutates ECM)       (read-only)
                                      └── Reset hooks on /reset
```

## The core types

| Type | Header | Role |
|------|--------|------|
| `Entity` | `Entity.hh` | Just a `uint64_t` ID. Means nothing on its own. |
| `components::Component<T, id>` | `components/Component.hh` | Strongly-typed data attached to an Entity. |
| `EntityComponentManager` | `EntityComponentManager.hh` | The world's database. Add/remove/query components. |
| `EventManager` | `EventManager.hh` | Pub/sub for cross-system events. |
| `System` (`ISystemConfigure`, `ISystemPreUpdate`, `ISystemUpdate`, `ISystemPostUpdate`, `ISystemReset`) | `System.hh` | Behaviour. Plugins implement one or more of these. |
| `SimulationRunner` | (internal) | Owns and drives the above. |
| `Server` | `Server.hh` | Public façade — `Run()`, `SetUpdatePeriod()`, etc. |

## Adding behaviour: pick a system phase

* **`Configure()`** — once on plugin load. Read SDF, cache handles, register
  topics. Do *not* mutate the world here unless you're spawning fixtures.
* **`PreUpdate()`** — every iteration, *before* physics. Apply commands
  (forces, joint targets), spawn entities.
* **`Update()`** — every iteration, alongside physics. Most user code does
  *not* belong here.
* **`PostUpdate()`** — every iteration, *after* physics. **Read-only ECM**
  access — emit telemetry, publish state, write logs. The ECM is `const`
  here for a reason.
* **`Reset()`** — when the world resets (e.g. via `/world/<name>/control`).
  Restore any internal state cached in the system.

## The "Cmd / state / Reset" component triplet

A common pattern in gz-sim:

* `<X>` — current value (e.g. `JointPosition`).
* `<X>Cmd` — request from a user system, consumed and cleared in
  `PreUpdate` by the physics-coupled system.
* `<X>Reset` — value to apply at the next world reset.

Look at `components/JointPositionReset.hh` / `JointVelocityCmd.hh` for the
canonical examples.

## Querying the ECM efficiently

Prefer `Each<>` views — they're cached and re-used across iterations:

```cpp
_ecm.Each<components::Joint, components::JointVelocity>(
  [&](const Entity &_ent,
      const components::Joint *,
      const components::JointVelocity *_vel) -> bool
  {
    // do work with *_vel
    return true; // keep iterating
  });
```

`EachNew`, `EachRemoved`, `EachChanged` exist for change-detection passes.

## Where things live

* **Component definitions** — `include/gz/sim/components/<Name>.hh`
  (header-only). Register serialization in `src/ComponentFactory.cc` when
  you want the component to survive log replay / save.
* **System plugins** — `src/systems/<snake_case>/<CamelCase>.{hh,cc}` plus
  an entry in `src/systems/CMakeLists.txt`. Use the `new-system` skill to
  scaffold.
* **High-level wrappers** — `Model`, `Link`, `Joint`, `Light`, `Actor`,
  `Sensor` (`include/gz/sim/`). These wrap an `Entity` plus ECM accessors
  and are usually how user code touches a model.

## Threading model

* `PreUpdate` / `Update` / `PostUpdate` of *different* systems run
  sequentially inside one runner iteration — not in parallel.
* `Server::Run(true, n)` blocks; `Run(false, n)` runs the loop on a worker
  thread. Multi-runner setups exist (different worlds), each on its own
  thread.
* Don't add long-running work in `Update()`. Spawn a thread in `Configure`,
  communicate via lock-free queues, drain in `PreUpdate`.

## Read the source, not just this

If you only have time for two files, read:

1. `include/gz/sim/EntityComponentManager.hh` — every comment matters.
2. `include/gz/sim/System.hh` — the interfaces are tiny and tell you
   exactly what each phase guarantees.
behaviortree-reviewerSubagent

Use proactively before opening a PR that adds or changes BehaviorTree.CPP nodes or BehaviorTree.ROS2 wrappers (RosActionNode/RosServiceNode/RosTopicPub/SubNode, TreeExecutionServer). Reviews a diff against BT.CPP v4 conventions — node base-class choice, non-blocking ticks, ports/blackboard typing, factory/plugin registration, XML v4, and the ROS 2 wrapper contract. Returns a punch list with file:line anchors, not a rewrite.

clean-arch-architectSubagent

Use when a design decision touches Clean Architecture boundaries in a ROS 2 project — which layer a new behaviour belongs to, whether a port belongs in domain or application, whether a new node should be lifecycle-managed, whether to compose nodes or split packages. Returns an architectural recommendation with trade-offs, not implementation.

ecs-architectSubagent

Use when a design decision touches the gz-sim ECS — where new state should live, which system phase should write it, how to avoid coupling, whether to add a component vs. a member variable, whether a new system should be split or merged with an existing one. Returns an architectural recommendation with trade-offs, not implementation.

gz-style-reviewerSubagent

Use proactively before opening any gz-sim PR. Reviews a diff against the project's C++17 style, ECS conventions, plugin registration patterns, CMake structure, test placement, Migration.md / Changelog.md expectations, and pre-commit configuration. Returns a punch list, not a rewrite.

ros2-controllers-reviewerSubagent

Use proactively before opening a PR that adds or changes a ros2_control controller, broadcaster, or hardware component (incl. URDF <ros2_control> bringup). Reviews a diff against ros2_controllers / ros2_control_demos conventions — controller & hardware lifecycle, command/state interface configuration, real-time safety of update()/read()/write(), generate_parameter_library usage, pluginlib registration, chainable-controller correctness, URDF wiring, and tests. Returns a punch list with file:line anchors, not a rewrite.

ros2-style-reviewerSubagent

Use proactively before opening any ROS 2 / Nav 2 PR. Reviews a diff against this template's Clean Architecture, ROS 2 communication, lifecycle, testing, and Nav 2 plugin conventions. Returns a punch list with file:line anchors, not a rewrite.

vda5050-reviewerSubagent

Use proactively before opening a PR that touches a VDA 5050 connector / fleet bridge. Reviews a diff against VDA 5050 v3.0.0 protocol compliance (topics, QoS, header rules, base/horizon, action state machine, schema validation) and the template's Clean Architecture for the MQTT↔Nav 2 bridge. Returns a punch list with file:line anchors, not a rewrite.

buildSlash Command

Build the colcon workspace (optionally a single package) and report the outcome.