Introduction

Flax is an easy to use Entity Component System.

What is an ECS

ECS, or Entity Component System is a design paradigm of where the state of the program is structured around multiple Entities, where each entity may have zero or more components attached to it.

Systems execute upon the entities and their components.

The main benefit of this priniciple is that the logic is separate from the data, and new functionality can be added to existing entities and components.

How it works

In Flax, there are 3 fundamental building blocks.

Entity. A unique identifier for the entities of the program. Has a managed lifecycle.

Component, data which can be added to an Entity. Has a unique Id, which works as the key for storing and retrieving the value, and a strongly typed value.

System functions which execute on the world or a group of entities. Provides the logic of the program.

Fundamentals

This chapter goes through the basic of using Flax.

To get started, add flax to the Cargo.toml file

flax = "0.2"

World

The world holds the entities and components in the ECS.

#![allow(unused)]
fn main() {
    let mut world = World::new();
}

Spawning an entity yields an Entity id.

#![allow(unused)]
fn main() {
    let id = world.spawn();

    if world.is_alive(id) {
        println!("It is alive!");
    }
}

When an entity is despawned, it can no longer be accessed.

Entity ids are versioned, which means that once an entity is despawned the index in the storage may be reused, but it will have a different version, which prevents dead entities to become alive at a later point in time. I.e; dead entities stay dead, this is not a zombie apocalypse we are working with.

#![allow(unused)]
fn main() {
    world.despawn(id)?;

    if world.is_alive(id) {
        println!("We've got a zombie on our hands");
    }

}

Many entities can be spawned at a time, which is easily demonstrated by this iterator which takes entity ids from the world

#![allow(unused)]
fn main() {
    let ids = world.spawn_many().take(10).collect_vec();
    println!("ids: {ids:?}");
}

Components

A component represents data which is attached to an entity.

Note: Compared to other Rust ECS implementations, a component is not the same as the underlying type. This allows different components of the same datatype to coexist without having to use newtypes and forward all traits, and implement Into and From implementations.

#![allow(unused)]

fn main() {
    component! {
        /// Represents the position in the world
        position: (f32, f32),
    }

}

This in turn exposes a function that will return the component id, as component ids are lazily allocated due to lack of compile time unique ids.

The component can be added, accessed and removed from entities using World::set, World::get, and World::remove.

#![allow(unused)]
fn main() {
    let id = world.spawn();

    // Add a component to `id`
    world.set(id, position(), (1.0, 4.0))?;

    {
        let val = world.get(id, position())?;

        println!("The entity is at: {val:?}");
    }

    // This will overwrite the previous value
    world.set(id, position(), (1.0, 4.5))?;

    {
        // Mutate the component
        let mut pos = world.get_mut(id, position())?;
        pos.1 += 1.0;
    }

    println!("The entity is now at: {:?}", world.get(id, position())?);

}

Accessing a component mutably does not require a mutable access to the world, as it uses an AtomicRefCell.

Multiple different components can be accessed simultaneously, even on the same entity.

Default components

Flax provides some opinionated default components to ease the communication between different libraries and users.

  • name: Provides a name for entities and components.
  • child_of: Default dataless hierarchy relation. See: Relations

Query

A query is the beating heart of the ECS.

They provide a declarative method to iterate, modify, and inspect the world's entities and their components.

In short a query is a declaration of which components to access, though they allow for so much more, such as filtering or excluding entities with certain components, change detection, relationship and graph traversal and much much more.

use flax::{
    component, entity_ids, CommandBuffer, Component, ComponentMut, Debuggable, Entity,
    EntityBorrow, FetchExt, Query, QueryBorrow, Schedule, System, World,
};
use glam::{vec2, Vec2};
use rand::{rngs::StdRng, Rng, SeedableRng};

fn main() -> anyhow::Result<()> {
    let mut world = World::new();

    component! {
        position: Vec2 => [ Debuggable ],
        health: f32 => [ Debuggable ],
    }

    // Spawn two entities
    let id = Entity::builder()
        .set(position(), vec2(1.0, 4.0))
        .set(health(), 75.0)
        .spawn(&mut world);

    let id2 = Entity::builder()
        .set(position(), vec2(-1.0, 4.0))
        .set(health(), 75.0)
        .spawn(&mut world);

    let mut query = Query::new((position(), health()));

    for (pos, health) in &mut query.borrow(&world) {
        println!("pos: {pos:?}, health: {health}");
    }


    component! {
        /// Distance to origin
        distance: f32 => [ flax::Debuggable ],
    }

    println!("Spawning id3");
    let id3 = world.spawn();
    world.set(id3, position(), vec2(5.0, 6.0))?;
    world.set(id3, health(), 5.0)?;

    for id in [id, id2, id3] {
        println!("Adding distance to {id}");
        world.set(id, distance(), 0.0)?;
    }

    let mut query = Query::new((entity_ids(), position(), distance().as_mut()))
        .with_filter(position().modified() & health().gt(0.0));

    println!("Updating distances");
    for (id, pos, dist) in &mut query.borrow(&world) {
        println!("Updating distance for {id} with position: {pos:?}");
        *dist = pos.length();
    }



    println!("Running query again");
    for (id, pos, dist) in &mut query.borrow(&world) {
        println!("Updating distance for {id} with position: {pos:?}");
        *dist = pos.length();
    }


    *world.get_mut(id2, position())? = vec2(8.0, 3.0);

    println!("... and again");
    for (id, pos, dist) in &mut query.borrow(&world) {
        println!("Updating distance for {id} with position: {pos:?}");
        *dist = pos.length();
    }


    #[allow(unused_variables)]
    {
        // Instead of this:
        let query = Query::new((position(), health(), distance()))
            .with_filter(position().modified() & health().modified());

        // Do this:
        let query = Query::new((position().modified(), health().modified(), distance()));
    }


    let mut update_distance = System::builder()
        .with_name("update_distance")
        .with_query(query)
        .build(
            |mut query: QueryBorrow<(_, Component<Vec2>, ComponentMut<f32>), _>| {
                for (id, pos, dist) in &mut query {
                    println!("Updating distance for {id} with position: {pos:?}");
                    *dist = pos.length();
                }
            },
        );

    update_distance.run(&mut world);

    let mut update_dist = System::builder()
        .with_name("update_distance")
        .with_query(
            Query::new((entity_ids(), position(), distance().as_mut()))
                .with_filter(position().modified()),
        )
        .for_each(|(id, pos, dist)| {
            tracing::debug!("Updating distance for {id} with position: {pos:?}");
            *dist = pos.length();
        });

    for _ in 0..16 {
        update_dist.run(&mut world);
    }


    // Despawn all entities with a distance > 50
    let despawn = System::builder()
        .with_name("delete_outside_world")
        .with_query(Query::new((entity_ids(), distance())).with_filter(distance().gt(50.0)))
        .with_cmd_mut()
        .build(|mut q: QueryBorrow<_, _>, cmd: &mut CommandBuffer| {
            for (id, &dist) in &mut q {
                println!("Despawning {id} at: {dist}");
                cmd.despawn(id);
            }
        });

    let debug_world = System::builder()
        .with_name("debug_world")
        .with_world()
        .build(|world: &_| {
            tracing::debug!("World: {world:?}");
        });


    component! {
        is_static: () => [ flax::Debuggable ],
    }

    // Spawn 150 static entities, which wont move
    let mut rng = StdRng::seed_from_u64(42);

    for _ in 0..150 {
        let pos = vec2(rng.gen_range(-5.0..5.0), rng.gen_range(-5.0..5.0));
        Entity::builder()
            .set(position(), pos)
            .set_default(distance())
            .set_default(is_static())
            .spawn(&mut world);
    }

    // Since this system will move non static entities out from the origin, they will
    // eventually be despawned
    let move_out = System::builder()
        .with_name("move_out")
        .with_query(Query::new(position().as_mut()).with_filter(is_static().without()))
        .for_each(|pos| {
            let dir = pos.normalize_or_zero();

            *pos += dir;
        });

    // Spawn new entities with a random position each frame
    let spawn = System::builder().with_name("spawner").with_cmd_mut().build(
        move |cmd: &mut CommandBuffer| {
            for _ in 0..100 {
                let pos = vec2(rng.gen_range(-10.0..10.0), rng.gen_range(-10.0..10.0));
                println!("Spawning new entity at: {pos:?}");
                Entity::builder()
                    .set(position(), pos)
                    .set_default(distance())
                    .spawn_into(cmd);
            }
        },
    );

    let mut frame_count = 0;

    // Count the number of entities in the world and log it
    let count = System::builder()
        .with_name("count")
        .with_query(Query::new(()))
        .build(move |mut query: QueryBorrow<()>| {
            let count: usize = query.iter_batched().map(|v| v.len()).sum();
            println!("[{frame_count}]: {count}");
            frame_count += 1;
        });

    // Assemble the schedule, takes care of dependency management
    let mut schedule = Schedule::builder()
        .with_system(update_dist)
        .with_system(despawn)
        .with_system(spawn)
        .with_system(move_out)
        .with_system(debug_world)
        .with_system(count)
        .build();

    println!("{schedule:#?}");

    for i in 0..20 {
        println!("Frame: {i}");
        println!("Batches: {:#?}", schedule.batch_info(&world));
        schedule.execute_par(&mut world)?;
    }


    component! {
        window_width: f32,
        window_height: f32,
        allow_vsync: bool,

        /// A static entity, which is always alive
        resources,
    }

    Entity::builder()
        .set(window_width(), 800.0)
        .set(window_height(), 600.0)
        .set(allow_vsync(), false)
        // Since `resources` is static, it is not required to spawn it
        .append_to(&mut world, resources())
        .unwrap();

    let mut query = Query::new((window_width(), window_height(), allow_vsync()))
        // Change the query strategy to only iterate the `resources` entity
        .entity(resources());

    let mut borrow = query.borrow(&world);
    let (width, height, vsync) = borrow.get().unwrap();
    println!("width: {width} height: {height}, vsync: {vsync}");


    drop(borrow);


    let mut window_system = System::builder()
        .with_query(query)
        .build(|mut q: EntityBorrow<_>| {
            if let Ok((width, height, allow_vsync)) = q.get() {
                println!(
                    "Config changed width: {width}, height: {height}, allow_vsync: {allow_vsync}"
                );
            } else {
                println!("No config change");
            }
        });

    window_system.run(&mut world);
    window_system.run(&mut world);
    world.set(resources(), window_height(), 720.0)?;
    window_system.run(&mut world);


    Ok(())
}

