retaker 0.8.6

a simple ecs implementation
Documentation
use macroquad::{
    color::WHITE,
    input::{is_key_down, is_key_released, KeyCode},
    math::{vec2, Vec2},
    miniquad::window::screen_size,
    rand::gen_range,
    shapes::draw_circle,
    window::{next_frame, screen_height},
};
use retaker::{
    work::{ThreadedWork, Work},
    world::{LockedWorld, World},
};

pub struct Camera {
    position: Vec2,
    scale: f32,
    follow_average: bool,
    hide_ui: bool,
}

pub struct ParticleParameters {
    delta_time: f32,
    particle_mass: f32,
    reset: bool,
    particle_count: f32,
    velocity_variation: f32,
}

pub struct Particle {
    position: Vec2,
    velocity: Vec2,
}

fn start_camera(world: &LockedWorld) {
    let mut world = world.lock_exclusive();

    world.create_resource(Camera {
        position: Vec2::ZERO,
        scale: 1.0,
        follow_average: false,
        hide_ui: false
    });
}

fn start_particles(world: &LockedWorld) {
    let mut world = world.lock_exclusive();

    world.create_resource(ParticleParameters {
        delta_time: 1.0 / 30.0,
        particle_mass: 1.0,
        reset: true,
        particle_count: 100.0,
        velocity_variation: 0.0,
    });

    reset_particles(&mut world);
}

fn reset_particles(world: &mut World) {
    if let Some(mut particles) = world.components_mut::<Particle>() {
        particles.clear();
    }
    let mut parameters = world.resource_mut::<ParticleParameters>().unwrap();
    parameters.reset = false;
    let particle_count = parameters.particle_count;
    let velocity_variation = parameters.velocity_variation;
    drop(parameters);
    for _ in 0..particle_count as usize {
        let particle = world.create_entity();
        world.insert(
            &particle,
            Particle {
                position: vec2(gen_range(0.0, 600.0), gen_range(0.0, 600.0)),
                velocity: vec2(
                    gen_range(-velocity_variation, velocity_variation),
                    gen_range(-velocity_variation, velocity_variation),
                ),
            },
        );
    }
}

fn update_particles(world: &LockedWorld) {
    let world = world.lock_shared();
    let parameters = world.resource::<ParticleParameters>().unwrap();
    let mut entities = world.components_mut::<Particle>().unwrap();
    let query = entities.query();
    for a_id in query.clone() {
        for b_id in query.clone() {
            if a_id == b_id {
                continue;
            }
            let [a, b] = entities.get_many_mut([&a_id, &b_id]).unwrap();

            let difference = (b.position - a.position) * parameters.particle_mass;
            a.velocity += difference * parameters.delta_time;
            b.velocity -= difference * parameters.delta_time;
        }
    }
    for id in query {
        let particle = entities.get_mut(&id).unwrap();
        particle.position += particle.velocity * parameters.delta_time;
    }
}

fn update_ui(world: &LockedWorld) {
    let world = world.lock_shared();
    let mut camera = world.resource_mut::<Camera>().unwrap();
    if camera.hide_ui {
        return;
    }
    let mut parameters = world.resource_mut::<ParticleParameters>().unwrap();
    macroquad::ui::root_ui().window(
        1,
        vec2(0.0, screen_height() - 160.0),
        vec2(360.0, 160.0),
        |ui| {
            parameters.reset = ui.button(vec2(0.0, 140.0), "reset");
            ui.slider(
                1,
                "particle mass",
                0.01..10.0,
                &mut parameters.particle_mass,
            );
            ui.slider(
                2,
                "particle count",
                3.0..1000.0,
                &mut parameters.particle_count,
            );
            ui.slider(3, "delta time", 0.001..1.0, &mut parameters.delta_time);
            ui.slider(4, "camera scale", 0.01..5.0, &mut camera.scale);
            ui.slider(
                5,
                "velocity variation",
                0.0..1000.0,
                &mut parameters.velocity_variation,
            );
            ui.checkbox(6, "camera follow", &mut camera.follow_average);
        },
    );
}

fn update_camera(world: &LockedWorld) {
    let world = world.lock_shared();
    let mut camera = world.resource_mut::<Camera>().unwrap();
    if is_key_released(KeyCode::Z) {
        camera.hide_ui = if camera.hide_ui {false} else {true};
    }
    if camera.follow_average {
        let entities = world.components::<Particle>().unwrap();
        let mut average = Vec2::ZERO;
        let mut particle_count = 0.;
        for id in entities.query() {
            let particle = entities.get(&id).unwrap();
            average += particle.position;
            particle_count += 1.;
        }
        camera.position =
            (average / particle_count) - Vec2::from(screen_size()) / 2. / camera.scale;
        return;
    }
    if is_key_down(KeyCode::W) {
        camera.position.y -= 10. / camera.scale;
    }
    if is_key_down(KeyCode::A) {
        camera.position.x -= 10. / camera.scale;
    }
    if is_key_down(KeyCode::S) {
        camera.position.y += 10. / camera.scale;
    }
    if is_key_down(KeyCode::D) {
        camera.position.x += 10. / camera.scale;
    }
}

fn check_reset(world: &LockedWorld) {
    let world = world.lock_upgradable();
    let parameters = world.resource::<ParticleParameters>().unwrap();
    if parameters.reset {
        drop(parameters);

        let mut world = world.upgrade();
        reset_particles(&mut world);
    }
}

fn draw(world: &LockedWorld) {
    let world = world.lock_shared();
    let camera = world.resource::<Camera>().unwrap();
    let entities = world.components::<Particle>().unwrap();
    let query = entities.query();
    for id in query {
        let particle = entities.get(&id).unwrap();
        draw_circle(
            (particle.position.x - camera.position.x) * camera.scale,
            (particle.position.y - camera.position.y) * camera.scale,
            5.0 * camera.scale,
            WHITE,
        );
    }
}

#[macroquad::main("")]
async fn main() {
    let world = LockedWorld::new();

    let start_work = ThreadedWork::new()
        .add_system(start_particles)
        .add_system(start_camera);
    let update_work = ThreadedWork::new()
        .add_system(update_particles)
        .add_system(check_reset);
    let draw_work = Work::new()
        .add_system(draw)
        .add_system(update_ui)
        .add_system(update_camera);

    start_work.run(&world);
    loop {
        update_work.run(&world);
        draw_work.run(&world);
        next_frame().await;
    }
}