necs 0.2.0

An ECS implementation, primarily serving as a learning project
Documentation
  • Coverage
  • 27.27%
    9 out of 33 items documented4 out of 10 items with examples
  • Size
  • Source code size: 112.52 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 4.73 MB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 18s Average build duration of successful builds.
  • all releases: 21s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • nullved/necs
    0 0 0
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • nullved

necs

crates.io badge

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.

[dependencies]
necs = "0.2.0" # Adjust path or git url as necessary

To enable the archetype-based storage:

[dependencies]
necs = { version = "0.2.0", features = ["archetypes"] }

Usage

Basic World

The default World allows you to spawn entities and attach components freely.

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.

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.

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.

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.