#ecs #component #entity #gamedev #send-sync

no-std sparsey

Entity Component System based on sparse sets

8 releases (4 breaking)

0.13.1 Oct 5, 2024
0.12.1 Jun 29, 2024
0.12.0 Jan 27, 2024
0.11.1 Sep 27, 2023
0.5.0 Nov 9, 2021

#77 in Game dev

Download history 6/week @ 2024-09-16 134/week @ 2024-09-23 179/week @ 2024-09-30 44/week @ 2024-10-07

1,071 downloads per month

MIT/Apache

145KB
4K SLoC

Sparsey

Crates.io Documentation

Sparsey is a sparse set-based Entity Component System (ECS).

Design Goals

  • Be flexible: Any Send + Sync + 'static type can be used as a component.
  • Be concise: The most commonly used functionalities should require the least amount of typing.
  • Make use of sparse sets: Provide features exclusive to sparse set-based ECS.

Example

    use sparsey::World;

    struct Position(i32, i32);
    struct Velocity(i32, i32);

    fn main() {
        let mut world = World::builder()
            .register::<Position>()
            .register::<Velocity>()
            .build();

        world.create((Position(0, 0), Velocity(1, 2)));
        world.create((Position(0, 0), Velocity(2, 3)));
    
        world.for_each::<(&mut Position, &Velocity)>(|(position, velocity)| {
            position.0 += velocity.0;
            position.1 += velocity.1;
        });
    }

Features

Powerful Queries

Get, include and exclude components using Sparsey's query API.

// Iter components A and B from entities with A and B.
world
    .query_all::<(&A, &B)>()
    .for_each(|_| ());

// Iter components A from entities with A and B.
world
    .query_all::<&A>()
    .include::<&B>()
    .for_each(|_| ());

// Iter components A from entities with A and without B.
world
    .query_all::<&A>()
    .exclude::<&B>()
    .for_each(|_| ());
    
// Iter components A from entities with A and B, without C.
world
    .query_all::<&A>()
    .include::<&B>()
    .exclude::<&C>()
    .for_each(|_| ());

Great Performance with Grouped Storages

Sparsey allows the user to "group" components to greatly optimize iteration performance. When a component group is formed, the World ensures that all components that belong to that group are stored in order at the beginning of their storages, making the iteration process a traversal of densely-packed arrays.

let mut world = World::builder()
    .add_group::<(A, B)>()
    .add_group::<(A, B, C, D>)>()
    .build();

Additionally, grouped components can be accessed as slices.

/// Get all entities with A and B as a slice.
world
    .query_all::<Entity>()
    .include::<(&A, &B)>
    .slice()
    .map(|_| ());

/// Get all A and B components of entities with A and B as a tuple of slices.
world
    .query_all::<(&A, &B)>()
    .slice()
    .map(|_| ());

/// Get all entities and A and B components of entities with A and B as a tuple
/// of slices.
world
    .query_all::<(Entity, &A, &B)>()
    .slice()
    .map(|_| ());

/// Get all A and B components of entities with A and B, and without C and D as
/// a tuple.
world
    .query_all::<(&A, &B)>()
    .exclude::<(&C, &D)>()
    .slice()
    .map(|_| ());

Thanks

Sparsey takes inspiration and borrows features from other free and open source ECS projects, namely Bevy, EnTT, Legion, Shipyard and Specs. Make sure you check them out!

License

Sparsey is dual-licensed under either

at your option.


Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above without any additional terms or conditions.

Dependencies

~0.5–1MB
~15K SLoC