A query accepts any type which implements Fetch, such as

See Queries for more details

Filters

A query allows for filters, such as skipping entities which have a certain components, or where the value of a component satisfies some condition. This will efficiently skip entire archetypes or ranges of entities.

The following example shows a query which will update the distance to origin when an entity moves for every entity.

#![allow(unused)]

fn main() {
    component! {
        /// Distance to origin
        distance: f32 => [ flax::Debuggable ],
    }

    println!("Spawning id3");
    let id3 = world.spawn();
    world.set(id3, position(), vec2(5.0, 6.0))?;
    world.set(id3, health(), 5.0)?;

    for id in [id, id2, id3] {
        println!("Adding distance to {id}");
        world.set(id, distance(), 0.0)?;
    }

    let mut query = Query::new((entity_ids(), position(), distance().as_mut()))
        .with_filter(position().modified() & health().gt(0.0));

    println!("Updating distances");
    for (id, pos, dist) in &mut query.borrow(&world) {
        println!("Updating distance for {id} with position: {pos:?}");
        *dist = pos.length();
    }

}

The same query can be run again, but since all changes have been visited, it yields nothing.

#![allow(unused)]

fn main() {
    println!("Running query again");
    for (id, pos, dist) in &mut query.borrow(&world) {
        println!("Updating distance for {id} with position: {pos:?}");
        *dist = pos.length();
    }
}

However, if the position were to be modified, the query would yield that one change.

#![allow(unused)]

fn main() {
    *world.get_mut(id2, position())? = vec2(8.0, 3.0);

    println!("... and again");
    for (id, pos, dist) in &mut query.borrow(&world) {
        println!("Updating distance for {id} with position: {pos:?}");
        *dist = pos.length();
    }

}

For situations where an or combined filter is used in conjunction with a fetch of the same components, the filter may be attached directly to the query fetch instead.

#![allow(unused)]
fn main() {
        // Instead of this:
        let query = Query::new((position(), health(), distance()))
            .with_filter(position().modified() & health().modified());

        // Do this:
        let query = Query::new((position().modified(), health().modified(), distance()));
}

Systems

Maintaining queries and their associated data and logic can be verbose and error prone.

Systems provide the perfect aid and allows bundling arguments, such as queries, world access, and much more along with the logic to execute with it.

A system can be run manually or in a Schedule, which will automatically parallelize the system execution on multiple threads.

For example, to update distance from each position you could do:

#![allow(unused)]
fn main() {
        let update_distance = System::builder()
            .with_name("update_distance")
            .with_query(Query::new((entity_ids(), position(), distance().as_mut())))
            .build(
                |mut query: QueryBorrow<(_, Component<Vec2>, ComponentMut<f32>), _>| {
                    for (id, pos, dist) in &mut query {
                        println!("Updating distance for {id} with position: {pos:?}");
                        *dist = pos.length();
                    }
                },
            );
}

The system construction can subsequently be extracted to a function

#![allow(unused)]
fn main() {
        fn update_distance_system() -> BoxedSystem {
            System::builder()
                .with_name("update_distance")
                .with_query(Query::new((entity_ids(), position(), distance().as_mut())))
                .build(
                    |mut query: QueryBorrow<(_, Component<Vec2>, ComponentMut<f32>), _>| {
                        for (id, pos, dist) in &mut query {
                            println!("Updating distance for {id} with position: {pos:?}");
                            *dist = pos.length();
                        }
                    },
                )
                .boxed()
        }
    }
    let mut update_distance = update_distance_system();

}

However, the query won't yield entities since none have the distance component to modify. No to fear, we could add another system which ensures the component is present.

#![allow(unused)]
fn main() {
    fn add_distance_system() -> BoxedSystem {
        let query = Query::new(entity_ids())
            .with(position())
            .without(distance());

        System::builder()
            .with_cmd_mut()
            .with_query(query)
            .build(
                |cmd: &mut CommandBuffer, mut query: QueryBorrow<'_, flax::EntityIds, _>| {
                    for id in &mut query {
                        cmd.set(id, distance(), 0.0);
                    }
                },
            )
            .boxed()
    }
}

For each

Most systems iterate each item of a single query, without any other system arguments.

The shorthand for_each is provided for this purpose

#![allow(unused)]
fn main() {
    fn update_distance_system() -> BoxedSystem {
        System::builder()
            .with_name("update_distance")
            .with_query(Query::new((entity_ids(), position(), distance().as_mut())))
            .for_each(|(id, pos, dist)| {
                println!("Updating distance for {id} with position: {pos:?}");
                *dist = pos.length();
            })
            .boxed()
    }

}

