gz-ecs-overview
gz-ecs-overview is a concise reference for Gazebo Simulation's Entity-Component-System architecture that explains how Entities, Components, Systems, the EntityComponentManager, and SimulationRunner interact within the simulation loop. Use this skill when a user asks how gz-sim is organized, where to add custom code, or how the simulation steps through Configure, PreUpdate, Update, and PostUpdate phases.
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-overviewSKILL.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.Scaffold a Behavior Tree leaf node — plain BehaviorTree.CPP (SyncActionNode / StatefulActionNode / ConditionNode) or a BehaviorTree.ROS2 wrapper (RosActionNode / RosServiceNode / RosTopicPubNode / RosTopicSubNode) — with ports, factory/plugin registration, and XML v4 usage. Trigger when the user asks to write a behavior-tree node (not Nav 2-specific).
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.
Build the colcon workspace (optionally a single package) and report the outcome.
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.
Author a new skill under .claude/skills/<name>/SKILL.md with valid frontmatter, following the template conventions, and index it in CLAUDE.md + README.md.
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.
Author a new asset for this .claude/ template — a rule, skill, slash command, or sub-agent — following the project's conventions and wiring it into the CLAUDE.md / README.md indexes. Trigger when the user wants to add or extend a command, skill, agent, or rule (make the template itself extensible).
Configure and build gz-sim from source using either CMake/Ninja directly or a colcon workspace. Trigger when the user asks to build, recompile, configure, or set up the gz-sim binary tree.