use std::time::Duration; use sdl2::{ event::Event, image::{self, LoadTexture, Sdl2ImageContext}, keyboard::Keycode, 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); pub struct Entity(u64); pub trait Component {} pub struct Sprite<'a> { texture: Texture<'a>, } pub struct Context<'a> { id_counter: &'a mut u64, canvas: &'a mut Canvas, texture_creator: &'a TextureCreator, entities: &'a mut Vec, components: &'a mut Vec<(u64, Box)>, systems: &'a mut Vec>, } impl<'a> Context<'a> { pub fn load_sprite<'b, P>(&self, path: P) -> Result, Error> where 'a: 'b, P: AsRef, { let texture = self.texture_creator.load_texture(path)?; Ok(Sprite { texture }) } pub fn draw_sprite<'b>(&mut self, sprite: &Sprite<'b>, x: i32, y: i32) -> Result<(), Error> { self.canvas.copy( &sprite.texture, None, Rect::new( x * 4, y * 4, sprite.texture.query().width * 4, sprite.texture.query().height * 4, ), )?; Ok(()) } pub fn spawn(&mut self, components: Vec>) { let id = *self.id_counter; *self.id_counter += 1; self.entities.push(Entity(id)); self.components .extend(components.into_iter().map(|component| (id, component))); } pub fn add_system(&mut self, system: Box) { system.on_add(self); self.systems.push(system) } } pub trait System { fn on_add(&self, _ctx: &mut Context) {} fn on_update(&self, _ctx: &mut Context) {} } pub struct Game { 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>, } impl 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::RGB(60, 180, 180)); 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![], }) } pub fn run ()>(mut self, f: F) { 'running: loop { for event in self.event_pump.poll_iter() { match event { Event::Quit { .. } | Event::KeyDown { keycode: Some(Keycode::Escape), .. } => break 'running, _ => {} } } self.canvas.set_draw_color(Color::RGB(60, 180, 180)); self.canvas.clear(); f(&mut self.context()); self.canvas.present(); } self.canvas.present(); std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 60)) } pub fn context<'a>(&'a mut self) -> Context<'a> { Context { id_counter: &mut self.id_counter, canvas: &mut self.canvas, texture_creator: &self.texture_creator, entities: &mut self.entities, components: &mut self.components, systems: &mut self.systems, } } }