Schedule

Lets add some more systems and add them to a Schedule

#![allow(unused)]
fn main() {
    /// Despawn all entities with a distance > 50
    fn despawn_system() -> BoxedSystem {
        System::builder()
            .with_name("delete_outside_world")
            .with_query(Query::new((entity_ids(), distance())).with_filter(distance().gt(50.0)))
            .with_cmd_mut()
            .build(|mut q: QueryBorrow<_, _>, cmd: &mut CommandBuffer| {
                for (id, &dist) in &mut q {
                    println!("Despawning {id} at: {dist}");
                    cmd.despawn(id);
                }
            })
            .boxed()
    }

    fn inspect_system() -> BoxedSystem {
        System::builder()
            .with_name("debug_world")
            .with_world()
            .build(|_world: &_| {
                // println!("World: {_world:#?}");
            })
            .boxed()
    }

    component! {
        /// Entities with this component will not be moved
        is_static: () => [ flax::Debuggable ],
    }

    fn move_system() -> BoxedSystem {
        System::builder()
            .with_name("move_out")
            .with_query(Query::new(position().as_mut()).with_filter(is_static().without()))
            .for_each(|pos| {
                let dir = pos.normalize_or_zero();

                *pos += dir;
            })
            .boxed()
    }

    // Since this system will move non static entities out from the origin, they will
    // eventually be despawned

    // Spawn new entities with a random position each frame
    fn spawn_system(mut rng: StdRng, count: usize) -> BoxedSystem {
        System::builder()
            .with_name("spawn")
            .with_cmd_mut()
            .build(move |cmd: &mut CommandBuffer| {
                for _ in 0..count {
                    let pos = vec2(rng.gen_range(-10.0..10.0), rng.gen_range(-10.0..10.0));
                    println!("Spawning new entity at: {pos:?}");
                    Entity::builder()
                        .set(position(), pos)
                        .set_default(distance())
                        .spawn_into(cmd);
                }
            })
            .boxed()
    }

    let rng = StdRng::seed_from_u64(42);

    // Assemble the schedule, takes care of dependency management
    let mut schedule = Schedule::builder()
        .with_system(add_distance_system())
        .flush()
        .with_system(update_distance_system())
        .with_system(despawn_system())
        .with_system(spawn_system(rng, 1))
        .with_system(move_system())
        .with_system(inspect_system())
        .build();

    for i in 0..20 {
        println!("Frame: {i}");
        schedule.execute_par(&mut world)?;
    }


}

System access

One of the abilities of a system is to automatically extract what they need for their arguments from the schedule context.

This both increases their ease of use, but further assures that only the declared pieces of data will be accessed, which allows seamless fine-grained parallelization capabilities

Therefore, it is advised to keep your system arguments as specific as possible, such as using queries whenever possible, rather than the whole world.

Note: Systems which access the world will not paralellize with other systems as it may access anything.

#![allow(unused)]
fn main() {
    // Despawn all entities with a distance > 50
    let despawn = System::builder()
        .with_name("delete_outside_world")
        .with_query(Query::new((entity_ids(), distance())).with_filter(distance().gt(50.0)))
        .with_cmd_mut()
        .build(|mut q: QueryBorrow<_, _>, cmd: &mut CommandBuffer| {
            for (id, &dist) in &mut q {
                println!("Despawning {id} at: {dist}");
                cmd.despawn(id);
            }
        });

    let debug_world = System::builder()
        .with_name("debug_world")
        .with_world()
        .build(|world: &_| {
            tracing::debug!("World: {world:?}");
        });

}

Schedule

A schedule allows execution and paralellization of multiple systems.

The systems discussed in the previous chapter can be put into a schedule to contain the logic and order of execution.

In addition to executing systems one after another, a schedule can automatically paralellize execution and run multiple systems at the same time using rayon such that the observable effects occurr order.

In other words, if two systems have queries which do not access the same archetype and components, they will run in paralell. If an archetype reads a value which is written by another system declared before, they will run in sequence.

#![allow(unused)]
fn main() {
    let despawn = System::builder()
        .with_name("delete_outside_world")
        .with_query(Query::new((entity_ids(), distance())).with_filter(distance().gt(50.0)))
        .with_cmd_mut()
        .build(|mut q: QueryBorrow<_, _>, cmd: &mut CommandBuffer| {
            for (id, &dist) in &mut q {
                println!("Despawning {id} at: {dist}");
                cmd.despawn(id);
            }
        });

    let debug_world = System::builder()
        .with_name("debug_world")
        .with_world()
        .build(|world: &_| {
            tracing::debug!("World: {world:?}");
        });


    component! {
        is_static: () => [ flax::Debuggable ],
    }

    // Spawn 150 static entities, which wont move
    let mut rng = StdRng::seed_from_u64(42);

    for _ in 0..150 {
        let pos = vec2(rng.gen_range(-5.0..5.0), rng.gen_range(-5.0..5.0));
        Entity::builder()
            .set(position(), pos)
            .set_default(distance())
            .set_default(is_static())
            .spawn(&mut world);
    }

    // Since this system will move non static entities out from the origin, they will
    // eventually be despawned
    let move_out = System::builder()
        .with_name("move_out")
        .with_query(Query::new(position().as_mut()).with_filter(is_static().without()))
        .for_each(|pos| {
            let dir = pos.normalize_or_zero();

            *pos += dir;
        });

    // Spawn new entities with a random position each frame
    let spawn = System::builder().with_name("spawner").with_cmd_mut().build(
        move |cmd: &mut CommandBuffer| {
            for _ in 0..100 {
                let pos = vec2(rng.gen_range(-10.0..10.0), rng.gen_range(-10.0..10.0));
                println!("Spawning new entity at: {pos:?}");
                Entity::builder()
                    .set(position(), pos)
                    .set_default(distance())
                    .spawn_into(cmd);
            }
        },
    );

    let mut frame_count = 0;

    // Count the number of entities in the world and log it
    let count = System::builder()
        .with_name("count")
        .with_query(Query::new(()))
        .build(move |mut query: QueryBorrow<()>| {
            let count: usize = query.iter_batched().map(|v| v.len()).sum();
            println!("[{frame_count}]: {count}");
            frame_count += 1;
        });

    // Assemble the schedule, takes care of dependency management
    let mut schedule = Schedule::builder()
        .with_system(update_dist)
        .with_system(despawn)
        .with_system(spawn)
        .with_system(move_out)
        .with_system(debug_world)
        .with_system(count)
        .build();

    println!("{schedule:#?}");

    for i in 0..20 {
        println!("Frame: {i}");
        println!("Batches: {:#?}", schedule.batch_info(&world));
        schedule.execute_par(&mut world)?;
    }

}

Entity Builder

The EntityBuilder allows you to incrementally construct an entity by adding components and then inserting it into the world. This provides both better ergonomics and efficiency as the entity does not bounce around archetypes.

Additionally, the entity builder allows constructing an entity in the absence of the world, like a function which only creates the EntityBuilder.

#![allow(unused)]
fn main() {
    component! {
        health: f32 => [Debuggable],
        position: (f32, f32) => [Debuggable],
        is_player: () => [Debuggable],
    }

    let mut world = World::new();

    // Instead of this
    let player = world.spawn();
    world.set(player, health(), 100.0).unwrap();
    world.set(player, position(), (5.0, 2.3)).unwrap();
    world.set(player, is_player(), ()).unwrap();
    world.set(player, name(), "Player".into()).unwrap();

    tracing::info!("Player: {:#?}", world.format_entities(&[player]));
    world.despawn(player).unwrap();

    // Do this
    let player = Entity::builder()
        .set(health(), 100.0)
        .set(position(), (5.0, 2.3))
        .tag(is_player())
        .set(name(), "Player".into())
        .spawn(&mut world);

    tracing::info!("Player: {:#?}", world.format_entities(&[player]));

}

