use std::any::{Any, TypeId}; use std::collections::HashSet; use std::rc::Rc; use std::time::{Duration, Instant}; pub use component_macro::Component; pub use sdl2::keyboard::Keycode; use sdl2::{ event::Event, image::{self, LoadTexture, Sdl2ImageContext}, pixels::Color, rect::Rect, render::{Canvas, Texture, TextureCreator}, video::{Window, WindowBuildError, WindowContext}, IntegerOrSdlError, Sdl, VideoSubsystem, }; #[derive(Debug, Clone)] pub struct Error(String); impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } } macro_rules! impl_from_T_for_Error { ($t:ty) => { impl From<$t> for Error { fn from(value: $t) -> Self { Self(value.to_string()) } } }; ($t:ty, $($ts:ty),+) => { impl From<$t> for Error { fn from(value: $t) -> Self { Self(value.to_string()) } } impl_from_T_for_Error!($($ts),+); }; } impl_from_T_for_Error!(String, WindowBuildError, IntegerOrSdlError, &str); pub type Id = u64; pub struct Entity(Id, Vec>); pub trait Component { fn inner_type_id(&self) -> TypeId; fn as_any(&mut self) -> &mut dyn Any; } #[derive(Clone, Copy)] pub struct Sprite(Id); pub struct Context<'context, 'game> where 'game: 'context, { id_counter: &'context mut Id, canvas: &'context mut Canvas, texture_creator: *const TextureCreator, entities: &'context mut Vec, systems: &'context mut Vec>, textures: &'context mut Vec<(Id, Texture<'game>)>, currently_pressed_keys: &'context HashSet, } pub struct ComponentQuery(std::marker::PhantomData); impl ComponentQuery { pub fn new() -> Self { Self(std::marker::PhantomData) } } pub trait QueryRunner { fn run(&self, context: &Context) -> Vec; } impl QueryRunner for ComponentQuery where T0: 'static + Component, { fn run(&self, context: &Context) -> Vec { context.entities_with_component::() } } impl QueryRunner for ComponentQuery<(T0, T1)> where T0: 'static + Component, T1: 'static + Component, { fn run(&self, context: &Context) -> Vec { let vs0 = context.entities_with_component::(); let vs1 = context.entities_with_component::(); vs0.into_iter() .filter(|v0| vs1.iter().find(|v1| *v0 == **v1).is_some()) .collect() } } impl QueryRunner for ComponentQuery<(T0, T1, T2)> where T0: 'static + Component, T1: 'static + Component, T2: 'static + Component, { fn run(&self, context: &Context) -> Vec { let vs0 = context.entities_with_component::(); let vs1 = context.entities_with_component::(); let vs2 = context.entities_with_component::(); vs0.into_iter() .filter(|v0| { vs1.iter().find(|v1| *v0 == **v1).is_some() && vs2.iter().find(|v2| *v0 == **v2).is_some() }) .collect() } } #[macro_export] macro_rules! query { ($ctx:expr, $t:ty) => { { use engine::QueryRunner; engine::ComponentQuery::<$t>::new().run($ctx) } }; ($ctx:expr, $($ts:ty),+) => { { #[allow(unused_imports)] use engine::QueryRunner; engine::ComponentQuery::<($($ts),+)>::new().run($ctx) } }; } #[macro_export] macro_rules! spawn { ($ctx:expr, [$($ts:expr),+ $(,)?]) => { engine::Context::spawn($ctx, vec![$(Box::new($ts)),+]) }; ($ctx:expr, $($ts:expr),+ $(,)?) => { engine::Context::spawn($ctx, vec![$(Box::new($ts)),+]) }; } impl<'context, 'game> Context<'context, 'game> { pub fn entities_with_component(&self) -> Vec { let entity_type_id = TypeId::of::(); self.entities .iter() .filter_map(|Entity(id, components)| { let contains_component = components .iter() .any(|entity| (*entity).inner_type_id() == entity_type_id); if contains_component { Some(*id) } else { None } }) .collect() } pub fn entity_component(&mut self, entity_id: u64) -> &mut T { let entity_type_id = TypeId::of::(); let Entity(_id, components) = self .entities .iter_mut() .find(|Entity(id, _)| *id == entity_id) .unwrap(); let component = components .iter_mut() .find_map(|entity| { let is_id = (*entity).inner_type_id() == entity_type_id; if is_id { Some(entity.as_any().downcast_mut::().unwrap()) } else { None } }) .unwrap(); component } pub fn load_sprite

