mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-07 07:15:45 -06:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
028ee28840 | ||
|
|
a489bff0d1 | ||
|
|
0907b5ebe7 | ||
|
|
4cc5816d1f | ||
|
|
208ad3e733 | ||
|
|
24e8b3e3bc | ||
|
|
da0f4d856a |
2
.github/workflows/build.yaml
vendored
2
.github/workflows/build.yaml
vendored
@@ -151,7 +151,7 @@ jobs:
|
|||||||
done
|
done
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
uses: actions/upload-pages-artifact@v3
|
uses: actions/upload-pages-artifact@v4
|
||||||
if: github.ref == 'refs/heads/master' && github.event_name == 'push'
|
if: github.ref == 'refs/heads/master' && github.event_name == 'push'
|
||||||
with:
|
with:
|
||||||
path: "./dist/"
|
path: "./dist/"
|
||||||
|
|||||||
40
Cargo.lock
generated
40
Cargo.lock
generated
@@ -722,6 +722,15 @@ dependencies = [
|
|||||||
"portable-atomic",
|
"portable-atomic",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pretty_assertions"
|
name = "pretty_assertions"
|
||||||
version = "1.4.1"
|
version = "1.4.1"
|
||||||
@@ -762,6 +771,17 @@ version = "0.9.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
"rand_core",
|
"rand_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1529,3 +1549,23 @@ name = "yansi"
|
|||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy"
|
||||||
|
version = "0.8.26"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy-derive"
|
||||||
|
version = "0.8.26"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|||||||
55
Cargo.toml
55
Cargo.toml
@@ -13,30 +13,51 @@ keywords = ["game", "pacman", "arcade", "sdl2"]
|
|||||||
categories = ["games", "emulators"]
|
categories = ["games", "emulators"]
|
||||||
publish = false
|
publish = false
|
||||||
exclude = ["/assets/unpacked/**", "/assets/site/**", "/bacon.toml", "/Justfile"]
|
exclude = ["/assets/unpacked/**", "/assets/site/**", "/bacon.toml", "/Justfile"]
|
||||||
|
default-run = "pacman"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
bevy_ecs = "0.16.1"
|
||||||
|
glam = "0.30.5"
|
||||||
|
pathfinding = "4.14"
|
||||||
tracing = { version = "0.1.41", features = ["max_level_debug", "release_max_level_debug"]}
|
tracing = { version = "0.1.41", features = ["max_level_debug", "release_max_level_debug"]}
|
||||||
tracing-error = "0.2.0"
|
tracing-error = "0.2.0"
|
||||||
tracing-subscriber = {version = "0.3.20", features = ["env-filter"]}
|
tracing-subscriber = {version = "0.3.20", features = ["env-filter"]}
|
||||||
thiserror = "2.0.16"
|
thiserror = "2.0.16"
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
bevy_ecs = "0.16.1"
|
|
||||||
glam = "0.30.5"
|
|
||||||
rand = { version = "0.9.2", default-features = false, features = ["small_rng", "os_rng"] }
|
|
||||||
pathfinding = "4.14"
|
|
||||||
smallvec = "1.15.1"
|
smallvec = "1.15.1"
|
||||||
bitflags = "2.9.4"
|
bitflags = "2.9.4"
|
||||||
micromap = "0.1.0"
|
micromap = "0.1.0"
|
||||||
circular-buffer = "1.1.0"
|
circular-buffer = "1.1.0"
|
||||||
parking_lot = "0.12.3"
|
parking_lot = "0.12.3"
|
||||||
spin_sleep = "1.3.2"
|
|
||||||
strum = "0.27.2"
|
strum = "0.27.2"
|
||||||
strum_macros = "0.27.2"
|
strum_macros = "0.27.2"
|
||||||
phf = { version = "0.13.1", features = ["macros"] }
|
|
||||||
thousands = "0.2.0"
|
thousands = "0.2.0"
|
||||||
num-width = "0.1.0"
|
num-width = "0.1.0"
|
||||||
|
# While not actively used in code, `build.rs` generates code that relies on this. Keep the versions synchronized.
|
||||||
|
phf = { version = "0.13.1", features = ["macros"] }
|
||||||
|
|
||||||
|
# Windows-specific dependencies
|
||||||
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
|
# Used for customizing console output on Windows; both are required due to the `windows` crate having poor Result handling with `GetStdHandle`.
|
||||||
|
windows = { version = "0.61.3", features = ["Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Console"] }
|
||||||
|
windows-sys = { version = "0.60.2", features = ["Win32_System_Console"] }
|
||||||
|
|
||||||
|
# Desktop-specific dependencies
|
||||||
|
[target.'cfg(not(target_os = "emscripten"))'.dependencies]
|
||||||
|
# On desktop platforms, build SDL2 with cargo-vcpkg
|
||||||
|
sdl2 = { version = "0.38", default-features = false, features = ["image", "ttf", "gfx", "mixer", "unsafe_textures", "static-link", "use-vcpkg"] }
|
||||||
|
rand = { version = "0.9.2", default-features = false, features = ["thread_rng"] }
|
||||||
|
spin_sleep = "1.3.2"
|
||||||
|
|
||||||
|
# Browser-specific dependencies
|
||||||
|
[target.'cfg(target_os = "emscripten")'.dependencies]
|
||||||
|
# On Emscripten, we don't use cargo-vcpkg
|
||||||
|
sdl2 = { version = "0.38", default-features = false, features = ["image", "ttf", "gfx", "mixer", "unsafe_textures"] }
|
||||||
|
# TODO: Document why Emscripten cannot use `os_rng`.
|
||||||
|
rand = { version = "0.9.2", default-features = false, features = ["small_rng", "os_rng"] }
|
||||||
|
libc = "0.2.175" # TODO: Describe why this is required.
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pretty_assertions = "1.4.1"
|
pretty_assertions = "1.4.1"
|
||||||
@@ -59,32 +80,16 @@ opt-level = 3
|
|||||||
lto = false
|
lto = false
|
||||||
panic = 'unwind'
|
panic = 'unwind'
|
||||||
|
|
||||||
# LTO optimizations, no unwinding on panic, optimize for size
|
# Optimized release profile for size
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
opt-level = "z"
|
||||||
lto = true
|
lto = true
|
||||||
panic = "abort"
|
panic = "abort"
|
||||||
opt-level = "z"
|
|
||||||
|
|
||||||
# Used for customizing console output on Windows; both are required due to the `windows` crate having poor Result handling with `GetStdHandle`.
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
|
||||||
windows = { version = "0.61.3", features = ["Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Console"] }
|
|
||||||
windows-sys = { version = "0.60.2", features = ["Win32_System_Console"] }
|
|
||||||
|
|
||||||
# On desktop platforms, build SDL2 with cargo-vcpkg
|
|
||||||
[target.'cfg(not(target_os = "emscripten"))'.dependencies.sdl2]
|
|
||||||
version = "0.38"
|
|
||||||
default-features = false
|
|
||||||
features = ["image", "ttf", "gfx", "mixer", "unsafe_textures", "static-link", "use-vcpkg"]
|
|
||||||
|
|
||||||
# On Emscripten, we don't use cargo-vcpkg
|
|
||||||
[target.'cfg(target_os = "emscripten")'.dependencies]
|
|
||||||
sdl2 = { version = "0.38", default-features = false, features = ["image", "ttf", "gfx", "mixer", "unsafe_textures"] }
|
|
||||||
libc = "0.2.175" # TODO: Describe why this is required.
|
|
||||||
|
|
||||||
[package.metadata.vcpkg]
|
[package.metadata.vcpkg]
|
||||||
dependencies = ["sdl2", "sdl2-image", "sdl2-ttf", "sdl2-gfx", "sdl2-mixer"]
|
dependencies = ["sdl2", "sdl2-image", "sdl2-ttf", "sdl2-gfx", "sdl2-mixer"]
|
||||||
git = "https://github.com/microsoft/vcpkg"
|
git = "https://github.com/microsoft/vcpkg"
|
||||||
rev = "2024.05.24" # release 2024.05.24 # to check for a new one, check https://github.com/microsoft/vcpkg/releases
|
rev = "2024.05.24" # to check for a new one, check https://github.com/microsoft/vcpkg/releases
|
||||||
|
|
||||||
[package.metadata.vcpkg.target]
|
[package.metadata.vcpkg.target]
|
||||||
x86_64-pc-windows-msvc = { triplet = "x64-windows-static-md" }
|
x86_64-pc-windows-msvc = { triplet = "x64-windows-static-md" }
|
||||||
|
|||||||
4
Justfile
4
Justfile
@@ -38,3 +38,7 @@ coverage:
|
|||||||
samply:
|
samply:
|
||||||
cargo build --profile profile
|
cargo build --profile profile
|
||||||
samply record ./target/profile/pacman{{ binary_extension }}
|
samply record ./target/profile/pacman{{ binary_extension }}
|
||||||
|
|
||||||
|
# Build the project for Emscripten
|
||||||
|
web:
|
||||||
|
bun run web.build.ts
|
||||||
|
|||||||
14
src/app.rs
14
src/app.rs
@@ -4,7 +4,8 @@ use crate::error::{GameError, GameResult};
|
|||||||
|
|
||||||
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;
|
use crate::platform;
|
||||||
|
use sdl2::{AudioSubsystem, Sdl};
|
||||||
|
|
||||||
/// Main application wrapper that manages SDL initialization, window lifecycle, and the game loop.
|
/// Main application wrapper that manages SDL initialization, window lifecycle, and the game loop.
|
||||||
///
|
///
|
||||||
@@ -15,6 +16,9 @@ pub struct App {
|
|||||||
pub game: Game,
|
pub game: Game,
|
||||||
last_tick: Instant,
|
last_tick: Instant,
|
||||||
focused: bool,
|
focused: bool,
|
||||||
|
// Keep SDL alive for the app lifetime so subsystems (audio) are not shut down
|
||||||
|
_sdl_context: Sdl,
|
||||||
|
_audio_subsystem: AudioSubsystem,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
@@ -31,8 +35,8 @@ impl App {
|
|||||||
pub fn new() -> GameResult<Self> {
|
pub fn new() -> GameResult<Self> {
|
||||||
let sdl_context = sdl2::init().map_err(|e| GameError::Sdl(e.to_string()))?;
|
let sdl_context = sdl2::init().map_err(|e| GameError::Sdl(e.to_string()))?;
|
||||||
let video_subsystem = sdl_context.video().map_err(|e| GameError::Sdl(e.to_string()))?;
|
let video_subsystem = sdl_context.video().map_err(|e| GameError::Sdl(e.to_string()))?;
|
||||||
let _audio_subsystem = sdl_context.audio().map_err(|e| GameError::Sdl(e.to_string()))?;
|
let audio_subsystem = sdl_context.audio().map_err(|e| GameError::Sdl(e.to_string()))?;
|
||||||
let _ttf_context = sdl2::ttf::init().map_err(|e| GameError::Sdl(e.to_string()))?;
|
// TTF context is initialized within Game::new where it is leaked for font usage
|
||||||
let event_pump = sdl_context.event_pump().map_err(|e| GameError::Sdl(e.to_string()))?;
|
let event_pump = sdl_context.event_pump().map_err(|e| GameError::Sdl(e.to_string()))?;
|
||||||
|
|
||||||
let window = video_subsystem
|
let window = video_subsystem
|
||||||
@@ -65,6 +69,8 @@ impl App {
|
|||||||
game,
|
game,
|
||||||
focused: true,
|
focused: true,
|
||||||
last_tick: Instant::now(),
|
last_tick: Instant::now(),
|
||||||
|
_sdl_context: sdl_context,
|
||||||
|
_audio_subsystem: audio_subsystem,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +101,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 {
|
||||||
get_platform().sleep(time, self.focused);
|
platform::sleep(time, self.focused);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ impl Asset {
|
|||||||
mod imp {
|
mod imp {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::error::AssetError;
|
use crate::error::AssetError;
|
||||||
use crate::platform::get_platform;
|
use crate::platform;
|
||||||
|
|
||||||
/// Loads asset bytes using the appropriate platform-specific method.
|
/// Loads asset bytes using the appropriate platform-specific method.
|
||||||
///
|
///
|
||||||
@@ -58,7 +58,7 @@ mod imp {
|
|||||||
/// Returns `AssetError::NotFound` if the asset file cannot be located (Emscripten only),
|
/// Returns `AssetError::NotFound` if the asset file cannot be located (Emscripten only),
|
||||||
/// or `AssetError::Io` for filesystem I/O failures.
|
/// or `AssetError::Io` for filesystem I/O failures.
|
||||||
pub fn get_asset_bytes(asset: Asset) -> Result<Cow<'static, [u8]>, AssetError> {
|
pub fn get_asset_bytes(asset: Asset) -> Result<Cow<'static, [u8]>, AssetError> {
|
||||||
get_platform().get_asset_bytes(asset)
|
platform::get_asset_bytes(asset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
91
src/bin/timing_demo.rs
Normal file
91
src/bin/timing_demo.rs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
use circular_buffer::CircularBuffer;
|
||||||
|
use pacman::constants::CANVAS_SIZE;
|
||||||
|
use sdl2::event::Event;
|
||||||
|
use sdl2::keyboard::Keycode;
|
||||||
|
use sdl2::pixels::Color;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
fn main() -> Result<(), String> {
|
||||||
|
let sdl_context = sdl2::init()?;
|
||||||
|
let video_subsystem = sdl_context.video()?;
|
||||||
|
|
||||||
|
let window = video_subsystem
|
||||||
|
.window("SDL2 Timing Demo", CANVAS_SIZE.x, CANVAS_SIZE.y)
|
||||||
|
.opengl()
|
||||||
|
.position_centered()
|
||||||
|
.build()
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let mut canvas = window.into_canvas().accelerated().build().map_err(|e| e.to_string())?;
|
||||||
|
canvas
|
||||||
|
.set_logical_size(CANVAS_SIZE.x, CANVAS_SIZE.y)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let mut event_pump = sdl_context.event_pump()?;
|
||||||
|
|
||||||
|
// Store frame timings in milliseconds
|
||||||
|
let mut frame_timings = CircularBuffer::<20_000, f64>::new();
|
||||||
|
let mut last_report_time = Instant::now();
|
||||||
|
let report_interval = Duration::from_millis(500);
|
||||||
|
|
||||||
|
'running: loop {
|
||||||
|
let frame_start_time = Instant::now();
|
||||||
|
|
||||||
|
for event in event_pump.poll_iter() {
|
||||||
|
match event {
|
||||||
|
Event::Quit { .. }
|
||||||
|
| Event::KeyDown {
|
||||||
|
keycode: Some(Keycode::Escape),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
break 'running;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the screen
|
||||||
|
canvas.set_draw_color(Color::RGB(0, 0, 0));
|
||||||
|
canvas.clear();
|
||||||
|
canvas.present();
|
||||||
|
|
||||||
|
// Record timing
|
||||||
|
let frame_duration = frame_start_time.elapsed();
|
||||||
|
frame_timings.push_back(frame_duration.as_secs_f64());
|
||||||
|
|
||||||
|
// Report stats every `report_interval`
|
||||||
|
let elapsed = last_report_time.elapsed();
|
||||||
|
if elapsed >= report_interval {
|
||||||
|
if !frame_timings.is_empty() {
|
||||||
|
let count = frame_timings.len() as f64;
|
||||||
|
let sum: f64 = frame_timings.iter().sum();
|
||||||
|
let mean = sum / count;
|
||||||
|
|
||||||
|
let variance = frame_timings
|
||||||
|
.iter()
|
||||||
|
.map(|value| {
|
||||||
|
let diff = mean - value;
|
||||||
|
diff * diff
|
||||||
|
})
|
||||||
|
.sum::<f64>()
|
||||||
|
/ count;
|
||||||
|
let std_dev = variance.sqrt();
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"Rendered {count} frames at {fps:.1} fps (last {elapsed:.2?}): mean={mean:.3?}, std_dev={std_dev:.3?}",
|
||||||
|
count = frame_timings.len(),
|
||||||
|
fps = count / elapsed.as_secs_f64(),
|
||||||
|
elapsed = elapsed,
|
||||||
|
mean = Duration::from_secs_f64(mean),
|
||||||
|
std_dev = Duration::from_secs_f64(std_dev),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset for next interval
|
||||||
|
frame_timings.clear();
|
||||||
|
last_report_time = Instant::now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -22,13 +22,12 @@ 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() {
|
||||||
let platform = platform::get_platform();
|
if platform::requires_console() {
|
||||||
if platform.requires_console() {
|
|
||||||
// Setup buffered tracing subscriber that will buffer logs until console is ready
|
// Setup buffered tracing subscriber that will buffer logs until console is ready
|
||||||
let switchable_writer = platform::tracing_buffer::setup_switchable_subscriber();
|
let switchable_writer = platform::tracing_buffer::setup_switchable_subscriber();
|
||||||
|
|
||||||
// Initialize platform-specific console
|
// Initialize platform-specific console
|
||||||
platform.init_console().expect("Could not initialize console");
|
platform::init_console().expect("Could not initialize console");
|
||||||
|
|
||||||
// Now that console is initialized, flush buffered logs and switch to direct output
|
// Now that console is initialized, flush buffered logs and switch to direct output
|
||||||
debug!("Switching to direct logging mode and flushing buffer...");
|
debug!("Switching to direct logging mode and flushing buffer...");
|
||||||
|
|||||||
@@ -3,15 +3,13 @@
|
|||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use rand::rngs::ThreadRng;
|
||||||
|
|
||||||
use crate::asset::Asset;
|
use crate::asset::Asset;
|
||||||
use crate::error::{AssetError, PlatformError};
|
use crate::error::{AssetError, PlatformError};
|
||||||
use crate::platform::CommonPlatform;
|
|
||||||
|
|
||||||
/// Desktop platform implementation.
|
/// Desktop platform implementation.
|
||||||
pub struct Platform;
|
pub fn sleep(duration: Duration, focused: bool) {
|
||||||
|
|
||||||
impl CommonPlatform for Platform {
|
|
||||||
fn sleep(&self, duration: Duration, focused: bool) {
|
|
||||||
if focused {
|
if focused {
|
||||||
spin_sleep::sleep(duration);
|
spin_sleep::sleep(duration);
|
||||||
} else {
|
} else {
|
||||||
@@ -19,11 +17,7 @@ impl CommonPlatform for Platform {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_time(&self) -> f64 {
|
pub fn init_console() -> Result<(), PlatformError> {
|
||||||
std::time::Instant::now().elapsed().as_secs_f64()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init_console(&self) -> Result<(), PlatformError> {
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
use tracing::{debug, info};
|
use tracing::{debug, info};
|
||||||
@@ -37,13 +31,13 @@ impl CommonPlatform for Platform {
|
|||||||
debug!("No existing console window found");
|
debug!("No existing console window found");
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(file_type) = Self::is_output_setup()? {
|
if let Some(file_type) = is_output_setup()? {
|
||||||
debug!(r#type = file_type, "Existing output detected");
|
debug!(r#type = file_type, "Existing output detected");
|
||||||
} else {
|
} else {
|
||||||
debug!("No existing output detected");
|
debug!("No existing output detected");
|
||||||
|
|
||||||
// Try to attach to parent console for direct cargo run
|
// Try to attach to parent console for direct cargo run
|
||||||
Self::attach_to_parent_console()?;
|
attach_to_parent_console()?;
|
||||||
info!("Successfully attached to parent console");
|
info!("Successfully attached to parent console");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,15 +45,11 @@ impl CommonPlatform for Platform {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn requires_console(&self) -> bool {
|
pub fn requires_console() -> bool {
|
||||||
cfg!(windows)
|
cfg!(windows)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_canvas_size(&self) -> Option<(u32, u32)> {
|
pub fn get_asset_bytes(asset: Asset) -> Result<Cow<'static, [u8]>, AssetError> {
|
||||||
None // Desktop doesn't need this
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_asset_bytes(&self, asset: Asset) -> Result<Cow<'static, [u8]>, AssetError> {
|
|
||||||
match asset {
|
match asset {
|
||||||
Asset::Wav1 => Ok(Cow::Borrowed(include_bytes!("../../assets/game/sound/waka/1.ogg"))),
|
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::Wav2 => Ok(Cow::Borrowed(include_bytes!("../../assets/game/sound/waka/2.ogg"))),
|
||||||
@@ -69,11 +59,16 @@ impl CommonPlatform for Platform {
|
|||||||
Asset::Font => Ok(Cow::Borrowed(include_bytes!("../../assets/game/TerminalVector.ttf"))),
|
Asset::Font => Ok(Cow::Borrowed(include_bytes!("../../assets/game/TerminalVector.ttf"))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn rng() -> ThreadRng {
|
||||||
|
rand::rng()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
/* Internal functions */
|
||||||
impl Platform {
|
|
||||||
/// Check if the output stream has been setup by a parent process
|
/// Check if the output stream has been setup by a parent process
|
||||||
|
/// Windows-only
|
||||||
|
#[cfg(windows)]
|
||||||
fn is_output_setup() -> Result<Option<&'static str>, PlatformError> {
|
fn is_output_setup() -> Result<Option<&'static str>, PlatformError> {
|
||||||
use tracing::{debug, warn};
|
use tracing::{debug, warn};
|
||||||
|
|
||||||
@@ -117,6 +112,8 @@ impl Platform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Try to attach to parent console
|
/// Try to attach to parent console
|
||||||
|
/// Windows-only
|
||||||
|
#[cfg(windows)]
|
||||||
fn attach_to_parent_console() -> Result<(), PlatformError> {
|
fn attach_to_parent_console() -> Result<(), PlatformError> {
|
||||||
use windows::{
|
use windows::{
|
||||||
core::PCSTR,
|
core::PCSTR,
|
||||||
@@ -170,4 +167,3 @@ impl Platform {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,35 +5,44 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use crate::asset::Asset;
|
use crate::asset::Asset;
|
||||||
use crate::error::{AssetError, PlatformError};
|
use crate::error::{AssetError, PlatformError};
|
||||||
use crate::platform::CommonPlatform;
|
use rand::{rngs::SmallRng, SeedableRng};
|
||||||
|
|
||||||
/// Emscripten platform implementation.
|
// Emscripten FFI functions
|
||||||
pub struct Platform;
|
#[allow(dead_code)]
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
impl CommonPlatform for Platform {
|
pub fn sleep(duration: Duration, _focused: bool) {
|
||||||
fn sleep(&self, duration: Duration, _focused: bool) {
|
|
||||||
unsafe {
|
unsafe {
|
||||||
emscripten_sleep(duration.as_millis() as u32);
|
emscripten_sleep(duration.as_millis() as u32);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_time(&self) -> f64 {
|
pub fn init_console() -> Result<(), PlatformError> {
|
||||||
unsafe { emscripten_get_now() }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init_console(&self) -> Result<(), PlatformError> {
|
|
||||||
Ok(()) // No-op for Emscripten
|
Ok(()) // No-op for Emscripten
|
||||||
}
|
}
|
||||||
|
|
||||||
fn requires_console(&self) -> bool {
|
pub fn requires_console() -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_canvas_size(&self) -> Option<(u32, u32)> {
|
pub fn get_canvas_size() -> Option<(u32, u32)> {
|
||||||
Some(unsafe { get_canvas_size() })
|
let mut width = 0.0;
|
||||||
|
let mut height = 0.0;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
emscripten_get_element_css_size(c"canvas".as_ptr().cast(), &mut width, &mut height);
|
||||||
|
if width == 0.0 || height == 0.0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some((width as u32, height as u32))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_asset_bytes(&self, asset: Asset) -> Result<Cow<'static, [u8]>, AssetError> {
|
pub fn get_asset_bytes(asset: Asset) -> Result<Cow<'static, [u8]>, AssetError> {
|
||||||
use sdl2::rwops::RWops;
|
use sdl2::rwops::RWops;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
|
||||||
@@ -49,18 +58,7 @@ impl CommonPlatform for Platform {
|
|||||||
|
|
||||||
Ok(Cow::Owned(buf))
|
Ok(Cow::Owned(buf))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Emscripten FFI functions
|
pub fn rng() -> SmallRng {
|
||||||
extern "C" {
|
SmallRng::from_os_rng()
|
||||||
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(c"canvas".as_ptr().cast(), &mut width, &mut height);
|
|
||||||
(width as u32, height as u32)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,52 +1,13 @@
|
|||||||
//! Platform abstraction layer for cross-platform functionality.
|
//! Platform abstraction layer for cross-platform functionality.
|
||||||
|
|
||||||
use crate::asset::Asset;
|
|
||||||
use crate::error::{AssetError, PlatformError};
|
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
#[cfg(not(target_os = "emscripten"))]
|
|
||||||
mod desktop;
|
|
||||||
#[cfg(target_os = "emscripten")]
|
|
||||||
mod emscripten;
|
|
||||||
|
|
||||||
pub mod buffered_writer;
|
pub mod buffered_writer;
|
||||||
pub mod tracing_buffer;
|
pub mod tracing_buffer;
|
||||||
|
|
||||||
/// Cross-platform abstraction layer providing unified APIs for platform-specific operations.
|
|
||||||
pub trait CommonPlatform {
|
|
||||||
/// Platform-specific sleep function (required due to Emscripten's non-standard sleep requirements).
|
|
||||||
///
|
|
||||||
/// Provides access to current window focus state, useful for changing sleep algorithm conditionally.
|
|
||||||
fn sleep(&self, duration: Duration, focused: bool);
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn get_time(&self) -> f64;
|
|
||||||
|
|
||||||
/// Configures platform-specific console and debugging output capabilities.
|
|
||||||
fn init_console(&self) -> Result<(), PlatformError>;
|
|
||||||
|
|
||||||
/// Retrieves the actual display canvas dimensions.
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn get_canvas_size(&self) -> Option<(u32, u32)>;
|
|
||||||
|
|
||||||
/// Loads raw asset data using the appropriate platform-specific method.
|
|
||||||
fn get_asset_bytes(&self, asset: Asset) -> Result<Cow<'static, [u8]>, AssetError>;
|
|
||||||
|
|
||||||
/// Whether the platform requires a console to be initialized.
|
|
||||||
fn requires_console(&self) -> bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the appropriate platform implementation based on compile-time target.
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn get_platform() -> &'static dyn CommonPlatform {
|
|
||||||
#[cfg(not(target_os = "emscripten"))]
|
#[cfg(not(target_os = "emscripten"))]
|
||||||
{
|
mod desktop;
|
||||||
&desktop::Platform
|
#[cfg(not(target_os = "emscripten"))]
|
||||||
}
|
pub use desktop::*;
|
||||||
|
|
||||||
#[cfg(target_os = "emscripten")]
|
#[cfg(target_os = "emscripten")]
|
||||||
{
|
pub use emscripten::*;
|
||||||
&emscripten::Platform
|
#[cfg(target_os = "emscripten")]
|
||||||
}
|
mod emscripten;
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::platform;
|
||||||
use crate::systems::components::{DirectionalAnimation, Frozen, GhostAnimation, GhostState, LastAnimationState, LinearAnimation};
|
use crate::systems::components::{DirectionalAnimation, Frozen, GhostAnimation, GhostState, LastAnimationState, LinearAnimation};
|
||||||
use crate::{
|
use crate::{
|
||||||
map::{
|
map::{
|
||||||
@@ -14,9 +15,7 @@ use crate::{
|
|||||||
use crate::systems::GhostAnimations;
|
use crate::systems::GhostAnimations;
|
||||||
use bevy_ecs::query::Without;
|
use bevy_ecs::query::Without;
|
||||||
use bevy_ecs::system::{Commands, Query, Res};
|
use bevy_ecs::system::{Commands, Query, Res};
|
||||||
use rand::rngs::SmallRng;
|
|
||||||
use rand::seq::IndexedRandom;
|
use rand::seq::IndexedRandom;
|
||||||
use rand::SeedableRng;
|
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
/// Autonomous ghost AI system implementing randomized movement with backtracking avoidance.
|
/// Autonomous ghost AI system implementing randomized movement with backtracking avoidance.
|
||||||
@@ -49,7 +48,7 @@ pub fn ghost_movement_system(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
*non_opposite_options.choose(&mut SmallRng::from_os_rng()).unwrap()
|
*non_opposite_options.choose(&mut platform::rng()).unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
velocity.direction = new_edge.direction;
|
velocity.direction = new_edge.direction;
|
||||||
|
|||||||
@@ -225,8 +225,6 @@ pub fn render_system(
|
|||||||
})
|
})
|
||||||
.err()
|
.err()
|
||||||
.map(|e| errors.write(TextureError::RenderFailed(e.to_string()).into()));
|
.map(|e| errors.write(TextureError::RenderFailed(e.to_string()).into()));
|
||||||
|
|
||||||
canvas.copy(&backbuffer.0, None, None).unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn present_system(
|
pub fn present_system(
|
||||||
|
|||||||
Reference in New Issue
Block a user