cheecs 0.1.0

A small, sparse-set-based Entity Component System with zero-boilerplate components.
Documentation
# cheecs

[![Crates.io](https://img.shields.io/crates/v/cheecs.svg)](https://crates.io/crates/cheecs)
[![Docs.rs](https://docs.rs/cheecs/badge.svg)](https://docs.rs/cheecs)
[![License](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg)](#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

| Method | What it does |
|---|---|
| `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.*