Macro gecs::ecs_world

source ·
macro_rules! ecs_world {
    {...} => { ... };
}
Expand description

Macro for declaring a new ECS world struct with archetype storage.

The ecs_world! macro is used for declaring an ECS world data structure to populate and perform queries on. All types used in the ECS world must be known at compile-time, and the full structure of each archetype must be declared with the world. Components may not be added or removed from entities at runtime.

Note that irrespective of capacity configuration, a single ECS archetype can hold at most 16,777,216 entities due to the encoding structure of the Entity type. For similar reasons, an ECS world can have only 256 distinct archetypes. Archetypes can store up to 16 distinct components by default. Use the 32_components crate feature to raise this limit to 32 components – note that this may impact compilation speed.

The ecs_world! macro has several inner pseudo-macros used for declaring archetypes or performing other tasks such as naming the ECS world’s data type. These are not true macros and have no purpose or meaning outside of the body of an ecs_world! declaration.

ecs_name!

ecs_name!(Name);

The ecs_name! inner pseudo-macro is used for setting the name (in PascalCase) of the ECS world struct. Without this declaration, the world’s name will default to EcsWorld.

ecs_archetype!

ecs_archetype!(Name, capacity, Component, ...);

The ecs_archetype! inner pseudo-macro is used for declaring an archetype in an ECS world. It takes the following arguments:

  • Name: The name (in PascalCase) of the archetype Rust type.
  • capacity: The capacity of the archetype, specified in one of the following ways:
    • A constant expression (e.g. 200 or config::ARCH_CAPACITY + 4). This will create a fixed-size archetype that can contain at most that number of entities.
    • The dyn keyword can be used to create a dynamically-sized archetype. This can grow to accommodate up to 16,777,216 entities. To initialize an ECS world’s dynamic archetype with a pre-allocated capacity, use the with_capacity() function at world creation. This function will be automatically generated with a named capacity argument for each dynamic archetype in that world.
  • Component, ...: One or more component types to include in this archetype. Because generated archetypes are pub with pub members, all components must be pub too.

The ecs_archetype! declaration supports the following attributes:

  • #[cfg] attributes can be used both on the ecs_archetype! itself, and on individual component parameters.
  • #[archetype_id(N)] can be used to override this archetype’s ARCHETYPE_ID to N (which must be between 0 and 255). By default, archetype IDs start at 0 and count up sequentially from the last value, similar to enum discriminants. No two archetypes may have the same archetype ID (this is compiler-enforced).

Example

use gecs::prelude::*;

// Components must be `pub`, as the ECS world will re-export them in its archetypes.
pub struct CompA(pub u32);
pub struct CompB(pub u32);
#[cfg(feature = "some_feature")] // CompC only exists if "some_feature" is enabled.
pub struct CompC(pub u32);

const BAR_CAPACITY: usize = 30;

ecs_world! {
    ecs_name!(MyWorld); // Set the type name of this ECS structure to MyWorld.

    // Declare an archetype called ArchFoo with capacity 100 and two components.
    ecs_archetype!(
        ArchFoo,
        100,
        CompA, // Note: Type paths are not currently supported for components.
        CompB,
    );

    // Declare ArchBar only if "some_feature" is enabled, otherwise it won't exist.
    #[cfg(feature = "some_feature")]
    ecs_archetype!(
        ArchBar,
        BAR_CAPACITY, // Constants may also be used for archetype capacity.
        CompA,
        CompC,
    );
    
    #[archetype_id(6)]
    ecs_archetype!(
        ArchBaz,
        dyn, // Use the dyn keyword for a dynamically-sized archetype.
        CompA,
        CompB,
        #[cfg(feature = "some_feature")]
        CompC, // ArchBaz will only have CompC if "some_feature" is enabled.
    );
}

fn main() {
    // Create a new world. Because ArchBaz is the only dynamic archetype, we only need to
    // set one capacity in world creation (the parameter is named capacity_arch_baz). The
    // other fixed-size archetypes will always be created sized to their full capacity.
    let mut world = MyWorld::with_capacity(30);

    // Create an ArchFoo entity in the world and unwrap the Option<Entity<ArchFoo>>.
    // Alternatively, we could use .create(), which will panic if the archetype is full.
    let entity_a = world.try_create::<ArchFoo>((CompA(0), CompB(1))).unwrap();

    // The length of the archetype should now be 1.
    assert_eq!(world.archetype::<ArchFoo>().len(), 1);

    // Destroy the entity (we don't need to turbofish because this is an Entity<ArchFoo>).
    world.destroy(entity_a);

    assert_eq!(world.archetype::<ArchFoo>().len(), 0);
    assert!(world.archetype::<ArchFoo>().is_empty());

    // Use of #[cfg]-conditionals.
    #[cfg(feature = "some_feature")] world.create::<ArchBar>((CompA(2), CompB(3), CompC(4)));
    world.create::<ArchBaz>((CompA(5), CompB(6), #[cfg(feature = "some_feature")] CompC(7)));

    // Use of #[archetype_id(N)] assignment.
    assert_eq!(ArchFoo::ARCHETYPE_ID, 0);
    assert_eq!(ArchBaz::ARCHETYPE_ID, 6);
    #[cfg(feature = "some_feature")] assert_eq!(ArchBar::ARCHETYPE_ID, 1);
}

Using an ECS World Across Modules

The ecs_world! macro locally generates a number of archetypes and macros, including its own ecs_find! and ecs_iter! macros and their borrow equivalents. These are all added to the module scope where the ecs_world! invocation exists, and are all marked pub. If you want to use a generated ECS world in another module or crate, you must import not only the world struct, but its archetypes and macros. The recommended way to do this is to wrap your ecs_world! declaration in its own prelude-like module and then glob import it:

use gecs::prelude::*;

pub struct CompA;
pub struct CompB;

pub mod my_world {
    pub mod prelude {
        // Pull in all the components we want to use as local identifiers.
        use super::super::*;

        ecs_world! {
            ecs_archetype!(ArchFoo, 10, CompA, CompB);
        }
    }
}

// Pull the world from another module/crate into scope with its archetypes and macros.
use my_world::prelude::*;

fn main() {
    let mut world = EcsWorld::default();
    let entity = world.create::<ArchFoo>((CompA, CompB));
    assert!(ecs_find!(world, entity, || {}).is_some());
}

Note that ecs_find!, ecs_iter!, and their borrow equivalents are generated specific to each world, and are scoped to the location of the ecs_world! that generated them. If you need to have multiple distinct ECS worlds in the same scope, you will need to disambiguate between their query macros manually.