(&mut self, path: P) -> Result where P: AsRef, { let path = path.as_ref().to_path_buf(); let texture: Texture<'game> = unsafe { (*self.texture_creator).load_texture(&path)? }; let id = *self.id_counter; *self.id_counter += 1; self.textures.push((id, texture)); Ok(Sprite(id)) } pub fn draw_sprite(&mut self, sprite: Sprite, x: i32, y: i32) -> Result<(), Error> { let texture = self .textures .iter() .find_map(|v| if v.0 == sprite.0 { Some(&v.1) } else { None }) .ok_or_else(|| "invalid sprite id")?; self.canvas.copy( texture, None, Rect::new(x, y, texture.query().width, texture.query().height), )?; Ok(()) } pub fn spawn(&mut self, components: Vec>) { let id = *self.id_counter; *self.id_counter += 1; self.entities.push(Entity(id, components)); } pub fn despawn(&mut self, entity_id: u64) { *self.entities = self .entities .drain(..) .filter(|v| v.0 != entity_id) .collect(); } pub fn add_system(&mut self, system: S) { system.on_add(self); self.systems.push(Rc::new(system)) } pub fn key_pressed(&self, keycode: Keycode) -> bool { self.currently_pressed_keys.contains(&keycode) } } pub trait System { fn on_add(&self, _ctx: &mut Context) {} fn on_update(&self, _ctx: &mut Context, _delta: f64) -> Result<(), Error> { Ok(()) } } pub struct Game<'a> { id_counter: u64, sdl_context: Sdl, video_subsystem: VideoSubsystem, image_context: Sdl2ImageContext, canvas: Canvas, texture_creator: TextureCreator, event_pump: sdl2::EventPump, entities: Vec, components: Vec<(u64, Box)>, systems: Vec>, textures: Vec<(Id, Texture<'a>)>, currently_pressed_keys: HashSet, } impl<'game> Game<'game> { pub fn new() -> Result { let sdl_context = sdl2::init()?; let video_subsystem = sdl_context.video()?; let image_context = image::init(image::InitFlag::PNG)?; let window = video_subsystem .window("pvp-game-dilapidation", 1280, 720) .position_centered() .build()?; let mut canvas = window.into_canvas().build()?; let texture_creator = canvas.texture_creator(); canvas.set_draw_color(Color::BLACK); canvas.clear(); canvas.present(); let event_pump = sdl_context.event_pump()?; Ok(Self { id_counter: 0, sdl_context, video_subsystem, image_context, canvas, texture_creator, event_pump, entities: vec![], components: vec![], systems: vec![], textures: vec![], currently_pressed_keys: HashSet::new(), }) } pub fn run(&mut self) { let mut time_before = Instant::now(); 'running: loop { for event in self.event_pump.poll_iter() { match event { Event::Quit { .. } | Event::KeyDown { keycode: Some(Keycode::Escape), .. } => break 'running, Event::KeyDown { keycode, .. } => { self.currently_pressed_keys.insert(keycode.unwrap()); } Event::KeyUp { keycode, .. } => { self.currently_pressed_keys.remove(&keycode.unwrap()); } _ => {} } } self.canvas.set_draw_color(Color::RGB(60, 180, 180)); self.canvas.clear(); let now = Instant::now(); let delta = (now - time_before).as_nanos() as f64 / 1_000_000_000.0; time_before = now; for system in self.systems.clone() { let Err(err) = system.on_update(&mut self.context(), delta) else { continue; }; println!("error occcurred updating system: {err}"); } self.canvas.present(); std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 60)) } } pub fn context<'context>(&'context mut self) -> Context<'context, 'game> where 'game: 'context, { Context { id_counter: &mut self.id_counter, canvas: &mut self.canvas, texture_creator: &self.texture_creator, entities: &mut self.entities, systems: &mut self.systems, textures: &mut self.textures, currently_pressed_keys: &mut self.currently_pressed_keys, } } pub fn add_system(&mut self, system: S) { system.on_add(&mut self.context()); self.systems.push(Rc::new(system)) } }