From 4cc5816d1f7f2b10f837b4d159186a314aced4a8 Mon Sep 17 00:00:00 2001 From: Ryan Walters Date: Wed, 3 Sep 2025 11:11:04 -0500 Subject: [PATCH] refactor: use small_rng for Emscripten only, simplify platform to top-level functions only, no trait/struct --- Cargo.lock | 40 +++++ Cargo.toml | 3 +- src/app.rs | 4 +- src/asset.rs | 4 +- src/main.rs | 5 +- src/platform/desktop.rs | 294 +++++++++++++++++++------------------ src/platform/emscripten.rs | 97 ++++++------ src/platform/mod.rs | 55 +------ src/systems/ghost.rs | 5 +- 9 files changed, 256 insertions(+), 251 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 319192b..d7aaf89 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -722,6 +722,15 @@ dependencies = [ "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]] name = "pretty_assertions" version = "1.4.1" @@ -762,6 +771,17 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" 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", ] @@ -1529,3 +1549,23 @@ name = "yansi" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" 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", +] diff --git a/Cargo.toml b/Cargo.toml index 227e554..7614b0d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,6 @@ tracing-error = "0.2.0" tracing-subscriber = {version = "0.3.20", features = ["env-filter"]} thiserror = "2.0.16" anyhow = "1.0" -rand = { version = "0.9.2", default-features = false, features = ["small_rng", "os_rng"] } smallvec = "1.15.1" bitflags = "2.9.4" micromap = "0.1.0" @@ -48,6 +47,7 @@ windows-sys = { version = "0.60.2", features = ["Win32_System_Console"] } [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 @@ -55,6 +55,7 @@ spin_sleep = "1.3.2" # 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] diff --git a/src/app.rs b/src/app.rs index 3ff05a0..d576de5 100644 --- a/src/app.rs +++ b/src/app.rs @@ -4,7 +4,7 @@ use crate::error::{GameError, GameResult}; use crate::constants::{CANVAS_SIZE, LOOP_TIME, SCALE}; 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. @@ -101,7 +101,7 @@ impl App { if start.elapsed() < LOOP_TIME { let time = LOOP_TIME.saturating_sub(start.elapsed()); if time != Duration::ZERO { - get_platform().sleep(time, self.focused); + platform::sleep(time, self.focused); } } diff --git a/src/asset.rs b/src/asset.rs index a76c449..013a5a8 100644 --- a/src/asset.rs +++ b/src/asset.rs @@ -44,7 +44,7 @@ impl Asset { mod imp { use super::*; use crate::error::AssetError; - use crate::platform::get_platform; + use crate::platform; /// 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), /// or `AssetError::Io` for filesystem I/O failures. pub fn get_asset_bytes(asset: Asset) -> Result, AssetError> { - get_platform().get_asset_bytes(asset) + platform::get_asset_bytes(asset) } } diff --git a/src/main.rs b/src/main.rs index 889fc32..681a7cd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,13 +22,12 @@ mod texture; /// This function initializes SDL, the window, the game state, and then enters /// the main game loop. 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 let switchable_writer = platform::tracing_buffer::setup_switchable_subscriber(); // 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 debug!("Switching to direct logging mode and flushing buffer..."); diff --git a/src/platform/desktop.rs b/src/platform/desktop.rs index 411c634..90ba2cf 100644 --- a/src/platform/desktop.rs +++ b/src/platform/desktop.rs @@ -1,173 +1,177 @@ //! Desktop platform implementation. use std::borrow::Cow; -use std::time::Duration; +use std::time::{Duration, Instant}; + +use rand::rngs::ThreadRng; use crate::asset::Asset; use crate::error::{AssetError, PlatformError}; -use crate::platform::CommonPlatform; /// Desktop platform implementation. -pub struct Platform; +pub fn sleep(duration: Duration, focused: bool) { + if focused { + spin_sleep::sleep(duration); + } else { + std::thread::sleep(duration); + } +} -impl CommonPlatform for Platform { - fn sleep(&self, duration: Duration, focused: bool) { - if focused { - spin_sleep::sleep(duration); +pub fn get_time() -> f64 { + Instant::now().elapsed().as_secs_f64() +} + +pub fn init_console() -> Result<(), PlatformError> { + #[cfg(windows)] + { + use tracing::{debug, info}; + use windows::Win32::System::Console::GetConsoleWindow; + + // Check if we already have a console window + if unsafe { !GetConsoleWindow().0.is_null() } { + debug!("Already have a console window"); + return Ok(()); } else { - std::thread::sleep(duration); + debug!("No existing console window found"); + } + + if let Some(file_type) = is_output_setup()? { + debug!(r#type = file_type, "Existing output detected"); + } else { + debug!("No existing output detected"); + + // Try to attach to parent console for direct cargo run + attach_to_parent_console()?; + info!("Successfully attached to parent console"); } } - fn get_time(&self) -> f64 { - std::time::Instant::now().elapsed().as_secs_f64() - } + Ok(()) +} - fn init_console(&self) -> Result<(), PlatformError> { - #[cfg(windows)] - { - use tracing::{debug, info}; - use windows::Win32::System::Console::GetConsoleWindow; +pub fn requires_console() -> bool { + cfg!(windows) +} - // Check if we already have a console window - if unsafe { !GetConsoleWindow().0.is_null() } { - debug!("Already have a console window"); - return Ok(()); - } else { - debug!("No existing console window found"); - } +pub fn get_canvas_size() -> Option<(u32, u32)> { + None // Desktop doesn't need this +} - if let Some(file_type) = Self::is_output_setup()? { - debug!(r#type = file_type, "Existing output detected"); - } else { - debug!("No existing output detected"); - - // Try to attach to parent console for direct cargo run - Self::attach_to_parent_console()?; - info!("Successfully attached to parent console"); - } - } - - Ok(()) - } - - fn requires_console(&self) -> bool { - cfg!(windows) - } - - fn get_canvas_size(&self) -> Option<(u32, u32)> { - None // Desktop doesn't need this - } - - fn get_asset_bytes(&self, asset: Asset) -> Result, 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::AtlasImage => Ok(Cow::Borrowed(include_bytes!("../../assets/game/atlas.png"))), - Asset::Font => Ok(Cow::Borrowed(include_bytes!("../../assets/game/TerminalVector.ttf"))), - } +pub fn get_asset_bytes(asset: Asset) -> Result, 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::AtlasImage => Ok(Cow::Borrowed(include_bytes!("../../assets/game/atlas.png"))), + Asset::Font => Ok(Cow::Borrowed(include_bytes!("../../assets/game/TerminalVector.ttf"))), } } +pub fn rng() -> ThreadRng { + rand::rng() +} + +/* Internal functions */ + +/// Check if the output stream has been setup by a parent process +/// Windows-only #[cfg(windows)] -impl Platform { - /// Check if the output stream has been setup by a parent process - fn is_output_setup() -> Result, PlatformError> { - use tracing::{debug, warn}; +fn is_output_setup() -> Result, PlatformError> { + use tracing::{debug, warn}; - use windows::Win32::Storage::FileSystem::{ - GetFileType, FILE_TYPE_CHAR, FILE_TYPE_DISK, FILE_TYPE_PIPE, FILE_TYPE_REMOTE, FILE_TYPE_UNKNOWN, - }; + use windows::Win32::Storage::FileSystem::{ + GetFileType, FILE_TYPE_CHAR, FILE_TYPE_DISK, FILE_TYPE_PIPE, FILE_TYPE_REMOTE, FILE_TYPE_UNKNOWN, + }; - use windows_sys::Win32::{ - Foundation::INVALID_HANDLE_VALUE, - System::Console::{GetStdHandle, STD_OUTPUT_HANDLE}, - }; + use windows_sys::Win32::{ + Foundation::INVALID_HANDLE_VALUE, + System::Console::{GetStdHandle, STD_OUTPUT_HANDLE}, + }; - // Get the process's standard output handle, check if it's invalid - let handle = match unsafe { GetStdHandle(STD_OUTPUT_HANDLE) } { - INVALID_HANDLE_VALUE => { - return Err(PlatformError::ConsoleInit("Invalid handle".to_string())); - } - handle => handle, - }; - - // Identify the file type of the handle and whether it's 'well known' (i.e. we trust it to be a reasonable output destination) - let (well_known, file_type) = match unsafe { - use windows::Win32::Foundation::HANDLE; - GetFileType(HANDLE(handle)) - } { - FILE_TYPE_PIPE => (true, "pipe"), - FILE_TYPE_CHAR => (true, "char"), - FILE_TYPE_DISK => (true, "disk"), - FILE_TYPE_UNKNOWN => (false, "unknown"), - FILE_TYPE_REMOTE => (false, "remote"), - unexpected => { - warn!("Unexpected file type: {unexpected:?}"); - (false, "unknown") - } - }; - - debug!("File type: {file_type:?}, well known: {well_known}"); - - // If it's anything recognizable and valid, assume that a parent process has setup an output stream - Ok(well_known.then_some(file_type)) - } - - /// Try to attach to parent console - fn attach_to_parent_console() -> Result<(), PlatformError> { - use windows::{ - core::PCSTR, - Win32::{ - Foundation::{GENERIC_READ, GENERIC_WRITE}, - Storage::FileSystem::{CreateFileA, FILE_FLAGS_AND_ATTRIBUTES, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING}, - System::Console::{ - AttachConsole, FreeConsole, SetStdHandle, ATTACH_PARENT_PROCESS, STD_ERROR_HANDLE, STD_OUTPUT_HANDLE, - }, - }, - }; - - // Attach the process to the parent's console - unsafe { AttachConsole(ATTACH_PARENT_PROCESS) } - .map_err(|e| PlatformError::ConsoleInit(format!("Failed to attach to parent console: {:?}", e)))?; - - let handle = unsafe { - let pcstr = PCSTR::from_raw(c"CONOUT$".as_ptr() as *const u8); - CreateFileA::( - pcstr, - (GENERIC_READ | GENERIC_WRITE).0, - FILE_SHARE_READ | FILE_SHARE_WRITE, - None, - OPEN_EXISTING, - FILE_FLAGS_AND_ATTRIBUTES(0), - None, - ) + // Get the process's standard output handle, check if it's invalid + let handle = match unsafe { GetStdHandle(STD_OUTPUT_HANDLE) } { + INVALID_HANDLE_VALUE => { + return Err(PlatformError::ConsoleInit("Invalid handle".to_string())); } - .map_err(|e| PlatformError::ConsoleInit(format!("Failed to create console handle: {:?}", e)))?; + handle => handle, + }; - // Set the console's output and then error handles - if let Some(handle_error) = unsafe { SetStdHandle(STD_OUTPUT_HANDLE, handle) } - .map_err(|e| PlatformError::ConsoleInit(format!("Failed to set console output handle: {:?}", e))) - .and_then(|_| { - unsafe { SetStdHandle(STD_ERROR_HANDLE, handle) } - .map_err(|e| PlatformError::ConsoleInit(format!("Failed to set console error handle: {:?}", e))) - }) - .err() - { - // If either set handle call fails, free the console - unsafe { FreeConsole() } - // Free the console if the SetStdHandle calls fail - .map_err(|free_error| { - PlatformError::ConsoleInit(format!( - "Failed to free console after SetStdHandle failed: {free_error:?} ({handle_error:?})" - )) - }) - // And then return the original error if the FreeConsole call succeeds - .and(Err(handle_error))?; + // Identify the file type of the handle and whether it's 'well known' (i.e. we trust it to be a reasonable output destination) + let (well_known, file_type) = match unsafe { + use windows::Win32::Foundation::HANDLE; + GetFileType(HANDLE(handle)) + } { + FILE_TYPE_PIPE => (true, "pipe"), + FILE_TYPE_CHAR => (true, "char"), + FILE_TYPE_DISK => (true, "disk"), + FILE_TYPE_UNKNOWN => (false, "unknown"), + FILE_TYPE_REMOTE => (false, "remote"), + unexpected => { + warn!("Unexpected file type: {unexpected:?}"); + (false, "unknown") } + }; - Ok(()) - } + debug!("File type: {file_type:?}, well known: {well_known}"); + + // If it's anything recognizable and valid, assume that a parent process has setup an output stream + Ok(well_known.then_some(file_type)) +} + +/// Try to attach to parent console +/// Windows-only +#[cfg(windows)] +fn attach_to_parent_console() -> Result<(), PlatformError> { + use windows::{ + core::PCSTR, + Win32::{ + Foundation::{GENERIC_READ, GENERIC_WRITE}, + Storage::FileSystem::{CreateFileA, FILE_FLAGS_AND_ATTRIBUTES, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING}, + System::Console::{ + AttachConsole, FreeConsole, SetStdHandle, ATTACH_PARENT_PROCESS, STD_ERROR_HANDLE, STD_OUTPUT_HANDLE, + }, + }, + }; + + // Attach the process to the parent's console + unsafe { AttachConsole(ATTACH_PARENT_PROCESS) } + .map_err(|e| PlatformError::ConsoleInit(format!("Failed to attach to parent console: {:?}", e)))?; + + let handle = unsafe { + let pcstr = PCSTR::from_raw(c"CONOUT$".as_ptr() as *const u8); + CreateFileA::( + pcstr, + (GENERIC_READ | GENERIC_WRITE).0, + FILE_SHARE_READ | FILE_SHARE_WRITE, + None, + OPEN_EXISTING, + FILE_FLAGS_AND_ATTRIBUTES(0), + None, + ) + } + .map_err(|e| PlatformError::ConsoleInit(format!("Failed to create console handle: {:?}", e)))?; + + // Set the console's output and then error handles + if let Some(handle_error) = unsafe { SetStdHandle(STD_OUTPUT_HANDLE, handle) } + .map_err(|e| PlatformError::ConsoleInit(format!("Failed to set console output handle: {:?}", e))) + .and_then(|_| { + unsafe { SetStdHandle(STD_ERROR_HANDLE, handle) } + .map_err(|e| PlatformError::ConsoleInit(format!("Failed to set console error handle: {:?}", e))) + }) + .err() + { + // If either set handle call fails, free the console + unsafe { FreeConsole() } + // Free the console if the SetStdHandle calls fail + .map_err(|free_error| { + PlatformError::ConsoleInit(format!( + "Failed to free console after SetStdHandle failed: {free_error:?} ({handle_error:?})" + )) + }) + // And then return the original error if the FreeConsole call succeeds + .and(Err(handle_error))?; + } + + Ok(()) } diff --git a/src/platform/emscripten.rs b/src/platform/emscripten.rs index a0eb2fa..af4c69e 100644 --- a/src/platform/emscripten.rs +++ b/src/platform/emscripten.rs @@ -5,51 +5,7 @@ use std::time::Duration; use crate::asset::Asset; use crate::error::{AssetError, PlatformError}; -use crate::platform::CommonPlatform; - -/// Emscripten platform implementation. -pub struct Platform; - -impl CommonPlatform for Platform { - fn sleep(&self, duration: Duration, _focused: bool) { - 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 requires_console(&self) -> bool { - false - } - - fn get_canvas_size(&self) -> Option<(u32, u32)> { - Some(unsafe { get_canvas_size() }) - } - - fn get_asset_bytes(&self, asset: Asset) -> Result, 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::other(e)))?; - - Ok(Cow::Owned(buf)) - } -} +use rand::{rngs::SmallRng, SeedableRng}; // Emscripten FFI functions extern "C" { @@ -58,9 +14,54 @@ extern "C" { fn emscripten_get_element_css_size(target: *const u8, width: *mut f64, height: *mut f64) -> i32; } -unsafe fn get_canvas_size() -> (u32, u32) { +pub fn sleep(duration: Duration, _focused: bool) { + unsafe { + emscripten_sleep(duration.as_millis() as u32); + } +} + +pub fn get_time() -> f64 { + unsafe { emscripten_get_now() } +} + +pub fn init_console() -> Result<(), PlatformError> { + Ok(()) // No-op for Emscripten +} + +pub fn requires_console() -> bool { + false +} + +pub fn get_canvas_size() -> Option<(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) + + 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)) +} + +pub fn get_asset_bytes(asset: Asset) -> Result, 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::other(e)))?; + + Ok(Cow::Owned(buf)) +} + +pub fn rng() -> SmallRng { + SmallRng::from_os_rng() } diff --git a/src/platform/mod.rs b/src/platform/mod.rs index aeef2f8..ac4340f 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -1,52 +1,13 @@ //! 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 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); +#[cfg(not(target_os = "emscripten"))] +mod desktop; +#[cfg(not(target_os = "emscripten"))] +pub use desktop::*; - #[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, 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"))] - { - &desktop::Platform - } - - #[cfg(target_os = "emscripten")] - { - &emscripten::Platform - } -} +#[cfg(target_os = "emscripten")] +pub use emscripten::*; +#[cfg(target_os = "emscripten")] +mod emscripten; diff --git a/src/systems/ghost.rs b/src/systems/ghost.rs index a8e294c..7af363b 100644 --- a/src/systems/ghost.rs +++ b/src/systems/ghost.rs @@ -1,3 +1,4 @@ +use crate::platform; use crate::systems::components::{DirectionalAnimation, Frozen, GhostAnimation, GhostState, LastAnimationState, LinearAnimation}; use crate::{ map::{ @@ -14,9 +15,7 @@ use crate::{ use crate::systems::GhostAnimations; use bevy_ecs::query::Without; use bevy_ecs::system::{Commands, Query, Res}; -use rand::rngs::SmallRng; use rand::seq::IndexedRandom; -use rand::SeedableRng; use smallvec::SmallVec; /// Autonomous ghost AI system implementing randomized movement with backtracking avoidance. @@ -49,7 +48,7 @@ pub fn ghost_movement_system( break; } } else { - *non_opposite_options.choose(&mut SmallRng::from_os_rng()).unwrap() + *non_opposite_options.choose(&mut platform::rng()).unwrap() }; velocity.direction = new_edge.direction;