refactor: platform trait, platform-specific code handling into platform module

This commit is contained in:
2025-08-11 10:49:58 -05:00
parent 14cebe4462
commit f6e7228f75
8 changed files with 206 additions and 127 deletions

77
src/platform/desktop.rs Normal file
View 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"))),
}
}
}

View 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
View 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
}
}