diff --git a/demos/asset_packs/src/main.rs b/demos/asset_packs/src/main.rs index 8bb247882a..75c6656db3 100644 --- a/demos/asset_packs/src/main.rs +++ b/demos/asset_packs/src/main.rs @@ -47,13 +47,11 @@ fn main() { // Create a new session for the game menu. Each session is it's own bones world with it's own // plugins, systems, and entities. game.sessions - .create_with("menu", |builder: &mut SessionBuilder| { - // Install the default bones_framework plugin for this session - builder - .install_plugin(DefaultSessionPlugin) - // Add our menu system to the update stage - .add_system_to_stage(Update, menu_system); - }); + .create("menu") + // Install the default bones_framework plugin for this session + .install_plugin(DefaultSessionPlugin) + // Add our menu system to the update stage + .add_system_to_stage(Update, menu_system); BonesBevyRenderer::new(game).app().run(); } diff --git a/demos/assets_minimal/src/main.rs b/demos/assets_minimal/src/main.rs index b49b7c0a01..a69ecb7841 100644 --- a/demos/assets_minimal/src/main.rs +++ b/demos/assets_minimal/src/main.rs @@ -36,17 +36,13 @@ fn main() { // Create a new session for the game menu. Each session is it's own bones world with it's own // plugins, systems, and entities. - let mut menu_session_builder = SessionBuilder::new("menu"); - - // Install the default bones_framework plugin for this session - menu_session_builder + game.sessions + .create("menu") + // Install the default bones_framework plugin for this session .install_plugin(DefaultSessionPlugin) // Add our menu system to the update stage .add_system_to_stage(Update, menu_system); - // Finalize session and register with game sessions. - menu_session_builder.finish_and_add(&mut game.sessions); - BonesBevyRenderer::new(game).app().run(); } diff --git a/demos/features/src/main.rs b/demos/features/src/main.rs index 51e4380b9d..b7ac675f39 100644 --- a/demos/features/src/main.rs +++ b/demos/features/src/main.rs @@ -139,18 +139,18 @@ struct MenuData { } /// Menu plugin -pub fn menu_plugin(session: &mut SessionBuilder) { +pub fn menu_plugin(session: &mut Session) { // Register our menu system session // Install the bones_framework default plugins for this session .install_plugin(DefaultSessionPlugin) // Initialize our menu data resource - .init_resource::(); + .init_startup_resource::(); // And add our systems. session .add_system_to_stage(Update, menu_system) - .add_startup_system(menu_startup); + .add_system_to_stage(CoreStage::Startup, menu_startup); } /// Setup the main menu. @@ -280,10 +280,10 @@ fn menu_system( } /// Plugin for running the sprite demo. -fn sprite_demo_plugin(session: &mut SessionBuilder) { +fn sprite_demo_plugin(session: &mut Session) { session .install_plugin(DefaultSessionPlugin) - .add_startup_system(sprite_demo_startup) + .add_system_to_stage(CoreStage::Startup, sprite_demo_startup) .add_system_to_stage(Update, back_to_menu_ui) .add_system_to_stage(Update, move_sprite); } @@ -344,10 +344,10 @@ fn move_sprite( } /// Plugin for running the tilemap demo. -fn tilemap_demo_plugin(session: &mut SessionBuilder) { +fn tilemap_demo_plugin(session: &mut Session) { session .install_plugin(DefaultSessionPlugin) - .add_startup_system(tilemap_startup_system) + .add_system_to_stage(CoreStage::Startup, tilemap_startup_system) .add_system_to_stage(Update, back_to_menu_ui); } @@ -365,10 +365,9 @@ fn tilemap_startup_system( // Load our map and atlas info let map_info = assets.get(meta.tilemap_demo); - let atlas = assets.get(map_info.atlas); // Create a new map layer - let mut layer = TileLayer::new(map_info.map_size, atlas.tile_size, map_info.atlas); + let mut layer = TileLayer::new(map_info.map_size, map_info.atlas); // Load the layer up with the tiles from our metadata for tile in &map_info.tiles { @@ -390,10 +389,10 @@ fn tilemap_startup_system( } /// Plugin for running the atlas demo. -fn atlas_demo_plugin(session: &mut SessionBuilder) { +fn atlas_demo_plugin(session: &mut Session) { session .install_plugin(DefaultSessionPlugin) - .add_startup_system(atlas_demo_startup) + .add_system_to_stage(CoreStage::Startup, atlas_demo_startup) .add_system_to_stage(Update, back_to_menu_ui); } @@ -438,7 +437,7 @@ fn atlas_demo_startup( ); } -fn audio_demo_plugin(session: &mut SessionBuilder) { +fn audio_demo_plugin(session: &mut Session) { session .install_plugin(DefaultSessionPlugin) .add_system_to_stage(Update, back_to_menu_ui) @@ -464,7 +463,7 @@ fn audio_demo_ui( }); } -fn storage_demo_plugin(session: &mut SessionBuilder) { +fn storage_demo_plugin(session: &mut Session) { session .install_plugin(DefaultSessionPlugin) .add_system_to_stage(Update, storage_demo_ui) @@ -494,10 +493,10 @@ fn storage_demo_ui( }); } -fn path2d_demo_plugin(session: &mut SessionBuilder) { +fn path2d_demo_plugin(session: &mut Session) { session .install_plugin(DefaultSessionPlugin) - .add_startup_system(path2d_demo_startup) + .add_system_to_stage(CoreStage::Startup, path2d_demo_startup) .add_system_to_stage(Update, back_to_menu_ui); } diff --git a/demos/hello_world/src/main.rs b/demos/hello_world/src/main.rs index b2931bfa4d..d3b0420765 100644 --- a/demos/hello_world/src/main.rs +++ b/demos/hello_world/src/main.rs @@ -11,13 +11,11 @@ fn main() { // Create a new session for the game menu. Each session is it's own bones world with it's own // plugins, systems, and entities. game.sessions - .create_with("menu", |session: &mut SessionBuilder| { - session - // Install the default bones_framework plugin for this session - .install_plugin(DefaultSessionPlugin) - // Add our menu system to the update stage - .add_system_to_stage(Update, menu_system); - }); + .create("menu") + // Install the default bones_framework plugin for this session + .install_plugin(DefaultSessionPlugin) + // Add our menu system to the update stage + .add_system_to_stage(Update, menu_system); BonesBevyRenderer::new(game).app().run(); } diff --git a/demos/scripting/assets/plugin1.plugin.lua b/demos/scripting/assets/plugin1.plugin.lua index 91f34e8229..ff0273b1ea 100644 --- a/demos/scripting/assets/plugin1.plugin.lua +++ b/demos/scripting/assets/plugin1.plugin.lua @@ -30,5 +30,5 @@ local function update() end end -session:add_startup_system(startup) +session:add_system_to_stage(CoreStage.Startup, startup) session:add_system_to_stage(CoreStage.Update, update) diff --git a/demos/scripting/src/main.rs b/demos/scripting/src/main.rs index 8ada4088c8..fb81cce18d 100644 --- a/demos/scripting/src/main.rs +++ b/demos/scripting/src/main.rs @@ -19,12 +19,12 @@ fn main() { game.install_plugin(DefaultGamePlugin); game.shared_resource_mut::() .register_default_assets(); + Transform::register_schema(); GameMeta::register_schema(); game.sessions - .create_with("launch", |builder: &mut SessionBuilder| { - builder.add_startup_system(launch_game_session); - }); + .create("launch") + .add_system_to_stage(CoreStage::Startup, launch_game_session); let mut renderer = BonesBevyRenderer::new(game); renderer.app_namespace = ( @@ -42,16 +42,15 @@ fn launch_game_session( ) { session_ops.delete = true; // Build game session and add to `Sessions` - sessions.create_with("game", |builder: &mut SessionBuilder| { - builder - .install_plugin(DefaultSessionPlugin) - // Install the plugin that will load our lua plugins and run them in the game session - .install_plugin(LuaPluginLoaderSessionPlugin( - // Tell it to install the lua plugins specified in our game meta - Arc::new(meta.plugins.iter().copied().collect()), - )) - .add_startup_system(game_startup); - }); + sessions + .create("game") + .install_plugin(DefaultSessionPlugin) + // Install the plugin that will load our lua plugins and run them in the game session + .install_plugin(LuaPluginLoaderSessionPlugin( + // Tell it to install the lua plugins specified in our game meta + Arc::new(meta.plugins.iter().copied().collect()), + )) + .add_system_to_stage(CoreStage::Startup, game_startup); } fn game_startup( diff --git a/framework_crates/bones_asset/src/lib.rs b/framework_crates/bones_asset/src/lib.rs index 3c8c94e7b2..b54576826a 100644 --- a/framework_crates/bones_asset/src/lib.rs +++ b/framework_crates/bones_asset/src/lib.rs @@ -282,6 +282,13 @@ impl From> for Maybe { } } +impl From for Maybe { + #[inline] + fn from(value: T) -> Self { + Maybe::Set(value) + } +} + fn maybe_loader( ctx: &mut MetaAssetLoadCtx, ptr: SchemaRefMut<'_>, diff --git a/framework_crates/bones_bevy_renderer/src/render.rs b/framework_crates/bones_bevy_renderer/src/render.rs index 41eb2a70c5..8e20bb82b6 100644 --- a/framework_crates/bones_bevy_renderer/src/render.rs +++ b/framework_crates/bones_bevy_renderer/src/render.rs @@ -430,7 +430,7 @@ pub fn extract_bones_tilemaps( let tile = tiles.get(*tile_ent).unwrap(); let tile_pos = tile_layer.pos(tile_pos_idx as u32); - let tile_offset = tile_pos.as_vec2() * tile_layer.tile_size; + let tile_offset = tile_pos.as_vec2() * atlas.tile_size; let sprite_idx = tile.idx; let y = sprite_idx / atlas.columns; diff --git a/framework_crates/bones_ecs/examples/pos_vel.rs b/framework_crates/bones_ecs/examples/pos_vel.rs index 6d4f52ea2d..fba37e4944 100644 --- a/framework_crates/bones_ecs/examples/pos_vel.rs +++ b/framework_crates/bones_ecs/examples/pos_vel.rs @@ -23,17 +23,14 @@ fn main() { let mut world = World::new(); // Build SystemStages to store the systems that we will run more than once. - let mut stages_builder = SystemStagesBuilder::with_core_stages(); + let mut stages = SystemStages::with_core_stages(); // Add our systems to the system stages - stages_builder - .add_startup_system(startup_system) + stages + .add_system_to_stage(CoreStage::Startup, startup_system) .add_system_to_stage(CoreStage::Update, pos_vel_system) .add_system_to_stage(CoreStage::PostUpdate, print_system); - // Finish build to get `SystemStages`. - let mut stages = stages_builder.finish(); - // Run our game loop for 10 frames for _ in 0..10 { stages.run(&mut world); diff --git a/framework_crates/bones_ecs/src/resources.rs b/framework_crates/bones_ecs/src/resources.rs index 175f9030b0..c76f82dc2c 100644 --- a/framework_crates/bones_ecs/src/resources.rs +++ b/framework_crates/bones_ecs/src/resources.rs @@ -201,9 +201,40 @@ impl UntypedResources { ) } + /// Iterates through owned and shared resource cells removing them if the predicate returns `false`. + /// + /// See also: [`Self::retain_owned_resources`]. + pub fn retain_resources(&self, mut predicate: F) + where + F: FnMut(SchemaId) -> bool, + { + for (schema_id, resource_cell) in self.resources.read_only_view().iter() { + let is_retained = predicate(*schema_id); + if !is_retained { + resource_cell.remove(); + } + } + } + + /// Iterates through resource cells removing them if the predicate returns `false`. + /// + /// Doesn't include shared resources. See also: [`Self::retain_resources`]. + pub fn retain_owned_resources(&self, mut predicate: F) + where + F: FnMut(SchemaId) -> bool, + { + for (schema_id, resource_cell) in self.resources.read_only_view().iter() { + let is_shared = self.shared_resources.contains_key(schema_id); + let is_retained = predicate(*schema_id); + if !is_shared && !is_retained { + resource_cell.remove(); + } + } + } + /// Removes all resourcse that are not shared resources. - pub fn clear_owned_resources(&mut self) { - for (schema_id, resource_cell) in self.resources.iter_mut() { + pub fn clear_owned_resources(&self) { + for (schema_id, resource_cell) in self.resources.read_only_view().iter() { let is_shared = self.shared_resources.contains_key(schema_id); if !is_shared { resource_cell.remove(); @@ -297,8 +328,8 @@ impl Resources { } /// Removes all resources that are not shared. Shared resources are preserved. - pub fn clear_owned_resources(&mut self) { - self.untyped.clear_owned_resources(); + pub fn clear_owned_resources(&self) { + self.untyped().clear_owned_resources(); } } @@ -547,7 +578,7 @@ impl UntypedResourceSet { /// /// If `remove_empty` is set, if resource cell is empty, it will remove the /// resource from cell on world. - pub fn insert_on_world(&self, world: &mut World, remove_empty: bool) { + pub fn insert_on_world(&self, world: &World, remove_empty: bool) { for resource in self.resources.iter() { let resource_cell = world.resources.untyped().get_cell(resource.schema()); @@ -604,7 +635,7 @@ mod test { #[test] fn resources_clear_owned_values() { - let mut r = Resources::new(); + let r = Resources::new(); // insert A as non-shared resource r.insert(A(String::from("foo"))); diff --git a/framework_crates/bones_ecs/src/stage.rs b/framework_crates/bones_ecs/src/stage.rs index f5a4db2c86..a87e7102ea 100644 --- a/framework_crates/bones_ecs/src/stage.rs +++ b/framework_crates/bones_ecs/src/stage.rs @@ -1,93 +1,44 @@ //! Implementation of stage abstraction for running collections of systems over a [`World`]. use std::collections::VecDeque; +use std::marker::PhantomData; use crate::prelude::*; -/// Resource that is automatically added to the world while a system stage is being run -/// that specifies the unique ID of the stage that being run. -/// -/// If the stage is `Ulid(0)`, the default ID, then that means the startup stage is being run. -#[derive(Deref, DerefMut, Clone, Copy, HasSchema, Default)] -pub struct CurrentSystemStage(pub Ulid); - -/// Builder for [`SystemStages`]. It is immutable once created, -pub struct SystemStagesBuilder { +/// An ordered collection of [`SystemStage`]s. +pub struct SystemStages { /// The stages in the collection, in the order that they will be run. stages: Vec>, - /// The systems that should run at startup. - /// They will be executed next step based on if [`SessionStarted`] resource in world says session has not started, or if resource does not exist. - startup_systems: Vec>, - - /// Resources installed during session plugin installs. Copied to world as first step on startup of stages' execution. - startup_resources: UntypedResourceSet, - - /// Systems that are continously run until they succeed(return Some). These run before all stages. Uses Option to allow for easy usage of `?`. - single_success_systems: Vec>>, } -impl Default for SystemStagesBuilder { +impl Default for SystemStages { fn default() -> Self { Self::with_core_stages() } } -impl SystemStagesBuilder { - /// Create a [`SystemStagesBuilder`] for [`SystemStages`] collection, initialized with a stage for each [`CoreStage`]. +impl SystemStages { + /// Create a [`SystemStages`] collection, initialized with a stage for each [`CoreStage`]. pub fn with_core_stages() -> Self { Self { stages: vec![ + Box::new( + StartupSystemStage::::new( + CoreStage::StartupResource, + ), + ), + Box::new(StartupSystemStage::::new( + CoreStage::Startup, + )), Box::new(SimpleSystemStage::new(CoreStage::First)), Box::new(SimpleSystemStage::new(CoreStage::PreUpdate)), Box::new(SimpleSystemStage::new(CoreStage::Update)), Box::new(SimpleSystemStage::new(CoreStage::PostUpdate)), Box::new(SimpleSystemStage::new(CoreStage::Last)), ], - startup_resources: default(), - startup_systems: default(), - single_success_systems: Vec::new(), - } - } - - /// Finish building and convert to [`SystemStages`] - pub fn finish(self) -> SystemStages { - SystemStages { - stages: self.stages, - startup_systems: self.startup_systems, - startup_resources: self.startup_resources, - single_success_systems: self.single_success_systems, } } - /// Create [`SystemStagesBuilder`] by taking existing [`SystemStages`]. - pub fn from_stages(stages: SystemStages) -> Self { - Self { - stages: stages.stages, - startup_systems: stages.startup_systems, - startup_resources: stages.startup_resources, - single_success_systems: stages.single_success_systems, - } - } - - /// Add a system that will run only once, before all of the other non-startup systems. - /// If wish to reset session and run again, can modify [`SessionStarted`] resource in world. - pub fn add_startup_system(&mut self, system: S) -> &mut Self - where - S: IntoSystem>, - { - self.startup_systems.push(system.system()); - self - } - - /// Add a system that will run each frame until it succeeds (returns Some). Runs before all stages. Uses Option to allow for easy usage of `?`. - pub fn add_single_success_system(&mut self, system: S) -> &mut Self - where - S: IntoSystem, Sys = StaticSystem<(), Option<()>>>, - { - self.single_success_systems.push(system.system()); - self - } - /// Add a [`System`] to the stage with the given label. pub fn add_system_to_stage(&mut self, label: impl StageLabel, system: S) -> &mut Self where @@ -146,221 +97,112 @@ impl SystemStagesBuilder { self } - /// Insert a startup resource. On stage / session startup (first step), will be inserted into [`World`]. - /// - /// If already exists, will be overwritten. - pub fn insert_startup_resource(&mut self, resource: T) { - self.startup_resources.insert_resource(resource); - } - - /// Init startup resource with default, and return mutable ref for modification. - /// If already exists, returns mutable ref to existing resource. - pub fn init_startup_resource(&mut self) -> RefMut<'_, T> { - self.startup_resources.init_resource::() - } - - /// Get mutable reference to startup resource if found. - #[track_caller] - pub fn startup_resource_mut(&self) -> Option> { - self.startup_resources.resource_mut() - } -} - -/// An ordered collection of [`SystemStage`]s. -pub struct SystemStages { - /// The stages in the collection, in the order that they will be run. - stages: Vec>, - - /// The systems that should run at startup. - /// They will be executed next step based on if [`SessionStarted`] resource in world says session has not started, or if resource does not exist. - startup_systems: Vec>, - - /// Resources installed during session plugin installs. Copied to world as first step on startup of stages' execution. - startup_resources: UntypedResourceSet, - - /// Systems that are continously run until they succeed(return Some). These run before all stages. Uses Option to allow for easy usage of `?`. - single_success_systems: Vec>>, -} - -impl std::fmt::Debug for SystemStages { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("SystemStages") - // TODO: Add list of stages to the debug render for `SystemStages`. - // We can at least list the names of each stage for `SystemStages` debug - // implementation. - .finish() - } -} - -impl Default for SystemStages { - fn default() -> Self { - SystemStagesBuilder::default().finish() - } -} - -impl SystemStages { - /// Create builder for construction of [`SystemStages`]. - pub fn builder() -> SystemStagesBuilder { - SystemStagesBuilder::default() - } - /// Execute the systems on the given `world`. pub fn run(&mut self, world: &mut World) { - // If we haven't run startup systems and setup resources yet, do so - self.handle_startup(world); - - // Run single success systems - for (index, system) in self.single_success_systems.iter_mut().enumerate() { - let should_run = !Self::has_single_success_system_succeeded(index, world); - - if should_run && system.run(world, ()).is_some() { - Self::mark_single_success_system_succeeded(index, world); - } - } - - // Run each stage for stage in &mut self.stages { - // Set the current stage resource - world.insert_resource(CurrentSystemStage(stage.id())); - - // Run the stage stage.run(world); } - // Cleanup killed entities world.maintain(); - - // Remove the current system stage resource - world.resources.remove::(); } - /// If [`SessionStarted`] resource indicates have not yet started, - /// perform startup tasks (insert startup resources, run startup systems). - /// - /// For advanced use cases in which want to only insert startup resources, or run startup systems and split this - /// behavior, see [`SystemStages::handle_startup_systems`] and `[SystemStages::handle_startup_resources`]. - /// - /// - /// While this is used internally by [`SystemStages::run`], this is also used - /// for resetting world. This allows world to immediately startup and re-initialize after reset. - /// - /// # Panics - /// - /// May panic if resources are borrowed, should not borrow resources when calling. - pub fn handle_startup(&mut self, world: &mut World) { - self.handle_startup_resources(world); - self.handle_startup_systems(world); + /// Add a system to the [`CoreStage::Startup`] stage which specially runs + /// only once, before all other core stages. Inserting [`CoreStageStartupSystemsRan`] + /// into the world prevents systems in the startup stage from running. + #[deprecated = "use `.add_system_to_stage(CoreStage::Startup, your_system)` instead"] + pub fn add_startup_system(&mut self, system: S) -> &mut Self + where + S: IntoSystem>, + { + self.add_system_to_stage(CoreStage::Startup, system.system()); + self } - /// If [`SessionStarted`] resource indicates startup resources have not yet been inserted, will do so and update `SessionStarted`. + /// Convenience method. Adds a system that will run each frame until it succeeds (returns Some). + /// Uses Option to allow for easy usage of `?`. System is added to + /// [`CoreStage::First`]. /// - /// This function contains only half of stage's startup behavior, see [`SystemStages::handle_startup`] if not intending to split - /// resource insertion from startup systems (Splitting these is more for advanced special cases). - /// - /// # Panics - /// - /// May panic if resources are borrowed, should not borrow resources when calling. - pub fn handle_startup_resources(&mut self, world: &mut World) { - let (mut resources_inserted, systems_run) = match world.get_resource_mut::() - { - Some(session_started) => ( - session_started.startup_resources_inserted, - session_started.startup_systems_executed, - ), - None => (false, false), - }; - - if !resources_inserted { - self.insert_startup_resources(world); - resources_inserted = true; + /// See also the wrapper function used inside [`single_success_system`]. + #[deprecated = "use `.add_system_to_stage(First, single_success_system(your_system)"] + pub fn add_single_success_system(&mut self, system: S) -> &mut Self + where + S: IntoSystem, Sys = StaticSystem<(), Option<()>>>, + { + self.add_system_to_stage(First, single_success_system(system)); + self + } - world.insert_resource(SessionStarted { - startup_systems_executed: systems_run, - startup_resources_inserted: resources_inserted, - }); - } + /// Adds a system to [`CoreStage::Startup`] that inserts the default value + /// of a resource into the [`World`] if it doesn't already exist. + pub fn init_startup_resource(&mut self) -> &mut Self { + self.add_system_to_stage(StartupResource, |world: &World| { + world.init_resource::(); + }); + self } - /// If [`SessionStarted`] resource indicates startup systems have not yet been executed, will do so and update `SessionStarted`. - /// - /// This function contains only half of stage's startup behavior, see [`SystemStages::handle_startup`] if not intending to split - /// startup system execution from startup resource insertion (Splitting these is more for advanced special cases). - /// - /// # Panics + /// Adds a system to [`CoreStage::StartupResource`] that inserts the given + /// resource into the [`World`] if it doesn't already exist. The resource + /// type is required to have a [`HasSchema`] clone implementation to + /// re-insert the resource into the world after the first startup. /// - /// May panic if resources are borrowed, should not borrow resources when calling. - pub fn handle_startup_systems(&mut self, world: &mut World) { - let (resources_inserted, mut systems_run) = match world.get_resource_mut::() - { - Some(session_started) => ( - session_started.startup_resources_inserted, - session_started.startup_systems_executed, - ), - None => (false, false), - }; - - if !systems_run { - // Set the current stage resource - world.insert_resource(CurrentSystemStage(Ulid(0))); - - // For each startup system - for system in &mut self.startup_systems { - // Run the system - system.run(world, ()); + /// # Clone Requirement Details + /// If the type being added implements [`HasSchema`] with the derive like + /// so: + /// ```ignore + /// #[derive(HasSchema, Default)] + /// #[schema(no_clone)] // This bypasses HasSchema clone requirements. + /// struct ResourceBeingInserted; + /// ``` + /// Then the system being added to the [`StartupResource`] system stage will + /// panic when inserting a second time. The startup resource stage only runs + /// once so it is therefore safe to use a type like the example above as + /// long as you don't plan on removing from the [`World`] the + /// [`StartupSystemStage`] tag which in this case is + /// [`CoreStageStartupResourceSystemsRan`]. + pub fn add_startup_resource(&mut self, resource: R) -> &mut Self { + let mut once = Some(SchemaBox::new(resource)); + + self.add_system_to_stage(StartupResource, move |world: &World| { + if !world.resources.contains::() { + let schema_box = if R::schema().clone_fn.is_some() { + once.clone() + } else { + once.take() + }; + + if let Some(schema_box) = schema_box { + world + .resources + .untyped() + .get(R::schema()) + .insert(schema_box) + .unwrap(); + } else { + panic!( + "The schema for this type does not allow cloning it.\nSchema: {:#?}; \ + You may need to add the resource to the `ResetWorld::reset_resources` \ + to avoid cloning on startup.", + R::schema() + ) + }; } - systems_run = true; - - world.resources.remove::(); - world.insert_resource(SessionStarted { - startup_systems_executed: systems_run, - startup_resources_inserted: resources_inserted, - }); - } - } - - /// Check if single success system is marked as succeeded in [`SingleSuccessSystems`] [`Resource`]. - fn has_single_success_system_succeeded(system_index: usize, world: &World) -> bool { - if let Some(system_success) = world.get_resource::() { - return system_success.has_system_succeeded(system_index); - } - - false - } - - /// Mark a single success system as succeeded in [`SingleSuccessSystems`] [`Resource`]. - fn mark_single_success_system_succeeded(system_index: usize, world: &mut World) { - if let Some(mut system_succes) = world.get_resource_mut::() { - system_succes.set_system_completed(system_index); - return; - } - - // Resource does not exist - must initialize it - world - .init_resource::() - .set_system_completed(system_index); + }); + self } +} - /// Insert the startup resources that [`SystemStages`] and session were built with into [`World`]. - /// - /// This will update [`SessionStarted`] resource to flag that startup resources are inserted. - fn insert_startup_resources(&self, world: &mut World) { - for resource in self.startup_resources.resources().iter() { - // Deep copy startup resource and insert into world. - let resource_copy = resource.clone_data().unwrap(); - let resource_cell = world.resources.untyped().get_cell(resource.schema()); - let prev_val = resource_cell.insert(resource_copy).unwrap(); - - // Warn on already existing resource - if prev_val.is_some() { - let schema_name = resource.schema().full_name; - tracing::warn!("SystemStages` attempted to inserted resource {schema_name} on startup that already exists in world - startup resource not inserted. - When building new session, startup resources should be initialized on `SessionBuilder`."); - } - } +impl std::fmt::Debug for SystemStages { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("SystemStages") + // TODO: Add list of stages to the debug render for `SystemStages`. + // We can at least list the names of each stage for `SystemStages` debug + // implementation. + .finish() } } -/// Trait for system stages. A stage is a +/// Trait for system stages. pub trait SystemStage: Sync + Send { /// The unique identifier for the stage. fn id(&self) -> Ulid; @@ -431,6 +273,106 @@ impl SystemStage for SimpleSystemStage { } } +/// Resource for tracking whether or not the game has run the startup systems in +/// [`CoreStage::Startup`]. +#[derive(HasSchema, Clone, Default, Debug)] +pub struct CoreStageStartupSystemsRan; + +/// Resource for tracking whether or not the game has run the startup systems in +/// [`CoreStage::StartupResource`]. +#[derive(HasSchema, Clone, Default, Debug)] +pub struct CoreStageStartupResourceSystemsRan; + +/// [`SystemStage`] for running systems once on startup. +pub struct StartupSystemStage { + /// The unique identifier for the stage. + pub id: Ulid, + /// The human-readable name for the stage, used for error messages when something goes wrong. + pub name: String, + /// The list of systems in the stage. + /// + /// Each system will be run in the order that they are in in this list. + pub systems: Vec>, + _phantom_data: PhantomData, +} +impl StartupSystemStage { + /// Create a new, empty stage, for the given label. + pub fn new(label: L) -> Self { + Self { + id: label.id(), + name: label.name(), + systems: Default::default(), + _phantom_data: PhantomData, + } + } +} +impl SystemStage for StartupSystemStage { + fn id(&self) -> Ulid { + self.id + } + + fn name(&self) -> String { + self.name.clone() + } + + fn run(&mut self, world: &World) { + if world.get_resource_mut::().is_none() { + // Run the systems + for system in &mut self.systems { + system.run(world, ()); + } + world.insert_resource(T::default()); + } + + // Drain the command queue + let queue = world.resources.get_mut::(); + if let Some(mut command_queue) = queue { + for mut system in command_queue.queue.drain(..) { + system.run(world, ()); + } + } + } + + fn add_system(&mut self, system: StaticSystem<(), ()>) { + self.systems.push(system); + } + + fn remove_all_systems(&mut self) { + self.systems.clear(); + } +} + +/// Wrapper system for single success systems. +/// +/// Runs the given system each frame until it succeeds (returns Some). Uses +/// `Option<()>` instead of a `bool` to allow for easy usage of `?`. +/// +/// System success is tracked with the [`SingleSuccessSystems`] resource which +/// can be removed if you wish to let all single success systems run again. +pub fn single_success_system(system: S) -> StaticSystem<(), ()> +where + S: IntoSystem, Sys = StaticSystem<(), Option<()>>>, +{ + use std::sync::atomic::{AtomicU64, Ordering}; + static SINGLE_SUCCESS_SYSTEM_ID_GEN: AtomicU64 = AtomicU64::new(0); + + let id = SINGLE_SUCCESS_SYSTEM_ID_GEN.fetch_add(1, Ordering::SeqCst); + let mut system = system.system(); + (move |world: &World| { + if !world.init_resource::().contains(&id) + && system.run(world, ()).is_some() + { + world.init_resource::().insert(id); + } + }) + .system() +} + +/// Resource tracking which single success systems have completed using a +/// [`HashSet`] of unique [`u64`]s for the succeeded. +#[derive(HasSchema, Clone, Default, Deref, DerefMut)] +pub struct SingleSuccessSystems(pub HashSet); + /// Trait for things that may be used to identify a system stage. pub trait StageLabel { /// Returns the human-readable name of the label, used in error messages. @@ -442,6 +384,10 @@ pub trait StageLabel { /// A [`StageLabel`] for the 5 core stages. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum CoreStage { + /// The startup resource insertion stage + StartupResource, + /// The startup stage + Startup, /// The first stage First, /// The second stage @@ -461,6 +407,8 @@ impl StageLabel for CoreStage { fn id(&self) -> Ulid { match self { + CoreStage::StartupResource => Ulid(2162940536129995539438442124983465554), + CoreStage::Startup => Ulid(2149923225027836290977818907354065512), CoreStage::First => Ulid(2021715391084198804812356024998495966), CoreStage::PreUpdate => Ulid(2021715401330719559452824437611089988), CoreStage::Update => Ulid(2021715410160177201728645950400543948), @@ -525,41 +473,3 @@ impl<'a> SystemParam for Commands<'a> { Commands(state.borrow_mut().unwrap()) } } - -/// Resource tracking if Session has started (startup systems executed and resources inserted). -/// If field is set to false, that operation needs to be handled on next stage run. -/// If resource is not present, assumed to have not started (and will be initialized upon next stage execution). -#[derive(Copy, Clone, HasSchema, Default)] -pub struct SessionStarted { - /// Have startup systems executed? - pub startup_systems_executed: bool, - - /// Have startup resources beeen inserted into [`World`]? - pub startup_resources_inserted: bool, -} - -/// Resource tracking which of single success systems in `Session`'s [`SystemStages`] have completed. -/// Success is tracked to -#[derive(HasSchema, Clone, Default)] -pub struct SingleSuccessSystems { - /// Set of indices of [`SystemStages`]'s single success systems that have succeeded. - pub systems_succeeded: HashSet, -} - -impl SingleSuccessSystems { - /// Reset single success systems completion status. so they run again until success. - #[allow(dead_code)] - pub fn reset(&mut self) { - self.systems_succeeded.clear(); - } - - /// Check if system has completed - pub fn has_system_succeeded(&self, index: usize) -> bool { - self.systems_succeeded.contains(&index) - } - - /// Mark system as completed. - pub fn set_system_completed(&mut self, index: usize) { - self.systems_succeeded.insert(index); - } -} diff --git a/framework_crates/bones_ecs/src/world.rs b/framework_crates/bones_ecs/src/world.rs index 867d3686d4..ebac88d70c 100644 --- a/framework_crates/bones_ecs/src/world.rs +++ b/framework_crates/bones_ecs/src/world.rs @@ -130,7 +130,7 @@ impl World { } /// Initialize a resource of type `T` by inserting it's default value. - pub fn init_resource(&mut self) -> RefMut<'_, R> { + pub fn init_resource(&self) -> RefMut<'_, R> { if unlikely(!self.resources.contains::()) { let value = R::from_world(self); self.resources.insert(value); @@ -138,7 +138,7 @@ impl World { self.resource_mut() } - /// Insert a resource. + /// Insert a resource, replacing it if it already exists. pub fn insert_resource(&self, resource: R) -> Option { self.resources.insert(resource) } @@ -183,6 +183,31 @@ impl World { self.resources.get_mut() } + /// Removes all resources that are not shared. Shared resources are preserved. + pub fn clear_owned_resources(&self) { + self.resources.clear_owned_resources(); + } + + /// Iterates through owned and shared resource cells removing them if the predicate returns `false`. + /// + /// See also: [`Self::retain_owned_resources`]. + pub fn retain_resources(&self, predicate: F) + where + F: FnMut(SchemaId) -> bool, + { + self.resources.untyped().retain_resources(predicate); + } + + /// Iterates through resource cells removing them if the predicate returns `false`. + /// + /// Doesn't include shared resources. See also: [`Self::retain_resources`]. + pub fn retain_owned_resources(&self, predicate: F) + where + F: FnMut(SchemaId) -> bool, + { + self.resources.untyped().retain_owned_resources(predicate); + } + /// Borrow a component store from the world. /// # Panics /// Panics if the component store does not exist in the world. @@ -394,7 +419,7 @@ mod tests { #[test] fn init_resource_does_not_overwrite() { - let mut w = World::default(); + let w = World::default(); w.insert_resource(TestResource(0)); w.init_resource::(); w.insert_resource(TestResource(1)); diff --git a/framework_crates/bones_framework/src/animation.rs b/framework_crates/bones_framework/src/animation.rs index 159b244441..0d58421f41 100644 --- a/framework_crates/bones_framework/src/animation.rs +++ b/framework_crates/bones_framework/src/animation.rs @@ -3,7 +3,7 @@ use crate::prelude::*; /// Install animation utilities into the given [`SystemStages`]. -pub fn animation_plugin(core: &mut SessionBuilder) { +pub fn animation_plugin(core: &mut Session) { core.stages .add_system_to_stage(CoreStage::Last, update_animation_banks) .add_system_to_stage(CoreStage::Last, animate_sprites); diff --git a/framework_crates/bones_framework/src/audio.rs b/framework_crates/bones_framework/src/audio.rs index 447b03fd4b..08069e57b6 100644 --- a/framework_crates/bones_framework/src/audio.rs +++ b/framework_crates/bones_framework/src/audio.rs @@ -20,13 +20,12 @@ pub fn game_plugin(game: &mut Game) { game.insert_shared_resource(AudioManager::default()); game.init_shared_resource::(); - let mut session = SessionBuilder::new(DEFAULT_BONES_AUDIO_SESSION); + let session = game.sessions.create(DEFAULT_BONES_AUDIO_SESSION); // Audio doesn't do any rendering session.visible = false; session .add_system_to_stage(First, _process_audio_events) .add_system_to_stage(Last, _kill_finished_audios); - session.finish_and_add(&mut game.sessions); } /// Holds the handles and the volume to be played for a piece of Audio. diff --git a/framework_crates/bones_framework/src/debug.rs b/framework_crates/bones_framework/src/debug.rs index 33f3c80c8b..facfdfce1d 100644 --- a/framework_crates/bones_framework/src/debug.rs +++ b/framework_crates/bones_framework/src/debug.rs @@ -51,7 +51,7 @@ pub struct FrameTimeWindowState { /// If installed, allows opening egui window with [`FrameTimeWindowState`] in [`EguiCtx`] state /// to get frame time information. -pub fn frame_time_diagnostics_plugin(core: &mut SessionBuilder) { +pub fn frame_time_diagnostics_plugin(core: &mut Session) { core.stages .add_system_to_stage(CoreStage::Last, frame_diagnostic_window); } diff --git a/framework_crates/bones_framework/src/input.rs b/framework_crates/bones_framework/src/input.rs index d87a74837f..6fc7ebbce9 100644 --- a/framework_crates/bones_framework/src/input.rs +++ b/framework_crates/bones_framework/src/input.rs @@ -2,6 +2,7 @@ use bones_lib::ecs::World; use bones_schema::HasSchema; +use std::fmt::Debug; pub mod gamepad; pub mod gilrs; @@ -10,7 +11,7 @@ pub mod mouse; pub mod proto; pub mod window; -/// The state of a button, ether pressed or released. +/// The state of a button, either pressed or released. #[derive(HasSchema, Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] #[repr(u8)] pub enum ButtonState { @@ -31,41 +32,45 @@ impl ButtonState { /// Module prelude. pub mod prelude { pub use super::{gamepad::*, keyboard::*, mouse::*, proto, window::*, ButtonState}; - pub use crate::input::{ - Controls, DenseControl, DenseInput, DenseInputCollector, DenseInputConfig, InputCollector, - }; + pub use crate::input::{Controls, DenseControl, DenseInput, DenseInputConfig, InputCollector}; } -/// Maps raw inputs to game controls and exposes controls for respective player and their control source. -/// -/// [`InputCollector::apply_inputs`] maps raw input to game controls and updates them. -/// -/// [`InputCollector::update_just_pressed`] computes any changes in pressed buttons that may be stored on control. -/// -/// [`InputCollector::advance_frame`] is used to mark that the input has been consumed, and update the prev frame inputs to current, to compute changes next frame. -pub trait InputCollector<'a, Control>: Send + Sync { - /// Update the internal state with new inputs. This must be called every render frame with the - /// input events. This updates which buttons are pressed, but does not compute what buttons were "just_pressed". - /// use [`InputCollector::update_just_pressed`] to do this. +/// Dense input super trait for network replication. +pub trait DenseInput: + bytemuck::Pod + bytemuck::Zeroable + Copy + Clone + PartialEq + Eq + Send + Sync +{ +} +// Automatic implementation for `DenseInput`. +impl DenseInput for T where + T: bytemuck::Pod + bytemuck::Zeroable + Copy + Clone + PartialEq + Eq + Send + Sync +{ +} + +/// Collects local input state for the network client from the [`World`]. +pub trait InputCollector<'a, State>: Send + Sync { + /// Update the internal state with the [`World`]. + /// + /// This should be called every render frame / cpu cycle after input events. fn apply_inputs(&mut self, world: &World); - /// Indicate input for this frame has been consumed. An implementation of [`InputCollector`] that is - /// used with a fixed simulation step may track what keys are currently pressed, and what keys were "just pressed", - /// (changing from previous state). + /// Indicates to the collector that its input has been consumed. /// - /// This saves current inputs as previous frame's inputs, allowing for determining what is "just pressed" next frame. + /// This should be called on frame update when input for this frame has been + /// consumed as opposed to the cpu cycle. In a network session, by the time + /// this is called, the dense input should be sent across the network. fn advance_frame(&mut self); - /// Update which buttons have been "just pressed", when input has changed from last frame and current input state. - /// - /// This does not modify previous frame's input, to do this use [`InputCollector::advance_frame`]. - fn update_just_pressed(&mut self); + /// Get the internally stored input state. + fn get_state(&self) -> State; +} - /// Get control for player. - fn get_control(&self) -> &Control; +/// A trait representing a control type capable of being updated by a [`DenseInput`]. +pub trait DenseControl: Send + Sync + Default { + /// Update this control with a [`DenseInput`]. + fn update_from_dense(&mut self, new_control: &Dense); } -/// Trait that tracks player control state. Provides associated types for other input trait implementations. +/// Trait for getting every player control via index. pub trait Controls<'a, Control> { /// Get control for player. fn get_control(&self, player_idx: usize) -> &Control; @@ -74,67 +79,31 @@ pub trait Controls<'a, Control> { fn get_control_mut(&mut self, player_idx: usize) -> &mut Control; } -use std::fmt::Debug; - -/// Dense input for network replication. -pub trait DenseInput: - bytemuck::Pod + bytemuck::Zeroable + Copy + Clone + PartialEq + Eq + Send + Sync -{ -} - -/// Automatic implementation for `DenseInput`. -impl DenseInput for T where - T: bytemuck::Pod + bytemuck::Zeroable + Copy + Clone + PartialEq + Eq + Send + Sync -{ -} - -/// Define input types used by game for use in networking. -/// -/// As long as types `Controls` and `InputCollector` implement traits [`Controls`] and [`InputCollector`], -/// trait bounds [`DenseControl`] and [`DenseInputCollector`] are automatically implemented. +/// Defines input types for use in networking. #[allow(missing_docs)] pub trait DenseInputConfig<'a> { + /// Current input state for a client. This is what is sent across the + /// network. All input history and other data derived from the state should + /// be stored by the [`Control`](DenseInputConfig::Control). type Dense: DenseInput + Debug + Default; - type Control: DenseControl; - - // Must be HasSchema because expected to be retrieved from `World` as `Resource`. - type Controls: Controls<'a, Self::Control> + HasSchema; - - // InputCollector type params must match that of Controls, so using associated types. - type InputCollector: InputCollector<'a, Self::Control> + Default; -} - -/// Trait allowing for creating and applying [`DenseInput`] from control. -pub trait DenseControl: Send + Sync + Default { - /// Get [`DenseInput`] for control. - fn get_dense_input(&self) -> Dense; - /// Update control from [`DenseInput`]. - fn update_from_dense(&mut self, new_control: &Dense); -} + /// Local configuration for getting [`Dense`](DenseInputConfig::Dense) from + /// the [`World`] to be sent across the network. + type InputCollector: InputCollector<'a, Self::Dense> + Default; -/// Extension of [`InputCollector`] exposing dense control for networking. -/// -/// This trait is automatically implemented for [`InputCollector`]'s such that `Control` -/// implements [`DenseControl`] (i.e. implements dense input) -pub trait DenseInputCollector<'a, Dense, Control>: InputCollector<'a, Control> -where - Dense: DenseInput, - Control: DenseControl, -{ - /// Get dense control - fn get_dense_control(&self) -> Dense; -} + /// The complete input data of a client. Updated with + /// [`Dense`](DenseInputConfig::Dense) when recieved from the network. The + /// [`Controls`](DenseInputConfig::Controls) resource in the [`World`] will + /// contain every client's control after update. + type Control: DenseControl; -/// Provide automatic [`DenseInputCollector`] for [`InputCollector`] when type parameters -/// meet required bounds for networking. -impl<'a, T, Dense, Control> DenseInputCollector<'a, Dense, Control> for T -where - Dense: DenseInput, - Control: DenseControl, - T: InputCollector<'a, Control>, -{ - fn get_dense_control(&self) -> Dense { - self.get_control().get_dense_input() - } + /// The resource that will contain a [`Control`](DenseInputConfig::Control) + /// for every client in a network session. + type Controls: Controls<'a, Self::Control> + // HasSchema because expected to be retrieved from `World` as + // `Resource`. + + HasSchema + // Default because not expected to be initialized when retrieved from + // `World`. + + Default; } diff --git a/framework_crates/bones_framework/src/input/gamepad.rs b/framework_crates/bones_framework/src/input/gamepad.rs index 399ce61405..1cd07ac1a0 100644 --- a/framework_crates/bones_framework/src/input/gamepad.rs +++ b/framework_crates/bones_framework/src/input/gamepad.rs @@ -64,7 +64,19 @@ pub struct GamepadButtonEvent { /// A specific button on a gamepad. #[allow(missing_docs)] -#[derive(HasSchema, Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] +#[derive( + HasSchema, + Clone, + Copy, + Debug, + Default, + PartialEq, + Eq, + Hash, + serde::Deserialize, + serde::Serialize, +)] +#[schema(hash, eq)] #[repr(C, u8)] pub enum GamepadButton { #[default] @@ -135,8 +147,10 @@ pub struct GamepadAxisEvent { } /// A specific gamepad axis that may have changed. -#[derive(HasSchema, Clone, Copy, Debug, PartialEq, Eq, Hash)] -#[schema(no_default)] +#[derive( + HasSchema, Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Deserialize, serde::Serialize, +)] +#[schema(no_default, hash, eq)] #[allow(missing_docs)] #[repr(C, u8)] pub enum GamepadAxis { diff --git a/framework_crates/bones_framework/src/input/gilrs.rs b/framework_crates/bones_framework/src/input/gilrs.rs index 5e8df1f4ad..12d66afadb 100644 --- a/framework_crates/bones_framework/src/input/gilrs.rs +++ b/framework_crates/bones_framework/src/input/gilrs.rs @@ -9,7 +9,7 @@ use send_wrapper::SendWrapper; /// Lazy-initialized GilrsContext #[cfg(not(target_arch = "wasm32"))] -static GILRS_CONTEXT: Lazy>> = Lazy::new(|| { +pub static GILRS_CONTEXT: Lazy>> = Lazy::new(|| { Arc::new(Mutex::new( GilrsContext::new().expect("Failed to initialize GilrsContext"), )) @@ -20,7 +20,7 @@ static GILRS_CONTEXT: Lazy>> = Lazy::new(|| { // accessed from one thread. /// Lazy-initialized GilrsContext #[cfg(target_arch = "wasm32")] -static GILRS_CONTEXT: Lazy>>> = Lazy::new(|| { +pub static GILRS_CONTEXT: Lazy>>> = Lazy::new(|| { Arc::new(Mutex::new(SendWrapper::new( GilrsContext::new().expect("Failed to initialize GilrsContext"), ))) @@ -39,7 +39,6 @@ pub fn process_gamepad_events() -> GamepadInputs { let gamepad = usize::from(gilrs_event.id) as u32; match gilrs_event.event { EventType::Connected => { - let _pad = gilrs.gamepad(gilrs_event.id); gamepad_inputs.gamepad_events.push(GamepadEvent::Connection( GamepadConnectionEvent { gamepad, diff --git a/framework_crates/bones_framework/src/input/keyboard.rs b/framework_crates/bones_framework/src/input/keyboard.rs index 6e055f193e..246644d244 100644 --- a/framework_crates/bones_framework/src/input/keyboard.rs +++ b/framework_crates/bones_framework/src/input/keyboard.rs @@ -23,7 +23,18 @@ pub struct KeyboardEvent { } /// The key code of a keyboard key. -#[derive(HasSchema, Clone, Copy, Default, Debug, PartialEq, Eq, Hash)] +#[derive( + HasSchema, + Clone, + Copy, + Default, + Debug, + PartialEq, + Eq, + Hash, + serde::Deserialize, + serde::Serialize, +)] #[repr(u32)] pub enum KeyCode { #[default] diff --git a/framework_crates/bones_framework/src/input/mouse.rs b/framework_crates/bones_framework/src/input/mouse.rs index 76c99e9c6b..b2837cf7a8 100644 --- a/framework_crates/bones_framework/src/input/mouse.rs +++ b/framework_crates/bones_framework/src/input/mouse.rs @@ -42,7 +42,7 @@ pub struct MouseButtonEvent { } /// A button on the mouse. -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(HasSchema, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] #[repr(u8)] pub enum MouseButton { #[default] diff --git a/framework_crates/bones_framework/src/lib.rs b/framework_crates/bones_framework/src/lib.rs index f2629410ed..88adc57a33 100644 --- a/framework_crates/bones_framework/src/lib.rs +++ b/framework_crates/bones_framework/src/lib.rs @@ -91,7 +91,7 @@ pub mod external { /// Default plugins for bones framework sessions. pub struct DefaultSessionPlugin; impl lib::SessionPlugin for DefaultSessionPlugin { - fn install(self, session: &mut lib::SessionBuilder) { + fn install(self, session: &mut lib::Session) { session .install_plugin(animation::animation_plugin) .install_plugin(render::render_plugin); diff --git a/framework_crates/bones_framework/src/networking.rs b/framework_crates/bones_framework/src/networking.rs index b7953687b1..e00c721c37 100644 --- a/framework_crates/bones_framework/src/networking.rs +++ b/framework_crates/bones_framework/src/networking.rs @@ -1,7 +1,7 @@ #![doc = include_str!("./networking.md")] use self::{input::NetworkControls, socket::Socket}; -use crate::input::{DenseControl, DenseInput, DenseInputConfig}; +use crate::input::{DenseInput, DenseInputConfig}; use crate::networking::online::OnlineMatchmakerResponse; pub use crate::networking::random::RngGenerator; use crate::prelude::*; @@ -655,10 +655,9 @@ where // Collect inputs and update controls self.input_collector.apply_inputs(world); - self.input_collector.update_just_pressed(); // save local players dense input for use with ggrs - self.last_player_input = self.input_collector.get_control().get_dense_input(); + self.last_player_input = self.input_collector.get_state(); #[cfg(feature = "net-debug")] // Current frame before we start network update loop @@ -788,6 +787,11 @@ where inputs: network_inputs, } => { // Input has been consumed, signal that we are in new input frame + + // TODO: only call `InputCollector::advance_frame` + // when we are in a "prediction frame", not a "rollback catchup frame". + // If there are requests to load a snapshot and re-run frames the + // input collector's input has already been defined for those frames. self.input_collector.advance_frame(); // Fetch the PlayerNetworkStats for each remote player, guaranteeing each one is inserted into the index matching its handle @@ -838,12 +842,12 @@ where { world - .resource_mut::