When the entity builder is spawned, the held components are moved out into the matching archetype in the world.

This means the entity builder is cleared and ready to insert more components.

#![allow(unused)]

fn main() {
    let mut builder = Entity::builder();
    let mut rng = StdRng::seed_from_u64(42);

    let enemies = (0..10)
        .map(|i| {
            builder
                .set(health(), rng.gen_range(50..100) as f32)
                .set(
                    position(),
                    (rng.gen_range(-10.0..10.0), rng.gen_range(-10.0..10.0)),
                )
                .set(name(), format!("Enemy.{i}"))
                .spawn(&mut world)
        })
        .collect_vec();

    tracing::info!("Enemies: {enemies:?}");
}
#![allow(unused)]

fn main() {
    let mut query = Query::new((name(), position(), is_player().opt(), health()));
    for (name, pos, is_player, health) in &mut query.borrow(&world) {
        tracing::info!("name: {name}, pos: {pos:?}, player: {is_player:?}, health: {health}");
    }

    // Or to only get the non players

    {
        let mut query = Query::new((name(), position(), health())).without(is_player());
        info_span!("enemies");
        for (name, pos, health) in &mut query.borrow(&world) {
            tracing::info!("name: {name}, pos: {pos:?}, health: {health}");
        }
    }

}

Batch Spawn

If inserting many entities of the same type, it is more efficient to let the world know about the coming components and insert everything at once.

The batch spawn allows spawning many entities with the same component types at once by inserting columns of each component type. This is akin to the native format of the archetype storage and is thus maximally efficient.

#![allow(unused)]

fn main() {
    let mut trees = BatchSpawn::new(10000);
    trees
        .set(name(), (0..).map(|i| format!("Tree.{i}")))
        .expect("Invalid length");

    trees
        .set(
            position(),
            (0..).map(|i| {
                let f = i as f32 / 32.0;
                (f.cos() * (1.0 + f / 2.0), f.sin() * (1.0 + f / 2.0))
            }),
        )
        .expect("Invalid length");

    let trees = trees.spawn(&mut world);

    tracing::info!("Trees: {:#?}", world.format_entities(&trees[0..100]));

}

Hierarchy

In the previous chapter, hierarchies were constructed by adding a relation to an entity to the child entity.

The entity builder allows construction of hierarchies before being spawned into the world.

#![allow(unused)]

fn main() {
    let id = Entity::builder()
        .set(name(), "parent".into())
        .attach(
            child_of,
            Entity::builder()
                .set(name(), "child1".into())
                .attach(child_of, Entity::builder().set(name(), "child1.1".into())),
        )
        .attach(child_of, Entity::builder().set(name(), "child2".into()))
        .spawn(&mut world);

    tracing::info!("Parent: {id}");

    tracing::info!("World: {world:#?}");

}

CommandBuffer

The commandbuffer allows deferred modification of the world.

This is useful in queries where the world is currently being iterated over, or in other situations where a mutable reference to the world is not available.

#![allow(unused)]

fn main() {
    component! {
        position: Vec2,
    }

    let mut world = World::new();

    let mut cmd = CommandBuffer::new();

    cmd.spawn(Entity::builder().set(name(), "a".into()));

    cmd.apply(&mut world)?;

    let id = Query::new(entity_ids())
        .with_filter(name().eq("a"))
        .borrow(&world)
        .iter()
        .next()
        .context("Missing entity")?;

    cmd.set(id, position(), vec2(32.0, 2.6));
    let id2 = world.spawn();

    cmd.spawn_at(
        id,
        EntityBuilder::new()
            .set(name(), "b".into())
            .set(position(), vec2(4.6, 8.4)),
    );

    cmd.remove(id2, position());

    cmd.apply(&mut world)?;

    cmd.set(id2, child_of(id), ());

    // Execute this function when the commandbuffer is applied
    cmd.defer(move |w| {
        w.despawn_recursive(id, child_of)?;
        Ok(())
    });

    cmd.apply(&mut world)?;
}

Usage with schedule

A schedule contains a commandbuffer which is available in systems through .write::<CommandBuffer>()

#![allow(unused)]
fn main() {
    component! {
        world_matrix: Mat4 => [Debuggable],
    }

    // Make sure there are always 64 entities in the world
    let mut rng = StdRng::seed_from_u64(42);
    let spawner = System::builder()
        .with_name("spawn_entities")
        .with_query(Query::new(()))
        .with_cmd_mut()
        .build(move |mut q: QueryBorrow<()>, cmd: &mut CommandBuffer| {
            let count = q.count();

            for _ in count..64 {
                tracing::info!("Spawning new entity");
                cmd.spawn(
                    Entity::builder()
                        .set(name(), "entity".to_string())
                        .set(position(), rng.gen()),
                );
            }
        });

    // Ensure a world matrix to each entity with a position
    let add_world_matrix = System::builder()
        .with_name("add_world_matrix")
        .with_query(Query::new((entity_ids(), position())).without(world_matrix()))
        .with_cmd_mut()
        .build(
            |mut q: QueryBorrow<(EntityIds, Component<Vec2>), _>, cmd: &mut CommandBuffer| {
                for (id, pos) in &mut q {
                    tracing::info!("Adding world matrix to {id}");
                    cmd.set(id, world_matrix(), Mat4::from_translation(pos.extend(0.0)));
                }
            },
        );

    // Update the world matrix if position changes
    let update_world_matrix = System::builder()
        .with_name("update_world_matrix")
        .with_query(
            Query::new((entity_ids(), position(), world_matrix().as_mut()))
                .with_filter(position().modified()),
        )
        .for_each(|(id, pos, ltw)| {
            tracing::info!("Updating world matrix for {id}");
            *ltw = Mat4::from_translation(pos.extend(0.0));
        });

    let mut schedule = Schedule::builder()
        .with_system(spawner)
        .flush()
        .with_system(add_world_matrix)
        .flush()
        .with_system(update_world_matrix)
        .build();

    schedule
        .execute_par(&mut world)
        .context("Failed to run schedule")?;

}

The commandbuffer will be applied at the end of the schedule automatically.

flush can be used to apply the commandbuffer to make the modifications visible to the following systems.

Relations

A relation is a component which links to another Entity, similar to a foreign key in a database. This can be used to construct different kinds of graphs and trees inside the ECS.

The links between entities are managed by the ECS itself and will always be valid, see Lifetime.

The linked entity is referred to as the target of a relation, while the entity the component is attached to is called the subject.

This allows forming hierarchies such as parent-child relations for transforms and UI, as well as arbitrary graphs.

See the child_of relation for an example of a parent-child relation which uses the parent entity as the relation's target.

Relations are most easily declared using the component macro, but can be constructed dynamically as well. See dynamic_components

For example, declaring a child relationship that connects to a parent can be done like so:

#![allow(unused)]
fn main() {
    component! {
        child_of(id): (),
    }

    let parent = Entity::builder()
        .set(name(), "Parent".into())
        .spawn(&mut world);

    let child1 = Entity::builder()
        .set(name(), "Child1".into())
        .set_default(child_of(parent))
        .spawn(&mut world);

    let child2 = Entity::builder()
        .set(name(), "Child2".into())
        .set_default(child_of(parent))
        .spawn(&mut world);
}

