flax 0.7.1

An ergonomic archetypical ECS
Documentation
use std::{thread::sleep, time::Duration};

use flax::{
    component, components::name, entity_ids, CommandBuffer, Entity, FetchExt, Query, QueryBorrow,
    Schedule, System, World,
};
use itertools::Itertools;
use rand::{rngs::StdRng, seq::SliceRandom, Rng, SeedableRng};
use tracing::info_span;
use tracing_subscriber::prelude::*;

fn main() {
    tracing_subscriber::registry()
        .with(tracing_tree::HierarchicalLayer::new(4))
        .init();

    // ANCHOR: setup

    let mut world = World::new();

    component! {
        health: f32,
        poison: f32,
        armor: f32,
        player: (),
    }

    // ANCHOR_END: setup
    //
    // ANCHOR: health_changes

    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");
            }
        });

    // ANCHOR_END: health_changes

    // ANCHOR: damage

    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");
        });

    // ANCHOR_END: damage

    // ANCHOR: cleanup_system

    let query = Query::new((name().opt(), entity_ids(), player().satisfied()))
        .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);
            }
        });

    // ANCHOR_END: cleanup_system

    // ANCHOR: schedule

    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));
    }

    // ANCHOR_END: schedule
}