mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-08 08:07:54 -06:00
refactor: platform trait, platform-specific code handling into platform module
This commit is contained in:
19
src/app.rs
19
src/app.rs
@@ -10,19 +10,7 @@ use tracing::{error, event};
|
|||||||
|
|
||||||
use crate::constants::{CANVAS_SIZE, LOOP_TIME, SCALE};
|
use crate::constants::{CANVAS_SIZE, LOOP_TIME, SCALE};
|
||||||
use crate::game::Game;
|
use crate::game::Game;
|
||||||
|
use crate::platform::get_platform;
|
||||||
#[cfg(target_os = "emscripten")]
|
|
||||||
use crate::emscripten;
|
|
||||||
|
|
||||||
#[cfg(not(target_os = "emscripten"))]
|
|
||||||
fn sleep(value: Duration) {
|
|
||||||
spin_sleep::sleep(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "emscripten")]
|
|
||||||
fn sleep(value: Duration) {
|
|
||||||
emscripten::emscripten::sleep(value.as_millis() as u32);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct App<'a> {
|
pub struct App<'a> {
|
||||||
game: Game,
|
game: Game,
|
||||||
@@ -35,6 +23,9 @@ pub struct App<'a> {
|
|||||||
|
|
||||||
impl App<'_> {
|
impl App<'_> {
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new() -> Result<Self> {
|
||||||
|
// Initialize platform-specific console
|
||||||
|
get_platform().init_console().map_err(|e| anyhow!(e))?;
|
||||||
|
|
||||||
let sdl_context = sdl2::init().map_err(|e| anyhow!(e))?;
|
let sdl_context = sdl2::init().map_err(|e| anyhow!(e))?;
|
||||||
let video_subsystem = sdl_context.video().map_err(|e| anyhow!(e))?;
|
let video_subsystem = sdl_context.video().map_err(|e| anyhow!(e))?;
|
||||||
let audio_subsystem = sdl_context.audio().map_err(|e| anyhow!(e))?;
|
let audio_subsystem = sdl_context.audio().map_err(|e| anyhow!(e))?;
|
||||||
@@ -138,7 +129,7 @@ impl App<'_> {
|
|||||||
if start.elapsed() < LOOP_TIME {
|
if start.elapsed() < LOOP_TIME {
|
||||||
let time = LOOP_TIME.saturating_sub(start.elapsed());
|
let time = LOOP_TIME.saturating_sub(start.elapsed());
|
||||||
if time != Duration::ZERO {
|
if time != Duration::ZERO {
|
||||||
sleep(time);
|
get_platform().sleep(time);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
event!(
|
event!(
|
||||||
|
|||||||
32
src/asset.rs
32
src/asset.rs
@@ -42,40 +42,12 @@ impl Asset {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "emscripten"))]
|
|
||||||
mod imp {
|
mod imp {
|
||||||
use super::*;
|
use super::*;
|
||||||
macro_rules! asset_bytes_enum {
|
use crate::platform::get_platform;
|
||||||
( $asset:expr ) => {
|
|
||||||
match $asset {
|
|
||||||
Asset::Wav1 => Cow::Borrowed(include_bytes!("../assets/game/sound/waka/1.ogg")),
|
|
||||||
Asset::Wav2 => Cow::Borrowed(include_bytes!("../assets/game/sound/waka/2.ogg")),
|
|
||||||
Asset::Wav3 => Cow::Borrowed(include_bytes!("../assets/game/sound/waka/3.ogg")),
|
|
||||||
Asset::Wav4 => Cow::Borrowed(include_bytes!("../assets/game/sound/waka/4.ogg")),
|
|
||||||
Asset::Atlas => Cow::Borrowed(include_bytes!("../assets/game/atlas.png")),
|
|
||||||
Asset::AtlasJson => Cow::Borrowed(include_bytes!("../assets/game/atlas.json")),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub fn get_asset_bytes(asset: Asset) -> Result<Cow<'static, [u8]>, AssetError> {
|
|
||||||
Ok(asset_bytes_enum!(asset))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "emscripten")]
|
|
||||||
mod imp {
|
|
||||||
use super::*;
|
|
||||||
use sdl2::rwops::RWops;
|
|
||||||
use std::io::Read;
|
|
||||||
pub fn get_asset_bytes(asset: Asset) -> Result<Cow<'static, [u8]>, AssetError> {
|
pub fn get_asset_bytes(asset: Asset) -> Result<Cow<'static, [u8]>, AssetError> {
|
||||||
let path = format!("assets/game/{}", asset.path());
|
get_platform().get_asset_bytes(asset)
|
||||||
let mut rwops = RWops::from_file(&path, "rb").map_err(|_| AssetError::NotFound(asset.path().to_string()))?;
|
|
||||||
let len = rwops.len().ok_or_else(|| AssetError::NotFound(asset.path().to_string()))?;
|
|
||||||
let mut buf = vec![0u8; len];
|
|
||||||
rwops
|
|
||||||
.read_exact(&mut buf)
|
|
||||||
.map_err(|e| AssetError::Io(std::io::Error::new(std::io::ErrorKind::Other, e)))?;
|
|
||||||
Ok(Cow::Owned(buf))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
#[allow(dead_code)]
|
|
||||||
#[cfg(target_os = "emscripten")]
|
|
||||||
pub mod emscripten {
|
|
||||||
use std::os::raw::c_uint;
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
pub fn emscripten_get_now() -> f64;
|
|
||||||
pub fn emscripten_sleep(ms: c_uint);
|
|
||||||
pub fn emscripten_get_element_css_size(target: *const u8, width: *mut f64, height: *mut f64) -> i32;
|
|
||||||
}
|
|
||||||
|
|
||||||
// milliseconds since start of program
|
|
||||||
pub fn now() -> f64 {
|
|
||||||
unsafe { emscripten_get_now() }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sleep(ms: u32) {
|
|
||||||
unsafe {
|
|
||||||
emscripten_sleep(ms);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_canvas_size() -> (u32, u32) {
|
|
||||||
let mut width = 0.0;
|
|
||||||
let mut height = 0.0;
|
|
||||||
unsafe {
|
|
||||||
emscripten_get_element_css_size("canvas\0".as_ptr(), &mut width, &mut height);
|
|
||||||
}
|
|
||||||
(width as u32, height as u32)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,9 +4,9 @@ pub mod app;
|
|||||||
pub mod asset;
|
pub mod asset;
|
||||||
pub mod audio;
|
pub mod audio;
|
||||||
pub mod constants;
|
pub mod constants;
|
||||||
pub mod emscripten;
|
|
||||||
pub mod entity;
|
pub mod entity;
|
||||||
pub mod game;
|
pub mod game;
|
||||||
pub mod helpers;
|
pub mod helpers;
|
||||||
pub mod map;
|
pub mod map;
|
||||||
|
pub mod platform;
|
||||||
pub mod texture;
|
pub mod texture;
|
||||||
|
|||||||
53
src/main.rs
53
src/main.rs
@@ -5,59 +5,16 @@ use tracing::info;
|
|||||||
use tracing_error::ErrorLayer;
|
use tracing_error::ErrorLayer;
|
||||||
use tracing_subscriber::layer::SubscriberExt;
|
use tracing_subscriber::layer::SubscriberExt;
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
use winapi::{
|
|
||||||
shared::ntdef::NULL,
|
|
||||||
um::{
|
|
||||||
fileapi::{CreateFileA, OPEN_EXISTING},
|
|
||||||
handleapi::INVALID_HANDLE_VALUE,
|
|
||||||
processenv::SetStdHandle,
|
|
||||||
winbase::{STD_ERROR_HANDLE, STD_OUTPUT_HANDLE},
|
|
||||||
wincon::{AttachConsole, GetConsoleWindow},
|
|
||||||
winnt::{FILE_SHARE_READ, FILE_SHARE_WRITE, GENERIC_READ, GENERIC_WRITE},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Attaches the process to the parent console on Windows.
|
|
||||||
///
|
|
||||||
/// This allows the application to print to the console when run from a terminal,
|
|
||||||
/// which is useful for debugging purposes. If the application is not run from a
|
|
||||||
/// terminal, this function does nothing.
|
|
||||||
#[cfg(windows)]
|
|
||||||
unsafe fn attach_console() {
|
|
||||||
if !std::ptr::eq(GetConsoleWindow(), std::ptr::null_mut()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if AttachConsole(winapi::um::wincon::ATTACH_PARENT_PROCESS) != 0 {
|
|
||||||
let handle = CreateFileA(
|
|
||||||
c"CONOUT$".as_ptr(),
|
|
||||||
GENERIC_READ | GENERIC_WRITE,
|
|
||||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
||||||
std::ptr::null_mut(),
|
|
||||||
OPEN_EXISTING,
|
|
||||||
0,
|
|
||||||
NULL,
|
|
||||||
);
|
|
||||||
|
|
||||||
if handle != INVALID_HANDLE_VALUE {
|
|
||||||
SetStdHandle(STD_OUTPUT_HANDLE, handle);
|
|
||||||
SetStdHandle(STD_ERROR_HANDLE, handle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Do NOT call AllocConsole here - we don't want a console when launched from Explorer
|
|
||||||
}
|
|
||||||
|
|
||||||
mod app;
|
mod app;
|
||||||
mod asset;
|
mod asset;
|
||||||
mod audio;
|
mod audio;
|
||||||
mod constants;
|
mod constants;
|
||||||
#[cfg(target_os = "emscripten")]
|
|
||||||
mod emscripten;
|
|
||||||
mod entity;
|
mod entity;
|
||||||
mod game;
|
mod game;
|
||||||
mod helpers;
|
mod helpers;
|
||||||
mod map;
|
mod map;
|
||||||
|
mod platform;
|
||||||
mod texture;
|
mod texture;
|
||||||
|
|
||||||
/// The main entry point of the application.
|
/// The main entry point of the application.
|
||||||
@@ -65,12 +22,6 @@ mod texture;
|
|||||||
/// This function initializes SDL, the window, the game state, and then enters
|
/// This function initializes SDL, the window, the game state, and then enters
|
||||||
/// the main game loop.
|
/// the main game loop.
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
// Attaches the console on Windows for debugging purposes.
|
|
||||||
#[cfg(windows)]
|
|
||||||
unsafe {
|
|
||||||
attach_console();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup tracing
|
// Setup tracing
|
||||||
let subscriber = tracing_subscriber::fmt()
|
let subscriber = tracing_subscriber::fmt()
|
||||||
.with_ansi(cfg!(not(target_os = "emscripten")))
|
.with_ansi(cfg!(not(target_os = "emscripten")))
|
||||||
|
|||||||
77
src/platform/desktop.rs
Normal file
77
src/platform/desktop.rs
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
//! Desktop platform implementation.
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use crate::asset::{Asset, AssetError};
|
||||||
|
use crate::platform::{Platform, PlatformError};
|
||||||
|
|
||||||
|
/// Desktop platform implementation.
|
||||||
|
pub struct DesktopPlatform;
|
||||||
|
|
||||||
|
impl Platform for DesktopPlatform {
|
||||||
|
fn sleep(&self, duration: Duration) {
|
||||||
|
spin_sleep::sleep(duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_time(&self) -> f64 {
|
||||||
|
std::time::Instant::now().elapsed().as_secs_f64()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_console(&self) -> Result<(), PlatformError> {
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
unsafe {
|
||||||
|
use winapi::{
|
||||||
|
shared::ntdef::NULL,
|
||||||
|
um::{
|
||||||
|
fileapi::{CreateFileA, OPEN_EXISTING},
|
||||||
|
handleapi::INVALID_HANDLE_VALUE,
|
||||||
|
processenv::SetStdHandle,
|
||||||
|
winbase::{STD_ERROR_HANDLE, STD_OUTPUT_HANDLE},
|
||||||
|
wincon::{AttachConsole, GetConsoleWindow},
|
||||||
|
winnt::{FILE_SHARE_READ, FILE_SHARE_WRITE, GENERIC_READ, GENERIC_WRITE},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if !std::ptr::eq(GetConsoleWindow(), std::ptr::null_mut()) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if AttachConsole(winapi::um::wincon::ATTACH_PARENT_PROCESS) != 0 {
|
||||||
|
let handle = CreateFileA(
|
||||||
|
c"CONOUT$".as_ptr(),
|
||||||
|
GENERIC_READ | GENERIC_WRITE,
|
||||||
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
OPEN_EXISTING,
|
||||||
|
0,
|
||||||
|
NULL,
|
||||||
|
);
|
||||||
|
|
||||||
|
if handle != INVALID_HANDLE_VALUE {
|
||||||
|
SetStdHandle(STD_OUTPUT_HANDLE, handle);
|
||||||
|
SetStdHandle(STD_ERROR_HANDLE, handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_canvas_size(&self) -> Option<(u32, u32)> {
|
||||||
|
None // Desktop doesn't need this
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_asset_bytes(&self, asset: Asset) -> Result<Cow<'static, [u8]>, AssetError> {
|
||||||
|
match asset {
|
||||||
|
Asset::Wav1 => Ok(Cow::Borrowed(include_bytes!("../../assets/game/sound/waka/1.ogg"))),
|
||||||
|
Asset::Wav2 => Ok(Cow::Borrowed(include_bytes!("../../assets/game/sound/waka/2.ogg"))),
|
||||||
|
Asset::Wav3 => Ok(Cow::Borrowed(include_bytes!("../../assets/game/sound/waka/3.ogg"))),
|
||||||
|
Asset::Wav4 => Ok(Cow::Borrowed(include_bytes!("../../assets/game/sound/waka/4.ogg"))),
|
||||||
|
Asset::Atlas => Ok(Cow::Borrowed(include_bytes!("../../assets/game/atlas.png"))),
|
||||||
|
Asset::AtlasJson => Ok(Cow::Borrowed(include_bytes!("../../assets/game/atlas.json"))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
61
src/platform/emscripten.rs
Normal file
61
src/platform/emscripten.rs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
//! Emscripten platform implementation.
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use crate::asset::{Asset, AssetError};
|
||||||
|
use crate::platform::{Platform, PlatformError};
|
||||||
|
|
||||||
|
/// Emscripten platform implementation.
|
||||||
|
pub struct EmscriptenPlatform;
|
||||||
|
|
||||||
|
impl Platform for EmscriptenPlatform {
|
||||||
|
fn sleep(&self, duration: Duration) {
|
||||||
|
unsafe {
|
||||||
|
emscripten_sleep(duration.as_millis() as u32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_time(&self) -> f64 {
|
||||||
|
unsafe { emscripten_get_now() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_console(&self) -> Result<(), PlatformError> {
|
||||||
|
Ok(()) // No-op for Emscripten
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_canvas_size(&self) -> Option<(u32, u32)> {
|
||||||
|
Some(unsafe { get_canvas_size() })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_asset_bytes(&self, asset: Asset) -> Result<Cow<'static, [u8]>, AssetError> {
|
||||||
|
use sdl2::rwops::RWops;
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
|
let path = format!("assets/game/{}", asset.path());
|
||||||
|
let mut rwops = RWops::from_file(&path, "rb").map_err(|_| AssetError::NotFound(asset.path().to_string()))?;
|
||||||
|
|
||||||
|
let len = rwops.len().ok_or_else(|| AssetError::NotFound(asset.path().to_string()))?;
|
||||||
|
|
||||||
|
let mut buf = vec![0u8; len];
|
||||||
|
rwops
|
||||||
|
.read_exact(&mut buf)
|
||||||
|
.map_err(|e| AssetError::Io(std::io::Error::new(std::io::ErrorKind::Other, e)))?;
|
||||||
|
|
||||||
|
Ok(Cow::Owned(buf))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emscripten FFI functions
|
||||||
|
extern "C" {
|
||||||
|
fn emscripten_get_now() -> f64;
|
||||||
|
fn emscripten_sleep(ms: u32);
|
||||||
|
fn emscripten_get_element_css_size(target: *const u8, width: *mut f64, height: *mut f64) -> i32;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn get_canvas_size() -> (u32, u32) {
|
||||||
|
let mut width = 0.0;
|
||||||
|
let mut height = 0.0;
|
||||||
|
emscripten_get_element_css_size("canvas\0".as_ptr(), &mut width, &mut height);
|
||||||
|
(width as u32, height as u32)
|
||||||
|
}
|
||||||
58
src/platform/mod.rs
Normal file
58
src/platform/mod.rs
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
//! Platform abstraction layer for cross-platform functionality.
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use crate::asset::{Asset, AssetError};
|
||||||
|
|
||||||
|
pub mod desktop;
|
||||||
|
pub mod emscripten;
|
||||||
|
|
||||||
|
/// Platform abstraction trait that defines cross-platform functionality.
|
||||||
|
pub trait Platform {
|
||||||
|
/// Sleep for the specified duration using platform-appropriate method.
|
||||||
|
fn sleep(&self, duration: Duration);
|
||||||
|
|
||||||
|
/// Get the current time in seconds since some reference point.
|
||||||
|
/// This is available for future use in timing and performance monitoring.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn get_time(&self) -> f64;
|
||||||
|
|
||||||
|
/// Initialize platform-specific console functionality.
|
||||||
|
fn init_console(&self) -> Result<(), PlatformError>;
|
||||||
|
|
||||||
|
/// Get canvas size for platforms that need it (e.g., Emscripten).
|
||||||
|
/// This is available for future use in responsive design.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn get_canvas_size(&self) -> Option<(u32, u32)>;
|
||||||
|
|
||||||
|
/// Load asset bytes using platform-appropriate method.
|
||||||
|
fn get_asset_bytes(&self, asset: Asset) -> Result<Cow<'static, [u8]>, AssetError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Platform-specific errors.
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub enum PlatformError {
|
||||||
|
#[error("Console initialization failed: {0}")]
|
||||||
|
ConsoleInit(String),
|
||||||
|
#[error("Platform-specific error: {0}")]
|
||||||
|
Other(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the current platform implementation.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn get_platform() -> &'static dyn Platform {
|
||||||
|
static DESKTOP: desktop::DesktopPlatform = desktop::DesktopPlatform;
|
||||||
|
static EMSCRIPTEN: emscripten::EmscriptenPlatform = emscripten::EmscriptenPlatform;
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "emscripten"))]
|
||||||
|
{
|
||||||
|
&DESKTOP
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "emscripten")]
|
||||||
|
{
|
||||||
|
&EMSCRIPTEN
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user