The parameter to the component function determines the target entity of the relation.

Since the value of the relation in this case is (), set_default can be used as a shorthand over set

Two relations of the same type but with different targets behave like two separate components and will not interfere. This allows having many-to-many relationships between entities, if so desired.

This allows constructing many different kinds of graphs inside the ECS.

#![allow(unused)]
fn main() {
    let parent2 = Entity::builder()
        .set(name(), "Parent2".into())
        .spawn(&mut world);

    world.set(child1, child_of(parent2), ())?;

    tracing::info!("World: {world:#?}");

    // Give child1 yet one more parent
    world.set(child1, child_of(parent2), ())?;

    tracing::info!(
        "Connections from child1({child1}): {:?}",
        Query::new(relations_like(child_of))
            .borrow(&world)
            .get(child1)?
            .collect_vec()
    );

}

Queries

Since relations are normal components, they can be used in a query as normal, or used to exclude components.

See the Graphs chapter in queries.

#![allow(unused)]
fn main() {
    // Mathes a relation exactly
    let children_of_parent: Vec<Entity> = Query::new(entity_ids())
        .with(child_of(parent))
        .collect_vec(&world);

    tracing::info!("Children: {children_of_parent:?}");

    // Matches a relation with any parent
    let all_children: Vec<Entity> = Query::new(entity_ids())
        .with_filter(child_of.with_relation())
        .collect_vec(&world);

    tracing::info!("Children: {all_children:?}");

    let roots = Query::new(entity_ids())
        .with_filter(child_of.without_relation())
        .collect_vec(&world);

    tracing::info!("Roots: {roots:?}");
}

Associated values

In addition to linking between entities, a relation can also store additional data just like a component. This can be used to create weighted graphs or storing other additional information such as physical joint parameters.

Since relations behave like separate components, each value on a relation is specific to that link, and as such saves you the hassle of managing a separate list of values for each connection on an entity.

The following shows a more complete example of how to traverse and calculate the forces between entities connected via springs using hook's law.

#![allow(unused)]
fn main() {
    struct Spring {
        strength: f32,
        length: f32,
    }

    impl Spring {
        fn new(strength: f32, length: f32) -> Self {
            Self { strength, length }
        }
    }
    component! {
        spring_joint(id): Spring,
        position: Vec2,
    }

    let id1 = Entity::builder()
        .set(name(), "a".into())
        .set(position(), vec2(1.0, 4.0))
        .spawn(&mut world);

    // Connect id2 to id1 with a spring of strength 2.0
    let id2 = Entity::builder()
        .set(name(), "b".into())
        .set(spring_joint(id1), Spring::new(2.0, 1.0))
        .set(position(), vec2(2.0, 0.0))
        .spawn(&mut world);

    let _id3 = Entity::builder()
        .set(name(), "c".into())
        .set(spring_joint(id1), Spring::new(2.0, 3.0))
        .set(position(), vec2(2.0, 3.0))
        .spawn(&mut world);

    let _id4 = Entity::builder()
        .set(name(), "d".into())
        .set(spring_joint(id2), Spring::new(5.0, 0.5))
        .set(position(), vec2(1.0, 0.0))
        .spawn(&mut world);

    let mut query = Query::new((entity_ids(), name().cloned(), position()))
        .with_strategy(Dfs::new(spring_joint));

    query
        .borrow(&world)
        .traverse(&None, |(id, name, &pos), strength, parent| {
            if let (Some(spring), Some((parent_name, parent_pos))) = (strength, parent) {
                let distance = pos.distance(*parent_pos) - spring.length;
                let force = distance * spring.strength;
                tracing::info!("spring acting with {force:.1}N between {parent_name} and {name}");
            } else {
                tracing::info!(%id, name, "root");
            }

            Some((name, pos))
        });
}

Exclusive relations

Relations can be declared as exclusive, which means that only one relation of that type can exist on an entity at a time. This is useful for cases where you want to have a single parent or outgoing connection.

Note: This does not prevent multiple entities from referencing the same entity, but rather an entity referencing multiple entities.

When a new relation is added to an entity, any existing relation of the same type will be removed.

This is the case for the included child_of relation.

#![allow(unused)]
fn main() {
    component! {
        child_of(parent): () => [ Exclusive ],
    }

    let id1 = Entity::builder().spawn(&mut world);
    let id2 = Entity::builder().spawn(&mut world);

    let id3 = Entity::builder()
        .set_default(child_of(id1))
        .spawn(&mut world);

    let entity = world.entity_mut(id3).unwrap();

    tracing::info!(
        "relations of {id3}: {:?}",
        entity.relations(child_of).map(|v| v.0).collect_vec()
    );

    world.set(id3, child_of(id2), ()).unwrap();

    let entity = world.entity_mut(id3).unwrap();
    tracing::info!(
        "relations of {id3}: {:?}",
        entity.relations(child_of).map(|v| v.0).collect_vec()
    );
}

Lifetime

Relations are managed by the ECS and will automatically be cleaned up. When an entity is despawned all relations which reference it will be removed from the ECS. As such, a relation will never point to an invalid entity.

#![allow(unused)]
fn main() {
    tracing::info!(
        "has relation to: {parent2}: {}",
        world.has(child1, child_of(parent2))
    );

    world.despawn(parent2)?;

    tracing::info!(
        "has relation to: {parent2}: {}",
        world.has(child1, child_of(parent2))
    );

    tracing::info!("World: {world:#?}");
    world.despawn_recursive(parent, child_of)?;

    tracing::info!("World: {world:#?}");
}

Component metadata

The keen eyed of you may have noticed that ComponentId is the same as Entity.

This is a design choice; a component is also an entity, and as such, exists in the world.

This brings some interesting possibilities which are not possible in other ECS systems, mainly: components can have components.

This allows the components to be queried just as they were normal entities, which allows reflection.

For example, a component can itself have a component which knows how to Debug::fmt the component value, another component could be used to serialize a value.

While components could be added to components through the conventional [World::set] syntax, it can quickly become a spaghetti of init functions for each library to add the required components, or metadata to the exported components.

This is where the component macro comes into play. The component function acquires a globally free Entity and assigns that to the strongly typed component. When the component is first inserted into the world it can insert so called metadata to the component.

#![allow(unused)]

fn main() {
    component! {
        /// An entity's health.
        /// Provides the Debug bundle, which adds the `debug_visitor` component.
        health: f32 => [ flax::Debuggable ],
    }

    // After adding the component, the associate metadata of the `health`
    // component is added to the world.
    world.set(id, health(), 100.0)?;

    let component_name = world.get(health().id(), name())?;
    println!("The name of the component is {component_name:?}");

    // Print the state of the world
    println!("World: {world:#?}");
    // Prints:
    //
    // World: {
    //     1v2: {},
    //     2v1: {},
    //     3v1: {},
    //     4v1: {},
    //     5v1: {},
    //     6v1: {},
    //     7v1: {},
    //     8v1: {},
    //     9v1: {},
    //     10v1: {},
    //     11v1: {
    //         "position": _,
    //         "health": 100.0,
    //     },
    // }

}

The

#![allow(unused)]
fn main() {
component: type => [ Meta, Meta, Meta ]
}

syntax is used to add metadata to the component.

The component component, and the name component is always present.

Debug

If a component has the flax::Debuggable component, the component and value for each entity will be present when debug formatting the world.

Name

Every component has a name, which is the same as the declaration.

Custom metadata

