flax 0.7.1

An ergonomic archetypical ECS
Documentation
use std::f32::consts::TAU;

use flax::{components::name, *};
use glam::{vec3, Vec3};
use itertools::Itertools;
use rand::{rngs::StdRng, Rng, SeedableRng};
use tracing::info_span;
use tracing_subscriber::{prelude::*, registry};
use tracing_tree::HierarchicalLayer;

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

    component! {
        health: f32,
        position: Vec3,
        velocity: Vec3,
        player: (),
    }

    let mut world = World::new();

    // Spawn a single player, and mark it with the `player` component
    Entity::builder()
        .set(name(), "player".into())
        .set(health(), 100.0)
        .set_default(position())
        .set_default(velocity())
        .set_default(player())
        .spawn(&mut world);

    // Enemies
    (0..10)
        .map(|i| {
            let theta = TAU * i as f32 / 10.0;
            Entity::builder()
                .set(name(), format!("enemy.{i}"))
                .set(health(), 50.0)
                .set(position(), vec3(5.0 * theta.cos(), 0.0, 5.0 * theta.sin()))
                .set_default(velocity())
                .spawn(&mut world)
        })
        .collect_vec();

    // Spawn rocks
    (0..64)
        .map(|i| {
            let r = 2.0 + i as f32 / 10.0;
            let theta = TAU * i as f32 / 10.0;
            Entity::builder()
                .set(name(), format!("rock.{i}"))
                .set(position(), vec3(r * theta.cos(), 0.0, r * theta.sin()))
                .spawn(&mut world)
        })
        .collect_vec();

    {
        let _span = info_span!("query_name").entered();

        // ANCHOR: query_name

        let mut query = Query::new(name());

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

        // ANCHOR_END: query_name
    }

    {
        let _span = info_span!("query_tuple").entered();
        // ANCHOR: query_tuple

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

        // ANCHOR_END: query_tuple
    }

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

    {
        let _span = info_span!("query_mut").entered();
        // ANCHOR: query_mut

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

        // ANCHOR_END: query_mut
    }

    {
        let _span = info_span!("query_opt").entered();
        // ANCHOR: query_opt

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

        // ANCHOR_END: query_opt
    }

    {
        let _span = info_span!("query_with").entered();
        // ANCHOR: query_with

        let mut query = Query::new((name(), health())).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");
        }

        // ANCHOR_END: query_with
    }

    {
        let _span = info_span!("query_without").entered();
        // ANCHOR: query_without

        let mut query = Query::new((name(), health())).filter(player().without());

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

        // ANCHOR_END: query_without
    }

    {
        let _span = info_span!("query_combinators").entered();
        // ANCHOR: query_combinators

        let mut query =
            Query::new((name(), health().opt())).filter(player().with() | health().without());

        for (name, health) in &mut query.borrow(&world) {
            if let Some(health) = health {
                tracing::info!("{name} at {health}");
            } else {
                tracing::info!("{name} (immortal)");
            }
        }

        // ANCHOR_END: query_combinators
    }

    {
        let _span = info_span!("query_cmp").entered();
        // ANCHOR: query_cmp

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

        // ANCHOR_END: query_cmp
    }
}