flax 0.7.1

An ergonomic archetypical ECS
Documentation
use flax::{component, EntityBuilder, FetchExt, Query, World};
use itertools::Itertools;

use flax::components::name;

#[test]
fn query_change() {
    component! {
        health: f32,
        pos: (f32, f32),
        // Distance from origin
        distance: f32,
    }

    let mut world = World::new();
    EntityBuilder::new()
        .set(name(), "A".into())
        .set(health(), 50.0)
        .set(pos(), (3.0, 1.0))
        .set_default(distance())
        .spawn(&mut world);

    EntityBuilder::new()
        .set(name(), "B".into())
        .set(health(), 89.0)
        .set(pos(), (3.0, 8.0))
        .set_default(distance())
        .spawn(&mut world);

    EntityBuilder::new()
        .set(name(), "C".into())
        .set(health(), 30.0)
        .set(pos(), (3.0, 8.0))
        .set_default(distance())
        .spawn(&mut world);

    EntityBuilder::new()
        .set(name(), "D".into())
        .set(health(), 70.0)
        .set(pos(), (3.0, 8.0))
        .set_default(distance())
        .spawn(&mut world);

    // Only those strong enough shall move
    let mut move_alive = Query::new((name(), pos().as_mut())).filter(health().gt(40.0));
    let mut consumer = Query::new((name(), pos(), distance().as_mut())).filter(pos().modified());

    // Ignore spawn changes to only capture `move_alive`
    consumer.borrow(&world).iter().for_each(|_| {});

    let moved = move_alive
        .borrow(&world)
        .iter()
        .map(|(name, pos)| {
            pos.0 += 1.0;
            pos.1 *= 0.99;
            name.to_string()
        })
        .sorted()
        .collect_vec();

    assert_eq!(moved, ["A", "B", "D"]);

    dbg!(world.change_tick(), &consumer);

    let consumed = consumer
        .borrow(&world)
        .iter()
        .map(|(name, pos, distance)| {
            *distance = (pos.0 * pos.0 + pos.1 * pos.1).sqrt();
            name.to_string()
        })
        .sorted()
        .collect_vec();

    assert_eq!(consumed, ["A", "B", "D"]);
    // Everything which is alive will move a bit
}

#[test]
fn query_opt() {
    component! {
        mass: f32,
        vel: f32,
        status_effect: String,
    }

    let mut world = World::new();
    EntityBuilder::new()
        .set(name(), "Alyx".to_string())
        .set(mass(), 70.0)
        .set(vel(), 1.0)
        .set(status_effect(), "Neurotoxin".to_string())
        .spawn(&mut world);

    EntityBuilder::new()
        .set(name(), "Gordon".to_string())
        .set(mass(), 95.0)
        .set(vel(), 1.5)
        .spawn(&mut world);

    EntityBuilder::new()
        .set(name(), "Citadel".to_string())
        .set(mass(), 1e9)
        .spawn(&mut world);

    let mut query = Query::new((name(), mass(), vel().opt_or_default()));

    let items = query
        .borrow(&world)
        .iter()
        .sorted_by_key(|v| v.0)
        .map(|(a, b, c)| (a.clone(), *b, *c))
        .collect_vec();

    assert_eq!(
        items,
        [
            ("Alyx".to_string(), 70.0, 1.0),
            ("Citadel".to_string(), 1e9, 0.0),
            ("Gordon".to_string(), 95.0, 1.5)
        ]
    );

    let mut query = Query::new((name(), status_effect().opt()));
    let mut query = query.borrow(&world);
    let items = query.iter().sorted().collect_vec();

    assert_eq!(
        items,
        [
            (&"Alyx".to_string(), Some(&"Neurotoxin".to_string())),
            (&"Citadel".to_string(), None),
            (&"Gordon".to_string(), None),
        ]
    );
}