Custom metadata can be added to a component by creating a struct which implements MetaData, which is responsible of adding the appropriate components.

Note: Do not use conditional compilation for these structs as this leaks the cfg directives to any uses of the metadata in the component macro. Instead, conditionally add, or do not add, the component in the attach method.

A common example of this is serde, prefer to not add the serializer component and still define Serialize and making it no-op when serde is not enabled.

Queries

A query is the most useful tool in your ECS toolbox, and has many use cases.

This chapter explores the available methods and uses for queries

Basics

At its simplest form, a query is composed of a Fetch which specify the group of components to select. The query will visit the subset of entities in the world which have the specified components, and return their values.

In order to execute a query, it has borrow the relevant data from the world. This returns a QueryBorrow which can then be iterated.

#![allow(unused)]

fn main() {
        let mut query = Query::new(name());

        for name in &mut query.borrow(&world) {
            tracing::info!("Entity: {name:?}");
        }

}

A tuple can be used which will yield the entities with all the specified components, such as all entities with a name, position, and health; which excludes all the pretty rocks.

#![allow(unused)]

fn main() {
        let mut query = Query::new((name(), position(), health()));

        for (name, pos, health) in &mut query.borrow(&world) {
            tracing::info!("Entity: {name:?} pos: {pos}, health: {health}");
        }

}

Mutation

.as_mut will transform a component into yielding mutable references.

#![allow(unused)]

fn main() {
        fn lightning_strike(world: &World, rng: &mut StdRng) {
            let mut query = Query::new(health().as_mut());
            for h in &mut query.borrow(world) {
                // &mut f32
                *h -= rng.gen_range(10.0..20.0);
            }
        }

        lightning_strike(&world, &mut rng);

}

Optional

.opt makes a part of the fetch optional. This can be applied to a single component, or to the tuple or nested tuples as a whole.

In addition, .opt_or, and .opt_or_default allows specifying a fallback if the entity does not have the component.

#![allow(unused)]

fn main() {
        let mut query = Query::new((name(), position(), health().opt()));

        (&mut query.borrow(&world)).into_iter().for_each(
            |(name, pos, health): (&String, &Vec3, Option<&f32>)| {
                tracing::info!("Entity: {name:?} pos: {pos}, health: {health:?}");
            },
        );

}

Filters

A filter allows further specifying, and consequently narrowing, the subset of entities to visit.

For instance, a filter can allow querying the set of entities which have a specified component, or the set of entities which do not have the specified components, among others.

With and Without

Allows including or excluding a set of entities from the query depending on their components.

Combined with tag like components, a query which only yields the player can be achieved.

#![allow(unused)]

fn main() {
        let mut query = Query::new((name(), health())).with_filter(player().with());

        let mut borrow = query.borrow(&world);

        if let Some((name, health)) = borrow.iter().next() {
            tracing::info!("The player {name} is alive and well at {health} health");
        } else {
            tracing::info!("The player seems to have perished");
        }

}

... or everything that isn't a player, that of course still has the required name and health.

#![allow(unused)]

fn main() {
        let mut query = Query::new((name(), health())).with_filter(player().without());

        for (name, health) in &mut query.borrow(&world) {
            tracing::info!("Npc: {name} at {health} health");
        }

}

Combinators

Several filters can be combined using & and |, as well as !.

Comparison

It is also possible to filter on the value of a component.

This is different from using filter on the iterator, as the comparison will filter and select before the query traverses the entities, which means it avoids unnecessary modification events for mutable queries.

#![allow(unused)]

fn main() {
        let mut query = Query::new(name()).with_filter(health().without() | health().ge(35.0));
        for name in &mut query.borrow(&world) {
            tracing::info!("{name} is still standing strong");
        }

}

Change Detection

Flax tracks when a component is added, mutably accessed, or removed.

A query allows filtering the entities based on a change event since it last ran.

  • modified filter mutated or new components
  • added only new components
  • removed filter recently removed components.

The modified filter is best used for queries which calculate or update a value based on one or more components, or in other ways react to a changed value.

A change filter can be added to a single component, or to a tuple of components. Applying a .modified() transform on a tuple will create a query which yields if any of the constituents were modified.

The following example creates a system which prints the updated health values for each entity.

#![allow(unused)]

fn main() {
    let query = Query::new((name(), health().modified()));

    let health_changes = System::builder()
        .with_query(query)
        .build(|mut query: QueryBorrow<_>| {
            info_span!("health_changes");
            for (name, health) in &mut query {
                tracing::info!("{name:?}: is now at {health} health");
            }
        });

}

Combining filters

Change filters can be combined with other filters, which leads to queries needing to perform even even less work.

The following example creates a query which removes despawns entities when their health becomes 0. Noteworthy in particular, is that this system can run in parallel with the previously discussed system, since they do not overlap in mutable access.

#![allow(unused)]

fn main() {
    let query = Query::new((name().opt(), entity_ids(), player().satisfied()))
        .with_filter(health().le(0.0).modified());

    let cleanup = System::builder()
        .with_name("cleanup")
        .with_query(query)
        .with_cmd_mut()
        .build(|mut q: QueryBorrow<_, _>, cmd: &mut CommandBuffer| {
            for (name, id, is_player) in &mut q {
                if is_player {
                    tracing::info!("Player died");
                }
                tracing::info!(name, is_player, "Despawning {id}");
                cmd.despawn(id);
            }
        });

}

Bringing it all together

In order for the health monitoring and cleanup systems to be effective, there needs to be something to modify the health of entities.

Such as a random damage system, and a poison status effect.

#![allow(unused)]

fn main() {
    let player_id = Entity::builder()
        .set(name(), "player".into())
        .set(health(), 100.0)
        .set_default(player())
        .spawn(&mut world);

    let enemies = (0..10)
        .map(|i| {
            Entity::builder()
                .set(name(), format!("enemy.{i}"))
                .set(health(), 50.0)
                .spawn(&mut world)
        })
        .collect_vec();

    let all = enemies.iter().copied().chain([player_id]).collect_vec();

    let mut rng = StdRng::from_entropy();

    all.choose_multiple(&mut rng, all.len() / 5)
        .for_each(|&id| {
            world.set(id, poison(), 10.0).unwrap();
        });

    let damage_random = System::builder()
        .with_world_mut()
        .build(move |world: &mut World| {
            let count = rng.gen_range(0..enemies.len());
            let targets = all.choose_multiple(&mut rng, count);
            for &enemy in targets {
                if let Ok(mut health) = world.get_mut(enemy, health()) {
                    *health -= 1.0;
                }
            }
        });

    let update_poison = System::builder()
        .with_query(Query::new((name().opt(), health().as_mut(), poison())))
        .for_each(|(name, health, poison)| {
            *health -= poison;
            tracing::info!("{name:?} suffered {poison} in poison damage");
        });

}

Using a schedule allows for easy parallelization and execution of the systems, but is not a requirement for change detection.

#![allow(unused)]

fn main() {
    let mut schedule = Schedule::new()
        .with_system(damage_random)
        .with_system(update_poison)
        .with_system(health_changes)
        .flush()
        .with_system(cleanup)
        .flush();

    while world.is_alive(player_id) {
        schedule
            .execute_par(&mut world)
            .expect("Failed to run schedule");

        sleep(Duration::from_millis(1000));
    }

}

See the full example here

Implementation details

Each ChangeEvent consists of a subslice of adjacent entities in the same archetype, the change type, and when the change occurred.

Two change events where the entities are adjacent will be joined into a single one will be joined. This means the change list is always rather small compared to the number of changing entities (especially compared to using a HashSet).

