latticeon 0.1.0

A math and ECS library focused on easy academic reproduction of animation, physics simulation, and AI
Documentation
//! Processor groups and hierarchy demo.
//!
//! Groups act as **barriers**: all processors in a group run (with topo + waves
//! within the group), then commands are applied, then the next group runs.
//! Use `#[tag("...")]` on processors for categorization/debugging only (no effect on execution).
//!
//! Run with: `cargo run -p latticeon --example ecs_groups`

use std::marker::PhantomData;

use latticeon::ecs::prelude::*;

#[derive(Debug, Clone, Component)]
struct Position(i32, i32);

#[derive(Debug, Clone, Component)]
struct Velocity(i32, i32);

#[derive(Debug, Clone, Component)]
struct Health(i32);

// ---------------------------------------------------------------------------
// Processors (optional #[tag] for categorization; groups define barriers)
// ---------------------------------------------------------------------------

#[derive(Default, Processor)]
#[tag("Physics")]
struct MoveProcessor {
    #[read]  _vel: PhantomData<Velocity>,
    #[write] _pos: PhantomData<Position>,
}

impl MoveProcessorRun for MoveProcessor {
    fn run(&self, view: &mut MoveProcessorView<'_>, _commands: &Commands) {
        view.pos.0 += view.vel.0;
        view.pos.1 += view.vel.1;
        println!("  [Physics] move {:?}: pos=({},{})", view.entity, view.pos.0, view.pos.1);
    }
}

#[derive(Default, Processor)]
#[tag("Physics")]
#[after(MoveProcessor)]
struct BoundsClampProcessor {
    #[write] _pos: PhantomData<Position>,
}

impl BoundsClampProcessorRun for BoundsClampProcessor {
    fn run(&self, view: &mut BoundsClampProcessorView<'_>, _commands: &Commands) {
        view.pos.0 = view.pos.0.clamp(-100, 100);
        view.pos.1 = view.pos.1.clamp(-100, 100);
        println!("  [Physics] clamp {:?}", view.entity);
    }
}

#[derive(Default, Processor)]
#[tag("AI")]
struct HealProcessor {
    #[read]  _pos: PhantomData<Position>,
    #[write] _hp: PhantomData<Health>,
}

impl HealProcessorRun for HealProcessor {
    fn run(&self, view: &mut HealProcessorView<'_>, _commands: &Commands) {
        view.hp.0 += 1;
        println!("  [AI] heal {:?} hp={}", view.entity, view.hp.0);
    }
}

#[derive(Default, Processor)]
#[tag("AI")]
#[after(HealProcessor)]
struct LogProcessor;

impl LogProcessorRun for LogProcessor {
    fn run(&self, commands: &Commands) {
        let _ = commands;
        println!("  [AI] log step (runs after HealProcessor)");
    }
}

fn main() {
    let mut universe = Universe::new();
    universe.spawn((Position(0, 0), Velocity(1, 2), Health(50)));
    universe.spawn((Position(10, -5), Velocity(-1, 0), Health(80)));

    // Build schedule using processor groups (and nested groups)
    let mut schedule = Schedule::new();

    // Named group "Physics": movement then bounds (order from execute_after)
    schedule.add_group("Physics", |g| {
        g.add_processor(MoveProcessor::default());
        g.add_processor(BoundsClampProcessor::default());
    });

    // Nested group: "AI" contains "Steering" (sub-group) and other processors
    schedule.add_group("AI", |g| {
        g.add_processor(HealProcessor::default());
        g.add_group("Steering", |inner| {
            inner.add_processor(LogProcessor::default());
        });
    });

    println!("=== Tick 1 (groups: Physics, AI/Steering) ===");
    schedule.run(&mut universe);
    println!("Entity count: {}\n", universe.entity_count());

    println!("=== Tick 2 ===");
    schedule.run(&mut universe);
    println!("Entity count: {}", universe.entity_count());
}