pebble-engine 0.13.0

A modular, ECS-style graphics/app framework for Rust.
Documentation
# Queries, Commands, and Entities

## Components and entities

An entity is just an id; components are plain Rust values attached to it. Spawn one with `Commands`:

```rust
fn spawn_entities(mut commands: Commands) -> Option<()> {
    commands.spawn((
        Name("wanderer"),
        Position { x: 0.0, y: 0.0 },
        Velocity { dx: 1.0, dy: 2.0 },
    ));
    Some(())
}

app.add_system(SystemStage::PreUpdate, spawn_entities.once());
```

Any tuple of types works as the component set — no registration step, no marker trait to implement. A component with no data at all (`struct Asleep;`) is a common, useful pattern purely for tagging entities so a query can filter by "has this," regardless of what else it carries.

## Querying

`Query<Q>` fetches every entity matching the component set `Q`. Iterate it with `.iter()`:

```rust
fn move_entities(mut query: Query<(&Name, &mut Position, &Velocity)>) {
    for (name, pos, vel) in query.iter() {
        pos.x += vel.dx;
        pos.y += vel.dy;
        println!("{} moved to ({:.1}, {:.1})", name.0, pos.x, pos.y);
    }
}
```

Mixing `&T` (read) and `&mut T` (write) in the same query is fine — `hecs` (the ECS crate Pebble builds on) borrow-checks it per-component at runtime, so this only panics if some *other* system concurrently holds a conflicting borrow, not merely because the query itself asks for both.

Include `Entity` directly in `Q` when you need the id back alongside the components — it's a query term like any other, not a separate mechanism:

```rust
fn despawn_far_away(mut commands: Commands, mut query: Query<(Entity, &Name, &Position)>) {
    for (entity, name, pos) in query.iter() {
        if pos.x.hypot(pos.y) > 10.0 {
            commands.despawn(entity);
        }
    }
}
```

Two more helpers for the cases where you don't want the whole result set:

- **`query.get(entity)`** — look up one known entity directly, without scanning the rest of the query.
- **`query.single()`** / **`query.get_single()`** — expect exactly one match (the player, the active camera). `single` panics if that's not true; `get_single` returns `None` instead.

## Commands: deferred mutation

`despawn_far_away` above calls `commands.despawn(entity)` *while a query over the same world is still borrowed* — safe only because `Commands` doesn't touch the world immediately. Every `Commands` call (`spawn`, `despawn`, `insert_resource`, ...) queues an operation into a command buffer, which is flushed once the current stage finishes running every system in it. This is also why a resource inserted via `commands.insert_resource(...)` isn't visible to a system *later in the same stage* — only from the next stage (or the next tick) onward; use `App::add_resource` directly (outside of a system) or `ResMut`/direct mutation inside a system when you need it visible immediately.

## Why the split?

Querying and mutating the same world simultaneously is exactly the kind of aliasing Rust's borrow checker exists to prevent — `Commands` sidesteps it by not aliasing at all: it just appends "do this later" to a buffer, and the actual mutation happens at a single well-defined point (the end of the stage) where nothing else is borrowed. It's the same trick most ECS frameworks use, under whatever name they give it (Bevy calls its version `Commands` too).