# necs

`necs` is a simple Entity Component System (ECS) written in Rust. It is designed primarily for learning purposes to understand how ECS architectures work under the hood.
It provides two distinct storage implementations:
1. **Default**: A simple Struct of Arrays (SoA) storage where components are stored in sparse vectors indexed by Entity ID.
2. **Archetypes**: An optional implementation where entities with the same set of components are grouped together in memory for better data locality.
## Features
* **Generational Entities**: Entity IDs are recycled with a generation counter
* **Type-Erased Storage**: Components can be any `'static` type
* **Flexible Querying**: Query all components of a specific type mutably or immutably
* **Archetype-Based Storage (Optional)**: Group entities by component composition for cache efficiency
* **Bundle API**: Create entities with multiple components at once or add/remove sets of components atomically
* **No Systems**: There are no built-in "Systems" or schedulers. You simply query the `World` and iterate over data to implement your logic
## Installation
Add `necs` to your `Cargo.toml`.
```toml
[dependencies]
necs = "0.2.0" # Adjust path or git url as necessary
```
To enable the archetype-based storage:
```toml
[dependencies]
necs = { version = "0.2.0", features = ["archetypes"] }
```
## Usage
### Basic World
The default `World` allows you to spawn entities and attach components freely.
```rust
use necs::{World, Component};
// 1. Define Components
#[derive(Debug, Clone, Copy, PartialEq)]
struct Position { x: f64, y: f64 }
#[derive(Debug, Clone, Copy, PartialEq)]
struct Velocity { x: f64, y: f64 }
fn main() {
// 2. Initialize World
let mut world = World::new();
// 3. Spawn Entities
let entity = world.spawn();
// 4. Add Components
world.add_component(&entity, Position { x: 0.0, y: 0.0 });
world.add_component(&entity, Velocity { x: 1.0, y: 0.5 });
// 5. Systems (Query and Iterate)
// In necs, a "system" is just a loop over data.
let mut velocities = world.query_component_mut::<Velocity>();
for vel in velocities {
vel.x += 0.1;
}
// Check results
let vel = world.get_component::<Velocity>(&entity).unwrap();
println!("New Velocity: {:?}", vel);
}
```
### Archetype World
If you enable the `archetypes` feature, you can use `ArchetypedWorld`. This moves entities into specific "Archetypes" (tables) based on their component signature. This makes adding/removing components slightly slower (due to moving memory), but iteration is cache-friendly.
```rust
use necs::{ArchetypedWorld, Component};
fn main() {
let mut world = ArchetypedWorld::new();
let entity = world.spawn();
// When you add components, the entity migrates to a new archetype automatically.
world.add_component(&entity, 100u32); // Health
world.add_component(&entity, "Player"); // Name
// Queries work the same way
let names = world.query_component::<&str>();
assert_eq!(names[0], &"Player");
}
```
### Using Bundles
The `archetypes` feature also enables the `Bundle` API, allowing you to spawn entities with a predefined set of components or add multiple components in a single call. This is more efficient as it reduces the number of archetype migrations.
```rust
use necs::{ArchetypedWorld, Component};
#[derive(Debug, Clone, Copy, PartialEq)]
struct Health(f32);
#[derive(Debug, Clone, Copy, PartialEq)]
struct Position { x: f32, y: f32 }
fn main() {
let mut world = ArchetypedWorld::new();
// Spawn an entity with multiple components at once
let entity = world.spawn_with((
Health(100.0),
Position { x: 10.0, y: 20.0 }
));
// Add multiple components to an existing entity
// This performs a single migration
world.add_components(&entity, (
"Hero",
true // IsAlive
));
// Remove multiple components at once
world.remove_components::<(Health, Position)>(&entity);
}
```
### Advanced Querying
The `archetypes` feature enables tuple-based queries, allowing you to iterate over entities that have *all* specific components.
```rust
use necs::{ArchetypedWorld, Component};
fn main() {
let mut world = ArchetypedWorld::new();
// ... spawn entities with Position and Velocity ...
// Iterate over all entities with both Position and Velocity
// This is highly optimized as it iterates over contiguous memory blocks
world.query::<(Position, Velocity)>().for_each(|(pos, vel)| {
println!("Entity at {:?} moving with {:?}", pos, vel);
});
// Mutable queries
world.query_mut::<(Position, Velocity)>().for_each(|(pos, vel)| {
pos.x += vel.x;
pos.y += vel.y;
});
}
```
## Disclaimer
`necs` is not intended to be a replacement for production-ready ECS frameworks like `bevy_ecs`, `specs`, or `hecs`. It lacks advanced features like:
* Parallel system execution.
* Complex query filters (e.g., `With`, `Without`, Joined queries).
* Change detection.
It serves as a reference implementation for educational exploration.