The following example combines optional queries with change detection to create a small physic calculation.

Entity Query

By default, a query will iterate all entities which match the archetype.

However, the query strategy can be changed to only return a single entity, which is useful for queries over a resource entity or player.

#![allow(unused)]
fn main() {
    component! {
        window_width: f32,
        window_height: f32,
        allow_vsync: bool,

        /// A static entity, which is always alive
        resources,
    }

    Entity::builder()
        .set(window_width(), 800.0)
        .set(window_height(), 600.0)
        .set(allow_vsync(), false)
        // Since `resources` is static, it is not required to spawn it
        .append_to(&mut world, resources())
        .unwrap();

    let mut query = Query::new((window_width(), window_height(), allow_vsync()))
        // Change the query strategy to only iterate the `resources` entity
        .entity(resources());

    let mut borrow = query.borrow(&world);
    let (width, height, vsync) = borrow.get().unwrap();
    println!("width: {width} height: {height}, vsync: {vsync}");

}

In addition, an entity query can be used in a system

#![allow(unused)]

fn main() {
    let mut window_system = System::builder()
        .with_query(query)
        .build(|mut q: EntityBorrow<_>| {
            if let Ok((width, height, allow_vsync)) = q.get() {
                println!(
                    "Config changed width: {width}, height: {height}, allow_vsync: {allow_vsync}"
                );
            } else {
                println!("No config change");
            }
        });

    window_system.run(&mut world);
    window_system.run(&mut world);
    world.set(resources(), window_height(), 720.0)?;
    window_system.run(&mut world);


}

Graphs

The relation system allows creating Entity hierarchies and graphs.

These graphs can be traversed in different ways through the use of a Query.

The default child_of relation provides a mutually exclusive parent-child relation, which is perfect for transform hierarchies or trees, and is trivial to construct using the EntityBuilder::attach method.

#![allow(unused)]

fn main() {
    let root = Entity::builder()
        .set(name(), "root".into())
        .attach(
            child_of,
            Entity::builder().set(name(), "root.child.1".into()).attach(
                child_of,
                Entity::builder().set(name(), "root.child.1.1".into()),
            ),
        )
        .attach(
            child_of,
            Entity::builder().set(name(), "root.child.2".into()),
        )
        .attach(
            child_of,
            Entity::builder().set(name(), "root.child.3".into()),
        )
        .spawn(&mut world);

}

Likewise, attach_with can be used for stateful relations

Depth First Iteration

Dfs allows traversing hierarchies in depth-first order.

#![allow(unused)]

fn main() {
        let mut query = Query::new((entity_ids(), name())).with_strategy(Dfs::new(child_of));

        tracing::info!("Dfs:");
        for (id, name) in query.borrow(&world).iter() {
            tracing::info!(?id, ?name);
        }

}

Traversal

For modifying a graph through a value which is passed through the parents, such as updating a UI layout, there is Dfs::traverse which provides an easy recursion based visiting, which can for example be used for updating transform hierarchies.

Example:

#![allow(unused)]

fn main() {
    let update_world_position = System::builder()
        .with_query(
            Query::new((world_position().as_mut(), position())).with_strategy(Dfs::new(child_of)),
        )
        .build(
            |mut query: DfsBorrow<(ComponentMut<Vec3>, Component<Vec3>), All, ()>| {
                query.traverse(&Vec3::ZERO, |(world_pos, &pos), _, &parent_pos| {
                    *world_pos = pos + parent_pos;
                    *world_pos
                });
            },
        );

    let mut buf = String::new();
    let print_hierarchy = System::builder()
        .with_query(
            Query::new((name(), position(), world_position())).with_strategy(Dfs::new(child_of)),
        )
        .build(move |mut query: DfsBorrow<_, _, _>| {
            query.traverse(&0usize, |(name, pos, world_pos), _, depth| {
                let indent = depth * 4;
                writeln!(
                    buf,
                    "{:indent$}{name}: {pos} {world_pos}",
                    "",
                    indent = indent,
                )
                .unwrap();
                depth + 1
            });

            tracing::info!("{buf}");
        });

    let mut schedule = Schedule::new()
        .with_system(update_world_position)
        .with_system(print_hierarchy);

    schedule.execute_seq(&mut world).unwrap();

}

See: transform

Topological Iteration

In addition to depth first traversal, queries offer iteration in topological ordering through Topo.

Topological ordering is less constrained as it only ensures that each node's parents are visited before the children, but not that the children are visited immediately after the parent.

More specifically, each node is visited in the ordered of their maximum recursion depth. I.e, first all roots are visited, then all children, then all 2nd level children and so on.

This allows far greater cache locality and is more similar in memory access patterns to the non-relation aware Planar strategy.

Diving deeper

Advanced query

A query is a combination of a Fetch and a Filter in unison with a state.

The most common kind of Fetch is the component and the tuple fetch. The tuple combines multiple fetches into a single one.

A tuple of components, or any other Fetch will only yield entities which match the full tuple, I.e;

Optional query

#![allow(unused)]

fn main() {
    // Entities with mass
    (0..10).for_each(|i| {
        builder
            .set(name(), format!("Entity.{i}"))
            .set(position(), rng.gen::<Vec3>() * 10.0)
            .set(velocity(), rng.gen())
            .set(mass(), rng.gen_range(10..30) as f32)
            .spawn(&mut world);
    });

    // Entities without mass
    (0..100).for_each(|i| {
        builder
            .set(name(), format!("Entity.{i}"))
            .set(position(), rng.gen::<Vec3>() * 0.5)
            .set(velocity(), rng.gen())
            .spawn(&mut world);
    });

    // Since this query accessed `position`, `velocity` **and** `mass` only the
    // first group of entities will be matched
    for (pos, vel, mass) in &mut Query::new((position(), velocity(), mass())).borrow(&world) {
        tracing::debug!("pos: {pos}, vel: {vel}, mass: {mass}");
    }

}

Will only yield entities which have both position and velocity.

By adding .opt() to any fetch, you convert it to an Option<T> where T is the value yielded by the underlying fetch

#![allow(unused)]

fn main() {
    // Use an optional fetch to yield an `Option<T>`, works for any query
    for (pos, vel, mass) in &mut Query::new((position(), velocity(), mass().opt())).borrow(&world) {
        if mass.is_some() {
            tracing::debug!("Has mass");
        }
        tracing::debug!("pos: {pos}, vel: {vel}, mass: {mass:?}");
    }

}

However, it can still be cumbersome to deal with the option, especially since components yield references, which can not be combined with Option::unwrap_or_default

The opt_or_default combinator can be used, or opt_or to provide your custom value if the entity does not have the specified component.

There only work for fetches which return a shared reference, as the provided default is also returned by reference. For when there is an owned value, consider using the builting Option::unwrap_or_default.

This combinator is useful when writing systems which may need to operate on entities which lack some components, such as physics where the entity may not have a rotation.

Change detection

Every time a component is modified, either through World::get_mut, or a query, a Modified event is added to the affected entities.

Similarly, set when the component did not previously exist, and new entities will create an Added event.

Removal events are created by World::remove.

The following example makes use of optional combinators and change detection to handle a 3d world.

#![allow(unused)]

