From 0695e0778aabdade198ab6c1cfdeeeb47165c318 Mon Sep 17 00:00:00 2001 From: Jakub Duchniewicz Date: Mon, 8 Jun 2026 19:36:07 +0200 Subject: [PATCH] hot-reload: Add UI to display status. Don't crash when build fails Signed-off-by: Jakub Duchniewicz --- engine/pill_engine/src/app_config.rs | 58 ++++++++++++++++ .../build_status_indicator_component.rs | 45 +++++++++++++ .../ecs/components/egui_manager_component.rs | 40 ++++++++++- engine/pill_engine/src/ecs/components/mod.rs | 2 + engine/pill_engine/src/ecs/mod.rs | 8 +++ .../src/ecs/systems/build_status_system.rs | 24 +++++++ engine/pill_engine/src/ecs/systems/mod.rs | 2 + engine/pill_engine/src/engine.rs | 19 +++++- engine/pill_engine/src/lib.rs | 4 +- engine/pill_launcher/src/main.rs | 25 +++---- engine/pill_native/src/main.rs | 40 ++++++++--- engine/pill_renderer/src/renderer.rs | 1 + engine/pill_runtime/Cargo.toml | 2 +- engine/pill_runtime/src/lib.rs | 8 +++ engine/pill_web/src/lib.rs | 13 +++- examples/net_minimal/server/src/main.rs | 67 +++++++++++++------ 16 files changed, 305 insertions(+), 53 deletions(-) create mode 100644 engine/pill_engine/src/ecs/components/build_status_indicator_component.rs create mode 100644 engine/pill_engine/src/ecs/systems/build_status_system.rs diff --git a/engine/pill_engine/src/app_config.rs b/engine/pill_engine/src/app_config.rs index 9f077625..ef7cd7a2 100644 --- a/engine/pill_engine/src/app_config.rs +++ b/engine/pill_engine/src/app_config.rs @@ -1,12 +1,60 @@ use std::collections::HashMap; use pill_core::Result; +use std::str::FromStr; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum CompileMode { + Debug, + Release, + HotReload, +} + +impl CompileMode { + pub(crate) fn from_env_value(value: &str) -> Result { + match value { + "debug" => Ok(Self::Debug), + "release" => Ok(Self::Release), + "hot-reload" => Ok(Self::HotReload), + other => Err(format!("Invalid compile mode: {other}").into()), + } + } + + pub(crate) fn as_str(&self) -> &'static str { + match self { + CompileMode::Debug => "debug", + CompileMode::Release => "release", + CompileMode::HotReload => "hot-reload", + } + } +} + +#[derive(Clone)] +pub enum BuildTarget { + Native, + Web, +} + +impl BuildTarget { + pub(crate) fn as_str(&self) -> &'static str { + match self { + BuildTarget::Web => "web", + BuildTarget::Native => "native", + } + } +} #[derive(Default, Clone)] pub struct EngineConfig { values: HashMap, } +#[derive(Clone)] +pub struct EngineProcessInfo { + pub(crate) mode: CompileMode, + pub(crate) target: BuildTarget, +} + impl EngineConfig { pub fn from_ini(input: &str) -> Self { let mut values = HashMap::new(); @@ -56,3 +104,13 @@ impl EngineConfig { } } } + +impl EngineProcessInfo { + pub fn new(mode: &str, target: BuildTarget) -> Self { + let translated = CompileMode::from_env_value(mode).unwrap(); + Self { + target, + mode: translated, + } + } +} diff --git a/engine/pill_engine/src/ecs/components/build_status_indicator_component.rs b/engine/pill_engine/src/ecs/components/build_status_indicator_component.rs new file mode 100644 index 00000000..8b0f0b38 --- /dev/null +++ b/engine/pill_engine/src/ecs/components/build_status_indicator_component.rs @@ -0,0 +1,45 @@ +#![cfg(feature = "debug_ui")] + +use std::collections::HashMap; + +use crate::{ + ecs::{ + components::{GlobalComponent, GlobalComponentStorage}, + UpdatePhase, + }, + engine::Engine, +}; + +use egui::Ui; +use pill_core::{PillTypeMapKey, Timer, TimerRecord}; + +use pill_core::{ErrorContext, Result}; + +#[derive(Copy, Clone)] +pub enum BuildStatus { + Pass, + Fail, + Warning, +} + +// display the build type + target +// also display the last hot-reload status (compiled the code and reloaded or failed) with red/green +// light indicator +// updated every time we try hot-reloading and build_game +pub struct BuildStatusIndicatorComponent { + pub(crate) last_build_status: BuildStatus, +} + +impl Default for BuildStatusIndicatorComponent { + fn default() -> Self { + Self { + last_build_status: BuildStatus::Pass, + } + } +} + +impl PillTypeMapKey for BuildStatusIndicatorComponent { + type Storage = GlobalComponentStorage; +} + +impl GlobalComponent for BuildStatusIndicatorComponent {} diff --git a/engine/pill_engine/src/ecs/components/egui_manager_component.rs b/engine/pill_engine/src/ecs/components/egui_manager_component.rs index 5030c3a2..8cb72f66 100644 --- a/engine/pill_engine/src/ecs/components/egui_manager_component.rs +++ b/engine/pill_engine/src/ecs/components/egui_manager_component.rs @@ -5,12 +5,13 @@ use std::collections::HashMap; use crate::{ ecs::{ components::{GlobalComponent, GlobalComponentStorage}, - UpdatePhase, + BuildStatus, BuildStatusIndicatorComponent, UpdatePhase, }, engine::Engine, + internal::CompileMode, }; -use egui::Ui; +use egui::{Color32, Ui}; use pill_core::{PillTypeMapKey, Timer, TimerRecord}; use pill_core::{ErrorContext, Result}; @@ -25,6 +26,7 @@ impl Default for EguiManagerComponent { } } +// TODO: add the build type/status impl EguiManagerComponent { pub fn new() -> Self { Self { @@ -75,11 +77,41 @@ impl EguiManagerComponent { .sum::(); let frame_delta_time = engine.frame_delta_time; + let build_mode = engine.process.mode.as_str(); + let build_target = engine.process.target.as_str(); + let build_status = engine + .get_global_component::() + .unwrap() + .last_build_status; + let ui = Box::new(move |ui: &egui::Context| { egui::Window::new("Pill Engine") - .default_open(false) + .default_open(true) .resizable(true) .anchor(egui::Align2::LEFT_TOP, [0.0, 0.0]) + .show(ui, |ui| { + egui::ScrollArea::vertical().show(ui, |ui| { + ui.add(egui::Label::new(format!("Mode: {}", build_mode))); + ui.add(egui::Label::new(format!("Target: {}", build_target))); + if build_mode == CompileMode::HotReload.as_str() { + ui.add(egui::Label::new("Build Status:")); + let color = match build_status { + BuildStatus::Pass => Color32::GREEN, + BuildStatus::Fail => Color32::RED, + BuildStatus::Warning => Color32::YELLOW, + }; + let (rect, _) = ui + .allocate_exact_size(egui::vec2(24.0, 24.0), egui::Sense::hover()); + ui.painter().circle_filled(rect.center(), 12.0, color); + } + }); + }); + + egui::Window::new("Details") + .default_open(false) + .resizable(true) + //.anchor(egui::Align2::LEFT_TOP, [0.0, 0.0]) // TODO: how to make it below the + // previous .show(ui, |ui| { egui::ScrollArea::vertical() .auto_shrink([false; 2]) // optional: prevent auto shrink @@ -87,6 +119,8 @@ impl EguiManagerComponent { if ui.add(egui::Button::new("Click me")).clicked() { println!("PRESSED"); } + ui.add(egui::Label::new(format!("Mode: {}", build_mode))); + ui.add(egui::Label::new(format!("Target: {}", build_target))); ui.add(egui::Label::new(format!( "FPS {}", 1000.0 / frame_delta_time diff --git a/engine/pill_engine/src/ecs/components/mod.rs b/engine/pill_engine/src/ecs/components/mod.rs index 210a45d3..dae667b2 100644 --- a/engine/pill_engine/src/ecs/components/mod.rs +++ b/engine/pill_engine/src/ecs/components/mod.rs @@ -6,6 +6,8 @@ pub(crate) mod audio_listener_component; pub(crate) mod audio_manager_component; #[cfg(not(target_arch = "wasm32"))] pub(crate) mod audio_source_component; +#[cfg(feature = "debug_ui")] +pub(crate) mod build_status_indicator_component; pub(crate) mod camera_component; mod component; mod component_storage; diff --git a/engine/pill_engine/src/ecs/mod.rs b/engine/pill_engine/src/ecs/mod.rs index 248c7e8e..c37b6b31 100644 --- a/engine/pill_engine/src/ecs/mod.rs +++ b/engine/pill_engine/src/ecs/mod.rs @@ -31,6 +31,11 @@ pub use components::audio_source_component::AudioSourceComponent; #[cfg(feature = "debug_ui")] pub use components::egui_manager_component::EguiManagerComponent; +#[cfg(feature = "debug_ui")] +pub use components::build_status_indicator_component::{ + BuildStatus, BuildStatusIndicatorComponent, +}; + pub use components::deferred_update_component::{ DeferredUpdateComponent, DeferredUpdateComponentRequest, DeferredUpdateManagerPointer, DeferredUpdateRequest, DeferredUpdateResourceRequest, @@ -81,6 +86,9 @@ pub use systems::networking_system::{ NetworkEntityAction, NetworkUpdatePayload, }; +#[cfg(feature = "debug_ui")] +pub use systems::build_status_system::build_status_system; + // - Other pub use entity::{Entity, EntityBuilder, EntityHandle}; diff --git a/engine/pill_engine/src/ecs/systems/build_status_system.rs b/engine/pill_engine/src/ecs/systems/build_status_system.rs new file mode 100644 index 00000000..34749db0 --- /dev/null +++ b/engine/pill_engine/src/ecs/systems/build_status_system.rs @@ -0,0 +1,24 @@ +#![cfg(feature = "debug_ui")] +use crate::{ + ecs::{BuildStatus, BuildStatusIndicatorComponent}, + engine::Engine, +}; +use pill_core::{Matrix3f, Vector3f}; + +use pill_core::Result; + +pub fn build_status_system(engine: &mut Engine) -> Result<()> { + let build_status_component = + engine.get_global_component_mut::()?; + + let status_env = std::env::var("PILL_HOT_RELOAD_STATUS") + .map_err(|_| "PILL_HOT_RELOAD_STATUS is not set!")?; + let status = match status_env.as_str() { + "fail" => BuildStatus::Fail, + "warn" => BuildStatus::Warning, + "pass" => BuildStatus::Pass, + _ => BuildStatus::Fail, + }; + build_status_component.last_build_status = status; + Ok(()) +} diff --git a/engine/pill_engine/src/ecs/systems/mod.rs b/engine/pill_engine/src/ecs/systems/mod.rs index 6dee9b19..3d1bd7b7 100644 --- a/engine/pill_engine/src/ecs/systems/mod.rs +++ b/engine/pill_engine/src/ecs/systems/mod.rs @@ -2,6 +2,8 @@ #[cfg(not(target_arch = "wasm32"))] pub(crate) mod audio_system; +#[cfg(feature = "debug_ui")] +pub(crate) mod build_status_system; pub(crate) mod deferred_update_system; pub(crate) mod input_system; #[cfg(not(target_arch = "wasm32"))] diff --git a/engine/pill_engine/src/engine.rs b/engine/pill_engine/src/engine.rs index 427f3bef..11326dfe 100644 --- a/engine/pill_engine/src/engine.rs +++ b/engine/pill_engine/src/engine.rs @@ -1,3 +1,4 @@ +use crate::app_config::EngineProcessInfo; use crate::{app_config::EngineConfig, config::*, ecs::*, graphics::*, resources::*}; use pill_core::{ @@ -25,6 +26,7 @@ pub trait PillGame { /// Heart of Pill Engine pub struct Engine { pub(crate) config: EngineConfig, + pub(crate) process: EngineProcessInfo, pub(crate) game: Option, pub(crate) renderer: Box, pub(crate) scene_manager: SceneManager, @@ -49,6 +51,7 @@ impl Engine { game_resources_directory_path: std::path::PathBuf, renderer: Box, config: EngineConfig, + process: EngineProcessInfo, ) -> Self { let max_entity_count = config .get_int("MAX_ENTITIES") @@ -56,6 +59,7 @@ impl Engine { Self { config, + process, game: Some(game), renderer, scene_manager: SceneManager::new(max_entity_count), @@ -71,7 +75,7 @@ impl Engine { } #[cfg(feature = "headless")] - pub fn new(game: Box, config: EngineConfig) -> Self { + pub fn new(game: Box, config: EngineConfig, process: EngineProcessInfo) -> Self { let max_entity_count = config .get_int("MAX_ENTITIES") .unwrap_or(MAX_ENTITIES as i64) as usize; @@ -79,6 +83,7 @@ impl Engine { Self { config, + process, game: Some(game), renderer: dummy_renderer, scene_manager: SceneManager::new(max_entity_count), @@ -303,7 +308,10 @@ impl Engine { self.add_global_component(TimeComponent::new())?; self.add_global_component(DeferredUpdateComponent::new())?; #[cfg(feature = "debug_ui")] - self.add_global_component(EguiManagerComponent::new())?; + { + self.add_global_component(EguiManagerComponent::new())?; + self.add_global_component(BuildStatusIndicatorComponent::default())?; + } #[cfg(not(feature = "headless"))] { @@ -358,6 +366,13 @@ impl Engine { )?; } + #[cfg(feature = "debug_ui")] + self.system_manager.add_system( + "build_status_system", + build_status_system, + UpdatePhase::PostGame, + )?; + // Create default resources self.create_default_resources() .context("Failed to create default resources")?; diff --git a/engine/pill_engine/src/lib.rs b/engine/pill_engine/src/lib.rs index 46f92b24..598e087b 100644 --- a/engine/pill_engine/src/lib.rs +++ b/engine/pill_engine/src/lib.rs @@ -91,7 +91,7 @@ pub mod game { #[cfg(not(target_arch = "wasm32"))] mod internal_mod { - pub use crate::app_config::EngineConfig; + pub use crate::app_config::{BuildTarget, CompileMode, EngineConfig, EngineProcessInfo}; pub use crate::{ config::*, ecs::{ @@ -120,7 +120,7 @@ mod internal_mod { #[cfg(target_arch = "wasm32")] mod internal_mod { - pub use crate::app_config::EngineConfig; + pub use crate::app_config::{BuildTarget, CompileMode, EngineConfig, EngineProcessInfo}; pub use crate::{ config::*, ecs::{ diff --git a/engine/pill_launcher/src/main.rs b/engine/pill_launcher/src/main.rs index 955505f2..1484cd79 100644 --- a/engine/pill_launcher/src/main.rs +++ b/engine/pill_launcher/src/main.rs @@ -1,8 +1,8 @@ #![allow(non_snake_case, dead_code)] -mod web_dev_server; mod size_report; mod wasm_build; +mod web_dev_server; use anyhow::*; use clap::{App, AppSettings, Arg}; @@ -66,7 +66,7 @@ fn dylib(name: &str) -> String { format!("{DYLIB_PREFIX}{name}{DYLIB_SUFFIX}") } -fn target_dir_for(mode: &CompileMode) -> &'static str { +fn to_str(mode: &CompileMode) -> &'static str { match mode { CompileMode::Release => "release", CompileMode::Debug => "debug", @@ -558,7 +558,7 @@ fn prepare_workspace_for_game( if switching_game { let compilation_artifacts_folder_path = get_path(Location::EngineCrates) .join("target") - .join(target_dir_for(compile_mode)); + .join(to_str(compile_mode)); let artifact_prefix = if cfg!(target_os = "windows") { "pill_game" @@ -749,14 +749,7 @@ fn run_game_project( "PILL_STANDALONE_LAYOUT", standalone_layout_for(compile_mode), ) - .env( - "PILL_ENABLE_HOT_RELOAD", - if *compile_mode == CompileMode::HotReload { - "1" - } else { - "0" - }, - ) + .env("PILL_COMPILE_MODE", to_str(compile_mode)) .args(game_args) .status() .with_context(|| { @@ -838,7 +831,7 @@ fn build_game_project( .ok_or_else(|| Error::msg("build failed"))?; // Where cargo artifacts actually are now: - let compilation_artifacts_folder_path = cargo_target_dir.join(target_dir_for(compile_mode)); + let compilation_artifacts_folder_path = cargo_target_dir.join(to_str(compile_mode)); // Ensure build/data exists fs::create_dir_all(output_directory_path.join("data").as_path()) @@ -1290,8 +1283,12 @@ fn run_app() -> Result<()> { "Note: `-o/--output-path` is ignored with `-t wasm`; output is fixed at /build/wasm/" ); } - wasm_build::build(&game_project_directory_path, &compile_mode, max_wasm_size_kb) - .context("Failed to build game project for wasm")?; + wasm_build::build( + &game_project_directory_path, + &compile_mode, + max_wasm_size_kb, + ) + .context("Failed to build game project for wasm")?; } } } diff --git a/engine/pill_native/src/main.rs b/engine/pill_native/src/main.rs index 534b6c5c..5fe59c0c 100644 --- a/engine/pill_native/src/main.rs +++ b/engine/pill_native/src/main.rs @@ -711,14 +711,14 @@ fn build_hot_reload_via_launcher(project_paths: &ProjectPaths) -> Result<()> { output_directory.to_str().unwrap(), ]; - let status = std::process::Command::new(&launcher_cmd) + let output = std::process::Command::new(&launcher_cmd) .args(args) .env("PILL_HOT_RELOAD_CHILD", "1") .env("PILL_ENGINE_WORKSPACE_DIR", engine_source_directory_path) - .status(); + .output(); - let status = match status { - Ok(status) => status, + let output = match output { + Ok(output) => output, Err(error) if error.kind() == std::io::ErrorKind::NotFound => { let manifest = engine_source_directory_path .join("pill_launcher") @@ -728,16 +728,29 @@ fn build_hot_reload_via_launcher(project_paths: &ProjectPaths) -> Result<()> { .args(args) .env("PILL_HOT_RELOAD_CHILD", "1") .env("PILL_ENGINE_WORKSPACE_DIR", engine_source_directory_path) - .status() + .output() .context("Failed to invoke pill_launcher via cargo for hot reload")? } Err(error) => return Err(error).context("Failed to invoke pill_launcher for hot reload"), }; - if !status.success() { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + print!("{stdout}"); + eprint!("{stderr}"); + + if !output.status.success() { + std::env::set_var("PILL_HOT_RELOAD_STATUS", "fail"); bail!("pill_launcher build hot-reload failed"); } + let has_warnings = stdout.contains("warning:") || stderr.contains("warning:"); + if has_warnings { + std::env::set_var("PILL_HOT_RELOAD_STATUS", "warn"); + } else { + std::env::set_var("PILL_HOT_RELOAD_STATUS", "pass"); + } Ok(()) } @@ -804,7 +817,18 @@ fn check_and_reload( let build_start = Instant::now(); if !game_source_changed.is_empty() || !engine_source_changed.is_empty() { - build_hot_reload_via_launcher(project_paths)?; + if let Err(error) = build_hot_reload_via_launcher(project_paths) { + warn!( + LogContext::HotReload => + "Hot-reload build failed; keeping currently loaded runtime/game. Error: {error:?}" + ); + + // drain dylib watcher changes to reduce stale/partial build triggers (fat or fast + // fingers) + let _ = file_watchers.dynamic_libraries_files_watcher.get_changes(); + + return Ok(()); + } warn!("Build took: {:?} time", build_start.elapsed()); } @@ -1193,7 +1217,7 @@ fn run_app() -> Result<()> { // └── Cargo.lock let mut hot_reload_enabled = - std::env::var("PILL_ENABLE_HOT_RELOAD").ok().as_deref() == Some("1"); + std::env::var("PILL_COMPILE_MODE").ok().as_deref() == Some("hot-reload"); let current_directory_path = std::env::current_exe() .context("Failed to get current executable path")? diff --git a/engine/pill_renderer/src/renderer.rs b/engine/pill_renderer/src/renderer.rs index 6d34ad57..8716c063 100644 --- a/engine/pill_renderer/src/renderer.rs +++ b/engine/pill_renderer/src/renderer.rs @@ -306,6 +306,7 @@ pub struct State { mesh_drawer: MeshDrawer, #[cfg(feature = "debug_ui")] egui_drawer: crate::drawers::egui_drawer::EguiDrawer, + #[allow(clippy::type_complexity)] #[cfg(feature = "debug_ui")] pending_egui_ui: Option>, // Other diff --git a/engine/pill_runtime/Cargo.toml b/engine/pill_runtime/Cargo.toml index 22b4d6a0..39151455 100644 --- a/engine/pill_runtime/Cargo.toml +++ b/engine/pill_runtime/Cargo.toml @@ -10,7 +10,7 @@ crate-type = ["cdylib", "rlib"] pill_abi = { path = "../pill_abi" } pill_core = { path = "../pill_core" } pill_engine = { path = "../pill_engine", features = ["internal"] } -pill_renderer = { path = "../pill_renderer" } +pill_renderer = { path = "../pill_renderer", features = ["debug_ui"] } libloading = "0.8.8" winit = "0.30.12" diff --git a/engine/pill_runtime/src/lib.rs b/engine/pill_runtime/src/lib.rs index 978894de..6a9db4ab 100644 --- a/engine/pill_runtime/src/lib.rs +++ b/engine/pill_runtime/src/lib.rs @@ -62,6 +62,8 @@ struct Runtime { config: EngineConfig, + process: EngineProcessInfo, + // Keep engine ptr for hot-reload engine: Option, game_library: Option, @@ -79,6 +81,7 @@ impl Runtime { self.resource_directory.clone(), renderer, self.config.clone(), + self.process.clone(), ); engine.initialize(Some(self.window_size))?; Ok(engine) @@ -126,6 +129,10 @@ extern "C" fn create(args: *const PillEngineCreateArgsV1, out_engine: *mut Engin if config.get_int("WINDOW_HEIGHT").is_err() { config.set("WINDOW_HEIGHT", a.initial_h as i64); } + let compile_mode = + std::env::var("PILL_COMPILE_MODE").map_err(|_| "PILL_COMPILE_MODE is not set")?; + let process = + EngineProcessInfo::new(&compile_mode, pill_engine::internal::BuildTarget::Native); let (game_library, game) = load_game(&game_library_path)?; @@ -134,6 +141,7 @@ extern "C" fn create(args: *const PillEngineCreateArgsV1, out_engine: *mut Engin window_size: winit::dpi::PhysicalSize::new(a.initial_w, a.initial_h), resource_directory: game_resource_dir.into(), config, + process, engine: None, game_library: Some(game_library), }); diff --git a/engine/pill_web/src/lib.rs b/engine/pill_web/src/lib.rs index 53c59464..f9312be7 100644 --- a/engine/pill_web/src/lib.rs +++ b/engine/pill_web/src/lib.rs @@ -95,6 +95,11 @@ async fn run_async(game: Box, config_ini: &'static str) { let mut config = pill_engine::internal::EngineConfig::from_ini(config_ini); config.set("WINDOW_WIDTH", window_size.width as i64); config.set("WINDOW_HEIGHT", window_size.height as i64); + let compile_mode = std::env::var("PILL_COMPILE_MODE").unwrap_or_else(|_| "unknown".to_string()); + let process = pill_engine::internal::EngineProcessInfo::new( + &compile_mode, + pill_engine::internal::BuildTarget::Web, + ); log::info!("Creating renderer..."); let renderer: Box = Box::new(must!( @@ -102,7 +107,13 @@ async fn run_async(game: Box, config_ini: &'static str) { )); log::info!("Creating engine..."); - let mut engine = Engine::new(game, std::path::PathBuf::from("res"), renderer, config); + let mut engine = Engine::new( + game, + std::path::PathBuf::from("res"), + renderer, + config, + process, + ); log::info!("Initializing engine..."); match engine.initialize(Some(window_size)) { diff --git a/examples/net_minimal/server/src/main.rs b/examples/net_minimal/server/src/main.rs index 094316db..5e8f2556 100644 --- a/examples/net_minimal/server/src/main.rs +++ b/examples/net_minimal/server/src/main.rs @@ -1,21 +1,32 @@ +use log::info; use pill_core::Result; -use pill_engine::internal::{Engine, EngineConfig, PillGame, TransformComponent, NetworkStateComponent, NetworkSide, NetworkEntityState, networking_system_server}; use pill_core::{server_broadcast_exit, server_dying_grasp}; -use log::info; -use std::time::{Duration, Instant}; -use env_logger; +use pill_engine::internal::{ + networking_system_server, Engine, EngineConfig, NetworkEntityState, NetworkSide, + NetworkStateComponent, PillGame, TransformComponent, +}; +use pill_engine::internal::{EngineProcessInfo, NetworkManagerComponent}; use std::io::Write; -use pill_engine::internal::{NetworkManagerComponent}; +use std::time::{Duration, Instant}; -fn spawn_player(engine: &mut Engine, network_state_component: &NetworkStateComponent, transform: &TransformComponent) -> Result<()> { - let my_id = engine.get_global_component_mut::()?.my_id; +fn spawn_player( + engine: &mut Engine, + network_state_component: &NetworkStateComponent, + transform: &TransformComponent, +) -> Result<()> { + let my_id = engine + .get_global_component_mut::()? + .my_id; let scene = engine.get_active_scene_handle()?; - println!("[SERVER] Spawning PLAYER with nid{ } for cid {} with transform {:?}", network_state_component.network_entity_id, my_id, transform); + println!( + "[SERVER] Spawning PLAYER with nid{ } for cid {} with transform {:?}", + network_state_component.network_entity_id, my_id, transform + ); let entity = engine.create_entity(scene)?; - let mut network_state = network_state_component.clone(); - network_state.state = NetworkEntityState::Alive; + let mut network_state = network_state_component.clone(); + network_state.state = NetworkEntityState::Alive; engine.add_component_to_entity(scene, entity, network_state)?; @@ -23,11 +34,13 @@ fn spawn_player(engine: &mut Engine, network_state_component: &NetworkStateCompo // TODO: missing playerTag and targetTransform components - println!("[SERVER] Spawn finished with nid{ } for cid {} with transform {:?}", network_state_component.network_entity_id, my_id, transform); + println!( + "[SERVER] Spawn finished with nid{ } for cid {} with transform {:?}", + network_state_component.network_entity_id, my_id, transform + ); Ok(()) } - struct HeadlessGame; // TODO: placeholder for the actual game struct // impl PillGame for HeadlessGame { @@ -42,13 +55,12 @@ impl PillGame for HeadlessGame { let mut network_manager = NetworkManagerComponent::new_server("0.0.0.0:5000", 8)?; - network_manager.spawn_handlers.insert("player".into(), spawn_player); + network_manager + .spawn_handlers + .insert("player".into(), spawn_player); engine.add_global_component(network_manager)?; - engine.add_system( - "NetworkingSystemServer", - networking_system_server, - )?; + engine.add_system("NetworkingSystemServer", networking_system_server)?; log::info!("Server listening on 0.0.0.0:5000"); @@ -60,7 +72,9 @@ fn main() -> Result<()> { #[cfg(debug_assertions)] env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")) .format(|buf, record| { - writeln!(buf, "[{}] {} {}:{}: {}", + writeln!( + buf, + "[{}] {} {}:{}: {}", record.level(), chrono::Local::now().format("%Y-%m-%dT%H:%M:%S"), record.file().unwrap_or("unknown"), @@ -72,12 +86,18 @@ fn main() -> Result<()> { .init(); let game: Box = Box::new(HeadlessGame); - let mut engine = Engine::new(game, EngineConfig::from_ini("")); + let compile_mode = + std::env::var("PILL_COMPILE_MODE").map_err(|_| "PILL_COMPILE_MODE is not set")?; + let process = EngineProcessInfo::new(&compile_mode, pill_engine::internal::BuildTarget::Native); + let mut engine = Engine::new(game, EngineConfig::from_ini(""), process); engine.initialize(None)?; let (tx, rx) = std::sync::mpsc::channel(); - ctrlc::set_handler(move || { let _ = tx.send(()); }).expect("Error setting Ctrl-C handler"); + ctrlc::set_handler(move || { + let _ = tx.send(()); + }) + .expect("Error setting Ctrl-C handler"); let tick = Duration::from_millis(1000 / 60); // 60 FPS @@ -89,10 +109,13 @@ fn main() -> Result<()> { // graceful shutdown on Ctrl-C if rx.try_recv().is_ok() { info!("Shutdown requested, broadcasting Exit"); - if let Ok(network_manager) = engine.get_global_component_mut::() { + if let Ok(network_manager) = + engine.get_global_component_mut::() + { if let NetworkSide::Server(state) = &mut network_manager.side { let _ = server_broadcast_exit(&mut state.net, "Server shutting down"); - let _ = server_dying_grasp(&mut state.net, std::time::Duration::from_millis(500)); + let _ = + server_dying_grasp(&mut state.net, std::time::Duration::from_millis(500)); } } break Ok(());