//! //! Example Program completing the same ecs-toy example I did earlier on my GitHub, but now with my engine //! use std::{io::{stdout, Result, Stdout}, time::Duration}; use clap::Parser; use nate_engine::{Engine, Renderer, system, world}; use rand::random; use crossterm::{ event::{self, KeyCode, KeyEventKind}, terminal::{ disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen, }, ExecutableCommand, }; use ratatui::{ prelude::*, widgets::{canvas::Canvas, Block, Borders}, }; const WIDTH: isize = 212; const MIN_X: isize = -106; const MAX_X: isize = 105; const HEIGHT: isize = 50; const MIN_Y: isize = -25; const MAX_Y: isize = 24; const MAX_HEALTH: usize = 10; #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum Status { Dead, Low, Medium, High, } #[world(singular=[living_entities, canvas])] pub struct ToyWorld { position: (isize, isize), velocity: (isize, isize), acceleration: (isize, isize), health: usize, health_changes: isize, living_entities: usize, canvas: [[Status; WIDTH as usize]; HEIGHT as usize], } #[system(world=ToyWorld, write=[position, velocity, acceleration])] fn position_update_system() { let left_right = match random::() % 4 { 0 => 1, 1 => -1, _ => 0, }; let up_down = match random::() % 4 { 0 => 1, 1 => -1, _ => 0, }; *acceleration = (left_right, up_down); *velocity = (velocity.0 + acceleration.0, velocity.1 + acceleration.1); *position = (position.0 + velocity.0, position.1 + velocity.1); } #[system(world=ToyWorld, read=[position, health], _write=[canvas=[[Status::Dead; WIDTH as usize]; HEIGHT as usize]])] fn update_canvas_system() { let x = (position.0.clamp(MIN_X, MAX_X) + WIDTH / 2) as usize; let y = (position.1.clamp(MIN_Y, MAX_Y) + HEIGHT / 2) as usize; match health { 7.. => canvas[y][x] = Status::High, 4..=6 => canvas[y][x] = Status::Medium, 1..=3 => canvas[y][x] = Status::Low, _ => (), } } #[system(world=ToyWorld, read=[health_changes], write=[health])] fn health_update_system() { *health = health.saturating_add_signed(*health_changes).clamp(0, MAX_HEALTH); } #[system(world=ToyWorld, write=[health_changes])] fn health_changes_system() { if random() { *health_changes = random::() % 2; } else { *health_changes = -random::() % 2; } } #[system(world=ToyWorld, _write=[living_entities])] fn alive_entities_display_system() { *living_entities = world.health.read().unwrap().iter().map(|v| v.is_some() && v.unwrap() > 0).count(); } pub struct ToyTerminalRenderer { terminal: Terminal>, } impl ToyTerminalRenderer { pub fn new(terminal: Terminal>) -> Self { Self { terminal, } } } impl Renderer for ToyTerminalRenderer { type Error = String; fn render(&mut self, world: std::sync::Arc>) -> std::prelude::v1::Result<(), Self::Error> { let world = world.read().unwrap(); let _err = self.terminal.draw(|frame| { let area = frame.size(); frame.render_widget( Canvas::default() .block( Block::default() .borders(Borders::ALL) .title( format!( "Living Entities: {}", (*world.living_entities.read().unwrap()).unwrap(), ) ) ) .background_color(Color::Black) .x_bounds([0.0, WIDTH as f64]) .y_bounds([0.0, HEIGHT as f64]) .paint(|ctx| { let canvas = (*world.canvas.read().unwrap()).unwrap(); for (y, row) in canvas.iter().enumerate() { for (x, item) in row.iter().enumerate() { match *item { Status::Dead => ctx.print(x as f64, y as f64, "_".gray()), Status::Low => ctx.print(x as f64, y as f64, "X".red()), Status::Medium => ctx.print(x as f64, y as f64, "X".yellow()), Status::High => ctx.print(x as f64, y as f64, "X".green()), } } } }), area ) }); if event::poll(Duration::from_millis(5)).unwrap() { if let event::Event::Key(key) = event::read().unwrap() { if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') { return Err("Interrupt Pressed".into()); } } } Ok(()) } } #[derive(Parser, Debug)] #[command(version, about, long_about = None)] struct Args { // Number of Entities to Spawn #[arg(short, long, default_value_t = 100_000)] entities: usize, // Workers in the threadpool #[arg(short, long, default_value_t = 3)] workers: usize, } fn main() -> Result<()> { let args = Args::parse(); let world = ToyWorld::new(); { let mut world = world.write().unwrap(); let entity_ids = world.add_entities(args.entities); let positions = entity_ids.iter().map(|_v| (random::().clamp(MIN_X, MAX_X), random::().clamp(MIN_Y, MAX_Y))).collect(); world.set_positions(&entity_ids, positions); let velocities = entity_ids.iter().map(|_v| (0, 0)).collect(); world.set_velocitys(&entity_ids, velocities); let accelerations = entity_ids.iter().map(|_v| (0, 0)).collect(); world.set_accelerations(&entity_ids, accelerations); let healths = entity_ids.iter().map(|_v| random::() % 100).collect(); world.set_healths(&entity_ids, healths); let health_changes = entity_ids.iter().map(|_v| 0).collect(); world.set_health_changess(&entity_ids, health_changes); world.set_living_entities(args.entities); world.set_canvas([[Status::Dead; WIDTH as usize]; HEIGHT as usize]); } stdout().execute(EnterAlternateScreen)?; enable_raw_mode()?; let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?; terminal.clear()?; let mut engine = Engine::new( 30, args.workers, world, vec![ (position_update_system, 100_000), (update_canvas_system, 100_000), (health_update_system, 100_000), (alive_entities_display_system, 100_000), ], Box::new(ToyTerminalRenderer::new(terminal)) ); engine.run(); stdout().execute(LeaveAlternateScreen)?; disable_raw_mode()?; Ok(()) }