fn main() {
    component! {
        rotation: Quat,
        scale: Vec3,
        world_matrix: Mat4 => [Debuggable],
    }

    let create_world_matrix = System::builder()
        .with_name("add_world_matrix")
        .with_query(
            Query::new(entity_ids())
                .with(position())
                .without(world_matrix()),
        )
        .with_cmd_mut()
        .build(
            |mut query: QueryBorrow<EntityIds, _>, cmd: &mut CommandBuffer| {
                for id in &mut query {
                    tracing::info!("Adding world matrix to {id}");
                    cmd.set(id, world_matrix(), Mat4::IDENTITY);
                }
            },
        );

    let update_world_matrix = System::builder()
        .with_name("update_world_matrix")
        .with_query(
            Query::new((
                entity_ids(),
                world_matrix().as_mut(),
                position(),
                rotation().opt_or_default(),
                scale().opt_or(Vec3::ONE),
            ))
            .with_filter(position().modified() | rotation().modified() | scale().modified()),
        )
        .for_each(|(id, world_matrix, pos, rot, scale)| {
            tracing::info!("Updating world matrix for: {id} {pos} {rot} {scale}");
            *world_matrix = Mat4::from_scale_rotation_translation(*scale, *rot, *pos);
        });

    let mut schedule = Schedule::builder()
        .with_system(create_world_matrix)
        .flush()
        .with_system(update_world_matrix)
        .build();

    let all_ids = Query::new(entity_ids()).borrow(&world).iter().collect_vec();

    tracing::info!("Schedule: {schedule:#?}");

    for _ in 0..10 {
        schedule
            .execute_par(&mut world)
            .expect("Failed to execute schedule");

        for _ in 0..32 {
            let id = *all_ids.choose(&mut rng).expect("no ids");
            let mut pos = world.get_mut(id, position())?;
            // Move a bit away from origin
            let dir = pos.normalize();
            *pos += dir * rng.gen::<f32>();
            drop(pos);

            let mut scale = world.entry(id, scale())?.or_insert(Vec3::ONE);
            *scale *= 1.1;
        }

        sleep(Duration::from_secs(1))
    }

    tracing::info!("World: {world:#?}");

}

Implementation details

Each `ChangeEvent` consists of a subslice of adjacent entities in the same
archetype, the change type, and when the change occurred.

Two change events where the entities are adjacent will be joined into a single
one will be joined. This means the change list is always rather small compared
to the number of changing entities (especially compared to using a `HashSet`).

The following example combines optional queries with change detection to create
a small physic calculation.

Dynamic components

A component is nothing more than a type safe entity id.

The component uses a lazily acquired entity. It does not require the world since the entity is spawned in the STATIC global namespace which is shared across all worlds.

It is entirely possible to create a component at runtime for e.g; a local system.

#![allow(unused)]

fn main() {
    registry().with(HierarchicalLayer::default()).init();

    let mut world = World::new();

    let position: Component<Vec2> = world.spawn_component("position", |desc| {
        let mut buf = ComponentBuffer::new();
        <Debug as MetaData<Vec2>>::attach(desc, &mut buf);
        buf
    });

    let id = Entity::builder()
        .set(position, vec2(1.0, 6.4))
        .spawn(&mut world);

    tracing::info!("world: {world:#?}");

    // When `position` is despawned, it is removed from all entities.
    // This ensured that dead components never exist
    world.despawn(position.id())?;

    tracing::info!("world: {world:#?}");

    world.despawn(id)?;
}

The meta allows the user to provide a function to attach extra components to the entity. This is used by the => [ty, ty] syntax for the component macro.

The world will automatically manage the lifetime of the component to ensure that no entity has invalid components attached to it.

Relations

As a component is just a normal entity you can add an entity to another entity. Such as adding a parent entity to the children entities.

However, while this method allows quick and easy entity hierarchies, there is no notion of what the entity represents, and no way to distinguish it from another component.

This is where component parameterization comes to play.

An entity is 64 bits in size, sufficient for holding the kind, index, and generation.

However, since the world manages the lifetime of the component, the generation of 32 bits is freed up, which allows other data to be stored, such as another entity id.

This allows the upper bits of a component(entity) id to contain another entity as the generation is not needed.

#![allow(unused)]

fn main() {
    #[derive(Debug, Clone)]
    struct RelationData {
        // This allows you to add extra data to the child in the relation
        distance: f32,
    }

    let child_of = world.spawn_relation::<RelationData>("child_of", |desc| {
        let mut buf = ComponentBuffer::new();
        <Debug as MetaData<RelationData>>::attach(desc, &mut buf);
        buf
    });

    let parent = world.spawn();

    let child = Entity::builder()
        .set(child_of(parent), RelationData { distance: 1.0 })
        .spawn(&mut world);

    let data = world.get(child, child_of(parent))?;

    tracing::info!("Relation distance: {:?}", data.distance);

    drop(data);

    world.despawn(parent)?;
    assert!(world.get(child, child_of(parent)).is_err());

}

When despawning either the relation component or target entity, the "parent", the component is removed from all entities.

Serialization and deserialization

While the built in reflection system could be used for serialization, similar to Debug, deserialization can not. This is because the lazy registration of components mean that the world may not know of the componenets deserialization upfront, especially since deserialization yields a new world.

In addition, having serialization be implicit may lead to components not being serialized when they are expected to, or components which should not be serialized to be written to disk, such as local state. As such, it leads to unexpected, undesirable, or downright insecure behavior.

A similar story is to be found for deserialization, where program behaviour can be intrusively modified due to anything being able to be deserialized and put into the world.

As such [de]serialization is explicit and requires registering a subset of components.

#![allow(unused)]
fn main() {
    component! {
        position: Vec3 => [Debuggable],
        velocity: Vec3 => [Debuggable],
    }

    tracing_subscriber::fmt().init();

    use flax::serialize::{SerializationContextBuilder, SerializeFormat};
    tracing::info!("It works");

    let mut world = World::new();

    let mut rng = StdRng::seed_from_u64(239);

    let mut batch = BatchSpawn::new(16);

    batch.set(
        position(),
        (&mut rng).sample_iter(Standard).map(|v: Vec3| v * 2.0),
    )?;
    batch.set(velocity(), (&mut rng).sample_iter(Standard))?;
    batch.set(name(), (0..).map(|v| format!("id.{v}")))?;

    batch.spawn(&mut world);

    let mut batch = BatchSpawn::new(8);

    batch.set(
        position(),
        (&mut rng).sample_iter(Standard).map(|v: Vec3| v * 2.0),
    )?;
    batch.set(name(), (16..).map(|v| format!("id.{v}")))?;
    batch.spawn(&mut world);

}

We are interested in name, position, and velocity, nothing else, even if it implements Serialize.

#![allow(unused)]
fn main() {
    let (serializer, deserializer) = SerializationContextBuilder::new()
        .with(name())
        .with(position())
        .with(velocity())
        .build();

    let json =
        serde_json::to_string_pretty(&serializer.serialize(&world, SerializeFormat::RowMajor))?;

    // eprintln!("World: {json}");
}

When deserializing a world it is often of interest in merging it or deserializing into another world.

This is supported through the merge_with function, which will migrate colliding ids to new ones, returning a map in doing so.

The advantage of doing it this way is that the world is left untouched if deserialization failed.

#![allow(unused)]

fn main() {
    // An existing world with entities in it
    let mut world = World::new();

    let mut batch = BatchSpawn::new(32);

    batch.set(
        position(),
        (&mut rng).sample_iter(Standard).map(|v: Vec3| v * 2.0),
    )?;
    batch.set(name(), (0..).map(|v| format!("other_id.{v}")))?;
    batch.spawn(&mut world);

    let mut result = deserializer.deserialize(&mut serde_json::Deserializer::from_str(&json))?;

    // Merge `result` into `world`
    world.merge_with(&mut result);

    // eprintln!("World: {world:#?}");

}