use std::{ io::{Cursor, ErrorKind, Read, Write}, mem::align_of, }; use byteorder::{ReadBytesExt, WriteBytesExt, LE}; use contiguous_mem::*; pub enum IndexOrPtr { Index(u32), Ptr(*const T), } impl IndexOrPtr { pub fn to_ref(&self, data: &[*const T]) -> IndexOrPtr { match self { IndexOrPtr::Index(index) => IndexOrPtr::Ptr(data[*index as usize]), IndexOrPtr::Ptr(ref ptr) => IndexOrPtr::Ptr(*ptr), } } pub fn unwrap_ptr(&self) -> *const T { match self { IndexOrPtr::Index(_) => panic!("not a pointer"), IndexOrPtr::Ptr(ptr) => *ptr, } } pub fn unwrap_ref(&self) -> &'static mut T { unsafe { &mut *(self.unwrap_ptr() as *mut T) } } } pub trait Load { unsafe fn load(data: R) -> Self; } pub trait Save { fn save(&self, data: &mut W) -> Result<(), std::io::Error>; } pub struct Enemy { pub max_health: u32, pub health: u32, pub speed: f32, pub age: f32, } impl Enemy { pub fn reset(&mut self) { self.health = self.max_health; self.age = 0.0; } } impl Load for Enemy { unsafe fn load(mut data: R) -> Enemy { Enemy { max_health: data.read_u32::().unwrap_unchecked(), health: data.read_u32::().unwrap_unchecked(), speed: data.read_f32::().unwrap_unchecked(), age: data.read_f32::().unwrap_unchecked(), } } } impl Save for Enemy { fn save(&self, data: &mut W) -> Result<(), std::io::Error> { data.write_u32::(self.max_health)?; data.write_u32::(self.health)?; data.write_f32::(self.speed)?; data.write_f32::(self.age)?; Ok(()) } } pub struct Level { pub enemies: Vec>, } impl Load for Level { unsafe fn load(mut data: R) -> Level { let enemies_count = data.read_u32::().unwrap(); let enemies = (0..enemies_count).map(|_| { let enemy_index = data.read_u32::().unwrap(); IndexOrPtr::Index(enemy_index) }); Level { enemies: enemies.collect(), } } } impl Save for Level { fn save(&self, data: &mut W) -> Result<(), std::io::Error> { data.write_u32::(self.enemies.len() as u32)?; for enemy in self.enemies.iter() { match enemy { IndexOrPtr::Index(index) => data.write_u32::(*index)?, IndexOrPtr::Ptr(_) => { return Err(std::io::Error::new( ErrorKind::InvalidData, "can't save level with references", )) } } } Ok(()) } } // this function emulates FS access for this example, ignore it fn load_game_file(file_name: &'static str) -> T { let mut data = Vec::with_capacity(24); let mut data_cursor = Cursor::new(&mut data); match file_name { "enemy1.dat" => Enemy { max_health: 200, health: 200, speed: 2.0, age: 0.0, } .save(&mut data_cursor) .unwrap(), "enemy2.dat" => Enemy { max_health: 200, health: 200, speed: 2.0, age: 0.0, } .save(&mut data_cursor) .unwrap(), "enemy3.dat" => Enemy { max_health: 200, health: 200, speed: 2.0, age: 0.0, } .save(&mut data_cursor) .unwrap(), "enemy4.dat" => Enemy { max_health: 200, health: 200, speed: 2.0, age: 0.0, } .save(&mut data_cursor) .unwrap(), "level1.dat" => Level { enemies: vec![ IndexOrPtr::Index(0), IndexOrPtr::Index(1), IndexOrPtr::Index(1), IndexOrPtr::Index(2), ], } .save(&mut data_cursor) .unwrap(), "level2.dat" => Level { enemies: vec![ IndexOrPtr::Index(1), IndexOrPtr::Index(1), IndexOrPtr::Index(1), IndexOrPtr::Index(2), IndexOrPtr::Index(2), IndexOrPtr::Index(3), IndexOrPtr::Index(3), ], } .save(&mut data_cursor) .unwrap(), _ => unreachable!(), }; data_cursor.set_position(0); unsafe { T::load(data_cursor) } } fn main() { let mut data = UnsafeContiguousMemory::new_aligned(112, align_of::()).unwrap(); // Create enemy lookup list. let enemies: &[*const Enemy] = unsafe { &[ data.push(load_game_file("enemy1.dat")).unwrap_unchecked(), data.push(load_game_file("enemy2.dat")).unwrap_unchecked(), data.push(load_game_file("enemy3.dat")).unwrap_unchecked(), data.push(load_game_file("enemy4.dat")).unwrap_unchecked(), ] }; // Create level lookup list. let levels: &[*mut Level] = unsafe { &[ data.push(load_game_file("level1.dat")).unwrap_unchecked(), data.push(load_game_file("level2.dat")).unwrap_unchecked(), ] }; // data won't go out of scope while we're using it in this example, but in // your use case it might. This is here for completeness. data.forget(); // now we can assume all created pointers are 'static // prepare levels for use levels.iter().for_each(|level| { let level = unsafe { &mut **level }; level.enemies = level .enemies .iter() .map(|enemy| enemy.to_ref(enemies)) .collect(); }); let mut time = 0.0; let mut current_level: usize = 0; // Main game loop while current_level < levels.len() { // Simulate the passage of time (you can replace this with your game logic) time += 1.0; let mut all_enemies_killed = true; let current_lvl = unsafe { &mut *levels[current_level] }; for enemy in current_lvl.enemies.iter_mut() { let enemy_ref = enemy.unwrap_ref(); let health_reduction = ((5.0 + time * 0.25) as u32).min(enemy_ref.health); enemy_ref.health -= health_reduction; enemy_ref.age += 1.0; // Check if the enemy is still alive if enemy_ref.health > 0 { all_enemies_killed = false; } } // If all enemies in the current level are killed, reset them and progress to the next level if all_enemies_killed { println!( "All enemies in level {} have been killed!", current_level + 1 ); current_level += 1; // Reset all enemies in the next level if current_level < levels.len() { let next_level = unsafe { &mut *levels[current_level] }; for enemy in next_level.enemies.iter_mut() { enemy.unwrap_ref().reset(); } } } } println!( "Congratulations! You've completed all levels in: {:.2}", time ); }