# cheecs
[](https://crates.io/crates/cheecs)
[](https://docs.rs/cheecs)
[](#license)
A small, dependency-free **Entity Component System** for Rust, built on sparse-set storage.
`cheecs` favors simplicity over raw throughput: no archetypes, no scheduler, no macros required
on your component types. Just entities, sparse sets, and queries.
## Features
- **Zero boilerplate components** — any `'static + Send + Sync` type is automatically a `Component`. No `#[derive(...)]` needed.
- **Sparse-set storage** — O(1) insert / remove / lookup per component type, contiguous dense arrays for cache-friendly iteration.
- **Generational entities** — `Entity { index, generation }` so stale handles to despawned entities are safely rejected instead of aliasing new ones.
- **Tuple queries** — query up to 16 component types at once as a plain tuple, e.g. `world.query::<(&Position, &Velocity)>()`.
- **No unsafe code** in the public API surface.
## Installation
```toml
[dependencies]
cheecs = "0.1"
```
## Quick example
```rust
use cheecs::prelude::*;
struct Position { x: f32, y: f32 }
struct Velocity { dx: f32, dy: f32 }
fn main() {
let mut world = World::default();
let player = world.spawn();
world.add_component(player, Position { x: 0.0, y: 0.0 }).unwrap();
world.add_component(player, Velocity { dx: 1.0, dy: 0.5 }).unwrap();
// A second entity with only a Position — it will be skipped by the query below.
let decoration = world.spawn();
world.add_component(decoration, Position { x: 5.0, y: 5.0 }).unwrap();
for (pos, vel) in world.query::<(&Position, &Velocity)>() {
println!("moving by ({}, {})", vel.dx, vel.dy);
let _ = pos; // read-only access for now
}
world.kill(&decoration).unwrap();
assert!(!world.is_alive(&decoration));
}
```
## Why sparse sets (and not archetypes)?
Archetype-based ECS (like `bevy_ecs` or `hecs`) group entities by their exact component
combination, which gives extremely fast iteration but makes adding/removing components an
O(archetype size) move. `cheecs` instead keeps one sparse set per component type:
- Adding/removing a component only touches that one sparse set — no data is moved between archetypes.
- Iteration over a query starts from the smallest matching sparse set and filters down, which
keeps things fast for common cases without needing to precompute archetype graphs.
- The trade-off: iterating a query with many component types has more indirection than a
columnar archetype scan. For most gamejam/hobby-project workloads this is a good trade.
## Core API
| `World::spawn()` | Creates a new `Entity` |
| `World::kill(&entity)` | Removes an entity and all of its components |
| `World::is_alive(&entity)` | Checks the entity's generation is still current |
| `World::add_component(entity, component)` | Attaches (or overwrites) a component |
| `World::remove_component::<T>(entity)` | Removes a component of type `T` |
| `World::query::<Q>()` | Iterates entities matching a tuple of `&Component` types |
## Status
`cheecs` is a young, hobby-scale project — the API may still shift before `1.0`. Feedback and
issues are welcome.
## License
Licensed under either of
- [MIT license](LICENSE-MIT)
- [Apache License, Version 2.0](LICENSE-APACHE)
at your option.
### Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion
in this project, as defined in the Apache-2.0 license, shall be dual licensed as above, without
any additional terms or conditions.
---
*Documentation and README drafted with AI assistance, reviewed and edited by the author.*