From 6bee45d81db183924619775ad7e3cbbd1de389d7 Mon Sep 17 00:00:00 2001 From: Zicklag Date: Fri, 1 Sep 2023 19:30:58 +0000 Subject: [PATCH] feat!: implement widget systems and system input parameters. (#193) - This also removes the `SystemResult` and `EcsError` types from `bones_ecs`. We don't really need systems to return results, and `EcsError` was nearly unused. --- demos/assets_minimal/src/main.rs | 2 +- demos/features/src/main.rs | 32 ++- demos/hello_world/src/main.rs | 4 +- .../bones_ecs/examples/pos_vel.rs | 10 +- framework_crates/bones_ecs/src/components.rs | 102 ++++---- framework_crates/bones_ecs/src/error.rs | 36 --- framework_crates/bones_ecs/src/lib.rs | 6 +- framework_crates/bones_ecs/src/stage.rs | 51 ++-- framework_crates/bones_ecs/src/system.rs | 222 +++++++++--------- framework_crates/bones_ecs/src/world.rs | 45 ++-- .../bones_framework/src/localization.rs | 5 +- .../bones_framework/src/params.rs | 2 +- .../bones_framework/src/render/ui.rs | 40 ++++ framework_crates/bones_lib/src/lib.rs | 7 +- 14 files changed, 296 insertions(+), 268 deletions(-) delete mode 100644 framework_crates/bones_ecs/src/error.rs diff --git a/demos/assets_minimal/src/main.rs b/demos/assets_minimal/src/main.rs index 095fc1986f..5b09e0f9b2 100644 --- a/demos/assets_minimal/src/main.rs +++ b/demos/assets_minimal/src/main.rs @@ -42,7 +42,7 @@ fn main() { /// System to render the home menu. fn menu_system( - egui_ctx: Res, + egui_ctx: Egui, // We can access our root asset by using the Root parameter. meta: Root, ) { diff --git a/demos/features/src/main.rs b/demos/features/src/main.rs index 40991242d9..15e9ca50b7 100644 --- a/demos/features/src/main.rs +++ b/demos/features/src/main.rs @@ -136,17 +136,16 @@ fn menu_startup( /// Our main menu system. fn menu_system( meta: Root, - egui_ctx: ResMut, + ctx: Egui, mut sessions: ResMut, mut session_options: ResMut, - egui_textures: Res, // Get the localization field from our `GameMeta` localization: Localization, ) { // Render the menu. egui::CentralPanel::default() .frame(egui::Frame::none()) - .show(&egui_ctx, |ui| { + .show(&ctx, |ui| { BorderedFrame::new(&meta.menu_border).show(ui, |ui| { ui.vertical_centered(|ui| { ui.add_space(20.0); @@ -207,9 +206,11 @@ fn menu_system( ui.add_space(30.0); - // When using a bones image in egui, we have to get it's corresponding egui texture - // from the egui textures resource. - ui.image(egui_textures.get(meta.menu_image), [100., 100.]); + // We can use the `widget()` method on the `Egui` to conveniently run bones + // systems that can modify the `egui::Ui` and return an `egui::Response`. + // + // This makes it easier to compose widgets that have differing access to the bones world. + ctx.widget(demo_widget, ui); ui.add_space(30.0); }); @@ -378,14 +379,14 @@ fn path2d_demo_startup( /// Simple UI system that shows a button at the bottom of the screen to delete the current session /// and go back to the main menu. fn back_to_menu_ui( - egui_ctx: ResMut, + ctx: Egui, mut sessions: ResMut, mut session_options: ResMut, localization: Localization, ) { egui::CentralPanel::default() .frame(egui::Frame::none()) - .show(&egui_ctx, |ui| { + .show(&ctx, |ui| { ui.with_layout(egui::Layout::bottom_up(egui::Align::Center), |ui| { ui.add_space(20.0); if ui.button(localization.get("back-to-menu")).clicked() { @@ -395,3 +396,18 @@ fn back_to_menu_ui( }); }); } + +/// This is an example widget system. +fn demo_widget( + // Widget systems must have an `In<&mut egui::Ui>` parameter as their first argument. + mut ui: In<&mut egui::Ui>, + // They can have any normal bones system parameters + meta: Root, + egui_textures: Res, + // And they may return an `egui::Response` or any other value. +) -> egui::Response { + ui.label("Demo Widget"); + // When using a bones image in egui, we have to get it's corresponding egui texture + // from the egui textures resource. + ui.image(egui_textures.get(meta.menu_image), [100., 100.]) +} diff --git a/demos/hello_world/src/main.rs b/demos/hello_world/src/main.rs index cfd5b327e0..4eb8db8099 100644 --- a/demos/hello_world/src/main.rs +++ b/demos/hello_world/src/main.rs @@ -18,8 +18,8 @@ fn main() { } /// System to render the home menu. -fn menu_system(egui_ctx: Res) { - egui::CentralPanel::default().show(&egui_ctx, |ui| { +fn menu_system(ctx: Egui) { + egui::CentralPanel::default().show(&ctx, |ui| { ui.label("Hello World"); }); } diff --git a/framework_crates/bones_ecs/examples/pos_vel.rs b/framework_crates/bones_ecs/examples/pos_vel.rs index 32cb232317..e7c6cc0255 100644 --- a/framework_crates/bones_ecs/examples/pos_vel.rs +++ b/framework_crates/bones_ecs/examples/pos_vel.rs @@ -22,14 +22,12 @@ fn main() { // Initialize an empty world let mut world = World::new(); - // Run our setup system once to spawn our entities - world.run_system(setup_system).ok(); - // Create a SystemStages to store the systems that we will run more than once. let mut stages = SystemStages::with_core_stages(); - // Add our systems to the dispatcher + // Add our systems to the system stages stages + .add_startup_system(startup_system) .add_system_to_stage(CoreStage::Update, pos_vel_system) .add_system_to_stage(CoreStage::PostUpdate, print_system); @@ -38,12 +36,12 @@ fn main() { // Run our game loop for 10 frames for _ in 0..10 { - stages.run(&mut world).unwrap(); + stages.run(&mut world); } } /// Setup system that spawns two entities with a Pos and a Vel component. -fn setup_system( +fn startup_system( mut entities: ResMut, mut positions: CompMut, mut velocities: CompMut, diff --git a/framework_crates/bones_ecs/src/components.rs b/framework_crates/bones_ecs/src/components.rs index e6427cd667..75e482d8de 100644 --- a/framework_crates/bones_ecs/src/components.rs +++ b/framework_crates/bones_ecs/src/components.rs @@ -23,6 +23,17 @@ pub struct ComponentStores { pub(crate) components: HashMap>>, } +/// An error returned when trying to access an uninitialized component. +#[derive(Debug)] +pub struct NotInitialized; + +impl std::fmt::Display for NotInitialized { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Component not initialized") + } +} +impl std::error::Error for NotInitialized {} + // SOUND: all of the functions for ComponentStores requires that the types stored implement Sync + // Send. unsafe impl Sync for ComponentStores {} @@ -52,7 +63,7 @@ impl ComponentStores { } /// Get the components of a certain type - pub fn get_cell(&self) -> Result, EcsError> { + pub fn get_cell(&self) -> Result, NotInitialized> { let untyped = self.get_cell_by_schema_id(T::schema().id())?; // Safe: We know the schema matches, and `ComponentStore` is repr(transparent) over @@ -68,13 +79,9 @@ impl ComponentStores { /// Borrow a component store. /// # Errors /// Errors if the component store has not been initialized yet. - pub fn get(&self) -> Result>, EcsError> { + pub fn get(&self) -> Result>, NotInitialized> { let id = T::schema().id(); - let atomicref = self - .components - .get(&id) - .ok_or(EcsError::NotInitialized)? - .borrow(); + let atomicref = self.components.get(&id).ok_or(NotInitialized)?.borrow(); // SOUND: ComponentStore is repr(transparent) over UntypedComponent store. let atomicref = Ref::map(atomicref, |x| unsafe { @@ -87,13 +94,9 @@ impl ComponentStores { /// Borrow a component store. /// # Errors /// Errors if the component store has not been initialized yet. - pub fn get_mut(&self) -> Result>, EcsError> { + pub fn get_mut(&self) -> Result>, NotInitialized> { let id = T::schema().id(); - let atomicref = self - .components - .get(&id) - .ok_or(EcsError::NotInitialized)? - .borrow_mut(); + let atomicref = self.components.get(&id).ok_or(NotInitialized)?.borrow_mut(); // SOUND: ComponentStore is repr(transparent) over UntypedComponent store. let atomicref = RefMut::map(atomicref, |x| unsafe { @@ -107,11 +110,8 @@ impl ComponentStores { pub fn get_cell_by_schema_id( &self, id: SchemaId, - ) -> Result>, EcsError> { - self.components - .get(&id) - .cloned() - .ok_or(EcsError::NotInitialized) + ) -> Result>, NotInitialized> { + self.components.get(&id).cloned().ok_or(NotInitialized) } } @@ -125,44 +125,42 @@ mod test { #[test] fn borrow_many_mut() { - World::new() - .run_system( - |mut entities: ResMut, mut my_datas: CompMut| { - let ent1 = entities.create(); - let ent2 = entities.create(); - - my_datas.insert(ent1, MyData(7)); - my_datas.insert(ent2, MyData(8)); - - { - let [data2, data1] = my_datas.get_many_mut([ent2, ent1]).unwrap_many(); - - data1.0 = 0; - data2.0 = 1; - } - - assert_eq!(my_datas.get(ent1).unwrap().0, 0); - assert_eq!(my_datas.get(ent2).unwrap().0, 1); - }, - ) - .unwrap(); + World::new().run_system( + |mut entities: ResMut, mut my_datas: CompMut| { + let ent1 = entities.create(); + let ent2 = entities.create(); + + my_datas.insert(ent1, MyData(7)); + my_datas.insert(ent2, MyData(8)); + + { + let [data2, data1] = my_datas.get_many_mut([ent2, ent1]).unwrap_many(); + + data1.0 = 0; + data2.0 = 1; + } + + assert_eq!(my_datas.get(ent1).unwrap().0, 0); + assert_eq!(my_datas.get(ent2).unwrap().0, 1); + }, + (), + ); } #[test] #[should_panic = "must be unique"] fn borrow_many_overlapping_mut() { - World::new() - .run_system( - |mut entities: ResMut, mut my_datas: CompMut| { - let ent1 = entities.create(); - let ent2 = entities.create(); - - my_datas.insert(ent1, MyData(1)); - my_datas.insert(ent2, MyData(2)); - - my_datas.get_many_mut([ent1, ent2, ent1]).unwrap_many(); - }, - ) - .unwrap(); + World::new().run_system( + |mut entities: ResMut, mut my_datas: CompMut| { + let ent1 = entities.create(); + let ent2 = entities.create(); + + my_datas.insert(ent1, MyData(1)); + my_datas.insert(ent2, MyData(2)); + + my_datas.get_many_mut([ent1, ent2, ent1]).unwrap_many(); + }, + (), + ) } } diff --git a/framework_crates/bones_ecs/src/error.rs b/framework_crates/bones_ecs/src/error.rs deleted file mode 100644 index d8382a76d1..0000000000 --- a/framework_crates/bones_ecs/src/error.rs +++ /dev/null @@ -1,36 +0,0 @@ -use std::error::Error; - -/// The types of errors used throughout the ECS. -// TODO: Re-evaluate `EcsError` variants. -// Some these error variants may not be used anymore. Also, I think most of the times -// that we return `EcsError`, there is only one possible error that could occur for that function. -// If that is the case in all situations, we should consider breaking each error type into it's -// own struct, so that we aren't returning an enum with a bunch of errors that will never happen -// for each function call. -#[derive(Debug, thiserror::Error)] -pub enum EcsError { - /// A resource was not initialized in the [`World`][crate::World] but the - /// [`System`][crate::system::System] tries to access it. - #[error("Resource or component not initialized")] - NotInitialized, - /// The requested resource is already borrowed. - /// - /// This error is created if the `System` tries to read a resource that has already been mutably - /// borrowed. It can also happen when trying to mutably borrow a resource that is already being - /// read. - /// - /// This error should not occur during normal use, as the dispatchers can recover easily. - #[error("Resource or component already borrowed")] - AlreadyBorrowed, - /// The execution of the dispatcher failed and returned one or more errors. - #[error("Dispatcher failed with one or more errors: {0:?}")] - DispatcherExecutionFailed(Vec), - /// This variant is for user-defined errors. - /// - /// To create an error of this type easily, use the `system_error!` macro. - #[error("System errored: {0}")] - SystemError(Box), -} - -/// The result of a `System`'s execution. -pub type SystemResult = anyhow::Result; diff --git a/framework_crates/bones_ecs/src/lib.rs b/framework_crates/bones_ecs/src/lib.rs index 0e53d3b566..51ad16a7d1 100644 --- a/framework_crates/bones_ecs/src/lib.rs +++ b/framework_crates/bones_ecs/src/lib.rs @@ -21,9 +21,6 @@ pub mod system; pub use bones_schema as schema; pub use bones_utils as utils; -mod error; -pub use error::EcsError; - mod world; pub use world::{FromWorld, World}; @@ -37,7 +34,6 @@ pub mod prelude { bitset::*, components::*, entities::*, - error::*, resources::*, stage::{CoreStage::*, *}, system::*, @@ -96,7 +92,7 @@ mod test { let e = entities.create(); comps.insert(e, default()); }, + (), ) - .unwrap(); } } diff --git a/framework_crates/bones_ecs/src/stage.rs b/framework_crates/bones_ecs/src/stage.rs index ce228efeef..a688f99020 100644 --- a/framework_crates/bones_ecs/src/stage.rs +++ b/framework_crates/bones_ecs/src/stage.rs @@ -11,7 +11,7 @@ pub struct SystemStages { /// Whether or not the startup systems have been run yet. pub has_started: bool, /// The systems that should run at startup. - pub startup_systems: Vec, + pub startup_systems: Vec>, } impl std::fmt::Debug for SystemStages { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -43,20 +43,18 @@ impl SystemStages { /// /// > **Note:** You must call [`initialize_systems()`][Self::initialize_systems] once before /// > calling `run()` one or more times. - pub fn run(&mut self, world: &mut World) -> SystemResult { + pub fn run(&mut self, world: &mut World) { if !self.has_started { for system in &mut self.startup_systems { system.initialize(world); - system.run(world).unwrap(); + system.run(world, ()); } self.has_started = true; } for stage in &mut self.stages { - stage.run(world)?; + stage.run(world); } - - Ok(()) } /// Create a [`SystemStages`] collection, initialized with a stage for each [`CoreStage`]. @@ -75,17 +73,19 @@ impl SystemStages { } /// Add a system that will run only once, before all of the other non-startup systems. - pub fn add_startup_system>(&mut self, system: S) -> &mut Self { + pub fn add_startup_system(&mut self, system: S) -> &mut Self + where + S: IntoSystem>, + { self.startup_systems.push(system.system()); self } /// Add a [`System`] to the stage with the given label. - pub fn add_system_to_stage, L: StageLabel>( - &mut self, - label: L, - system: S, - ) -> &mut Self { + pub fn add_system_to_stage(&mut self, label: impl StageLabel, system: S) -> &mut Self + where + S: IntoSystem>, + { let name = label.name(); let id = label.id(); let mut stage = None; @@ -150,14 +150,14 @@ pub trait SystemStage: Sync + Send { /// /// > **Note:** You must call [`initialize()`][Self::initialize] once before calling `run()` one /// > or more times. - fn run(&mut self, world: &mut World) -> SystemResult; + fn run(&mut self, world: &mut World); /// Initialize the contained systems for the given `world`. /// /// Must be called once before calling [`run()`][Self::run]. fn initialize(&mut self, world: &mut World); /// Add a system to this stage. - fn add_system(&mut self, system: System<()>); + fn add_system(&mut self, system: StaticSystem<(), ()>); } /// A collection of systems that will be run in order. @@ -169,7 +169,7 @@ pub struct SimpleSystemStage { /// 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>, + pub systems: Vec>, } impl SimpleSystemStage { @@ -192,10 +192,10 @@ impl SystemStage for SimpleSystemStage { self.name.clone() } - fn run(&mut self, world: &mut World) -> SystemResult { + fn run(&mut self, world: &mut World) { // Run the systems for system in &mut self.systems { - system.run(world)?; + system.run(world, ()); } // Drain the command queue @@ -205,12 +205,10 @@ impl SystemStage for SimpleSystemStage { for mut system in command_queue.queue.drain(..) { system.initialize(world); - system.run(world).unwrap(); + system.run(world, ()); } } } - - Ok(()) } fn initialize(&mut self, world: &mut World) { @@ -220,7 +218,7 @@ impl SystemStage for SimpleSystemStage { } } - fn add_system(&mut self, system: System<()>) { + fn add_system(&mut self, system: StaticSystem<(), ()>) { self.systems.push(system); } } @@ -267,10 +265,10 @@ impl StageLabel for CoreStage { /// A resource containing the [`Commands`] command queue. /// /// You can use [`Commands`] as a [`SystemParam`] as a shortcut to [`ResMut`]. -#[derive(Debug, HasSchema, Default)] +#[derive(HasSchema, Default)] pub struct CommandQueue { /// The system queue that will be run at the end of the stage - pub queue: VecDeque, + pub queue: VecDeque>, } impl Clone for CommandQueue { @@ -290,7 +288,10 @@ impl Clone for CommandQueue { impl CommandQueue { /// Add a system to be run at the end of the stage. - pub fn add>(&mut self, system: S) { + pub fn add(&mut self, system: S) + where + S: IntoSystem>, + { self.queue.push_back(system.system()); } } @@ -312,7 +313,7 @@ impl<'a> SystemParam for Commands<'a> { world.resources.get_cell::().unwrap() } - fn borrow(state: &mut Self::State) -> Self::Param<'_> { + fn borrow<'s>(_world: &'s World, state: &'s mut Self::State) -> Self::Param<'s> { Commands(state.borrow_mut()) } } diff --git a/framework_crates/bones_ecs/src/system.rs b/framework_crates/bones_ecs/src/system.rs index aa0638bcd3..919207c319 100644 --- a/framework_crates/bones_ecs/src/system.rs +++ b/framework_crates/bones_ecs/src/system.rs @@ -9,47 +9,39 @@ struct Test { s: String, } -/// Struct used to run a system function using the world. -pub struct System { +/// Trait implemented by systems. +pub trait System { + /// Initialize the system, creating any component or resource storages necessary for the system + /// to run in the world. + fn initialize(&self, world: &mut World); + /// Run the system. + fn run(&mut self, world: &World, input: In) -> Out; + /// Get a best-effort name for the system, used in diagnostics. + fn name(&self) -> &str; +} + +/// Struct containing a static system. +pub struct StaticSystem { /// This should be called once to initialize the system, allowing it to intialize any resources /// or components in the world. /// /// Usually only called once, but this is not guaranteed so the implementation should be /// idempotent. - pub initialize: Box, + pub initialize: fn(&mut World), /// This is run every time the system is executed - pub run: Box SystemResult>, + pub run: Box Out + Send + Sync>, /// A best-effort name for the system, for diagnostic purposes. pub name: &'static str, } -impl std::fmt::Debug for System { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("System") - .field("name", &self.name) - .finish_non_exhaustive() - } -} - -impl System { - /// Initializes the resources required to run this system inside of the provided [`World`], if - /// those resources don't already exist. - /// - /// This is usually only called once, but this is not guaranteed so the implementation should be - /// idempotent. - pub fn initialize(&self, world: &mut World) { +impl System for StaticSystem { + fn initialize(&self, world: &mut World) { (self.initialize)(world) } - - /// Runs the system's function using the provided [`World`] - pub fn run(&mut self, world: &World) -> SystemResult { - (self.run)(world) + fn run(&mut self, world: &World, input: In) -> Out { + (self.run)(world, input) } - - /// Returns the underlying type name of the system. - /// - /// This is not guranteed to be stable or human-readable, but can be used for diagnostics. - pub fn name(&self) -> &'static str { + fn name(&self) -> &str { self.name } } @@ -60,47 +52,27 @@ impl System { /// /// - Have 26 or less arguments, /// - Where every argument implments [`SystemParam`], and -/// - That returns either `()` or [`SystemResult`] -/// -/// [`IntoSystem`] is also implemented for functions that take [`&World`][World] as an argument, and -/// return either `()` or [`SystemResult`]. /// /// The most common [`SystemParam`] types that you will use as arguments to a system will be: -/// - [`Res`] and [`ResMut`] parameters to access resources +/// - [`Res`] and [`ResMut`] parameters to access resources /// - [`Comp`] and [`CompMut`] parameters to access components -pub trait IntoSystem { - /// Convert into a [`System`]. - fn system(self) -> System; -} +/// - [`&World`][World] to access the world directly +/// - [`In`] for systems which have an input value. This must be the first argument of the function. +pub trait IntoSystem { + /// The type of the system that is output + type Sys: System; -impl IntoSystem, Out> for System { - fn system(self) -> System { - self - } + /// Convert into a [`System`]. + fn system(self) -> Self::Sys; } -impl IntoSystem<(World, F), Out> for F -where - F: FnMut(&World) -> Out + Send + Sync + 'static, -{ - fn system(mut self) -> System { - System { - initialize: Box::new(|_| ()), - run: Box::new(move |world| Ok(self(world))), - name: std::any::type_name::(), - } - } -} -impl IntoSystem<(World, F, SystemResult), Out> for F +impl IntoSystem for T where - F: FnMut(&World) -> SystemResult + Send + Sync + 'static, + T: System, { - fn system(self) -> System { - System { - initialize: Box::new(|_| ()), - run: Box::new(self), - name: std::any::type_name::(), - } + type Sys = T; + fn system(self) -> Self::Sys { + self } } @@ -135,9 +107,23 @@ pub trait SystemParam: Sized { /// This is used create an instance of the system parame, possibly borrowed from the /// intermediate parameter state. #[allow(clippy::needless_lifetimes)] // Explicit lifetimes help clarity in this case - fn borrow<'s>(state: &'s mut Self::State) -> Self::Param<'s>; + fn borrow<'s>(world: &'s World, state: &'s mut Self::State) -> Self::Param<'s>; +} + +impl SystemParam for &'_ World { + type State = (); + type Param<'s> = &'s World; + fn initialize(_world: &mut World) {} + fn get_state(_world: &World) -> Self::State {} + fn borrow<'s>(world: &'s World, _state: &'s mut Self::State) -> Self::Param<'s> { + world + } } +/// The system input parameter. +#[derive(Deref, DerefMut)] +pub struct In(pub T); + /// [`SystemParam`] for getting read access to a resource. /// /// Use [`Res`] if you want to automatically initialize the resource. @@ -212,7 +198,7 @@ impl<'a, T: HasSchema> SystemParam for Res<'a, T> { }) } - fn borrow(state: &mut Self::State) -> Self::Param<'_> { + fn borrow<'s>(_world: &'s World, state: &'s mut Self::State) -> Self::Param<'s> { Res(state.borrow()) } } @@ -231,7 +217,7 @@ impl<'a, T: HasSchema + FromWorld> SystemParam for ResInit<'a, T> { world.resources.get_cell::().unwrap() } - fn borrow(state: &mut Self::State) -> Self::Param<'_> { + fn borrow<'s>(_world: &'s World, state: &'s mut Self::State) -> Self::Param<'s> { ResInit(state.borrow()) } } @@ -254,7 +240,7 @@ impl<'a, T: HasSchema> SystemParam for ResMut<'a, T> { }) } - fn borrow(state: &mut Self::State) -> Self::Param<'_> { + fn borrow<'s>(_world: &'s World, state: &'s mut Self::State) -> Self::Param<'s> { ResMut(state.borrow_mut()) } } @@ -273,7 +259,7 @@ impl<'a, T: HasSchema + FromWorld> SystemParam for ResMutInit<'a, T> { world.resources.get_cell::().unwrap() } - fn borrow(state: &mut Self::State) -> Self::Param<'_> { + fn borrow<'s>(_world: &'s World, state: &'s mut Self::State) -> Self::Param<'s> { ResMutInit(state.borrow_mut()) } } @@ -295,7 +281,7 @@ impl<'a, T: HasSchema> SystemParam for Comp<'a, T> { world.components.get_cell::().unwrap() } - fn borrow(state: &mut Self::State) -> Self::Param<'_> { + fn borrow<'s>(_world: &'s World, state: &'s mut Self::State) -> Self::Param<'s> { state.borrow() } } @@ -312,7 +298,7 @@ impl<'a, T: HasSchema> SystemParam for CompMut<'a, T> { world.components.get_cell::().unwrap() } - fn borrow(state: &mut Self::State) -> Self::Param<'_> { + fn borrow<'s>(_world: &'s World, state: &'s mut Self::State) -> Self::Param<'s> { state.borrow_mut() } } @@ -326,28 +312,29 @@ macro_rules! impl_system { $( $args: SystemParam, )* - > IntoSystem<(F, $($args,)*), Out> for F + > IntoSystem<(F, $($args,)*), (), Out> for F where for<'a> F: 'static + Send + Sync + FnMut( $( <$args as SystemParam>::Param<'a>, )* - ) -> SystemResult + + ) -> Out + FnMut( $( $args, )* - ) -> SystemResult + ) -> Out { - fn system(mut self) -> System { - System { + type Sys = StaticSystem<(), Out>; + fn system(mut self) -> Self::Sys { + StaticSystem { name: std::any::type_name::(), - initialize: Box::new(|_world| { + initialize: |_world| { $( $args::initialize(_world); )* - }), - run: Box::new(move |_world| { + }, + run: Box::new(move |_world, _input| { $( #[allow(non_snake_case)] let mut $args = $args::get_state(_world); @@ -355,7 +342,7 @@ macro_rules! impl_system { self( $( - $args::borrow(&mut $args), + $args::borrow(_world, &mut $args), )* ) }) @@ -365,48 +352,53 @@ macro_rules! impl_system { }; } -macro_rules! impl_system_with_empty_return { +macro_rules! impl_system_with_input { ($($args:ident,)*) => { #[allow(unused_parens)] impl< + 'input, F, + InT: 'input, + Out, $( $args: SystemParam, )* - > IntoSystem<(F, $($args,)* ()), ()> for F + > IntoSystem<(F, InT, $($args,)*), InT, Out> for F where for<'a> F: 'static + Send + Sync + FnMut( + In, $( <$args as SystemParam>::Param<'a>, )* - ) + + ) -> Out + FnMut( + In, $( $args, )* - ) + ) -> Out { - fn system(mut self) -> System<()> { - System { + type Sys = StaticSystem; + fn system(mut self) -> Self::Sys { + StaticSystem { name: std::any::type_name::(), - initialize: Box::new(|_world| { + initialize: |_world| { $( $args::initialize(_world); )* - }), - run: Box::new(move |_world| { + }, + run: Box::new(move |_world, input| { $( #[allow(non_snake_case)] let mut $args = $args::get_state(_world); )* self( + In(input), $( - $args::borrow(&mut $args), + $args::borrow(_world, &mut $args), )* - ); - - Ok(()) + ) }) } } @@ -416,17 +408,18 @@ macro_rules! impl_system_with_empty_return { macro_rules! impl_systems { // base case - () => {}; + () => { + impl_system!(); + impl_system_with_input!(); + }; + // recursive call ($head:ident, $($idents:ident,)*) => { - // recursive call impl_system!($head, $($idents,)*); - impl_system_with_empty_return!($head, $($idents,)*); + impl_system_with_input!($head, $($idents,)*); impl_systems!($($idents,)*); } } -impl_system!(); -impl_system_with_empty_return!(); impl_systems!(A, B, C, D, E, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z,); #[cfg(test)] @@ -440,8 +433,8 @@ mod tests { _var2: Ref>, _var3: Res, _var4: ResMut, - ) -> SystemResult { - Ok(()) + ) -> u32 { + 0 } // Technically reusing the same type is incorrect and causes a runtime panic. // However, there doesn't seem to be a clean way to handle type inequality in generics. @@ -459,11 +452,12 @@ mod tests { _var10: CompMut, _var11: Comp, _var12: CompMut, - ) -> SystemResult { - Ok(()) + ) { } + fn tmp3(_in: In, _comp1: Comp) {} let _ = tmp.system(); let _ = tmp2.system(); + let _ = tmp3.system(); } #[test] @@ -472,23 +466,34 @@ mod tests { send( (move |_var1: Res| { let _y = x; - Ok(()) }) .system(), ); - send((|| Ok(())).system()); + send((|| ()).system()); send(sys.system()); } - fn sys(_var1: Res) -> SystemResult { - Ok(()) - } + fn sys(_var1: Res) {} fn send(_t: T) {} #[test] - fn manual_system_run() { - let mut world = World::default(); - world.init_resource::(); + fn in_and_out() { + fn mul_by_res(n: In, r: Res) -> usize { + *n * *r + } + + fn sys_with_ref_in(mut n: In<&mut usize>) { + **n *= 3; + } + + let mut world = World::new(); + world.insert_resource(2usize); + + let result = world.run_initialized_system(mul_by_res, 3); + assert_eq!(result, 6); + + let mut n = 3; + world.run_initialized_system(sys_with_ref_in, &mut n) } #[test] @@ -503,7 +508,6 @@ mod tests { let mut my_system = (|_a: ResInit, mut b: ResMutInit| { let b2 = B { x: 45 }; *b = b2; - Ok(()) }) .system(); @@ -515,7 +519,7 @@ mod tests { assert_eq!(res.x, 0); } - my_system.run(&world).unwrap(); + my_system.run(&world, ()); let res = world.resource::(); assert_eq!(res.x, 45); diff --git a/framework_crates/bones_ecs/src/world.rs b/framework_crates/bones_ecs/src/world.rs index 4773499685..40b0a534fd 100644 --- a/framework_crates/bones_ecs/src/world.rs +++ b/framework_crates/bones_ecs/src/world.rs @@ -68,11 +68,17 @@ impl World { /// Run a system once. /// /// This is good for initializing the world with setup systems. - pub fn run_system>(&mut self, system: S) -> SystemResult { + pub fn run_system<'system, R, In, Out, S>(&mut self, system: S, input: In) -> Out + where + In: 'system, + Out: 'system, + S: IntoSystem, + S::Sys: 'system, + { let mut s = system.system(); s.initialize(self); - s.run(self) + s.run(self, input) } /// Run a system once, assuming any necessary initialization has already been performed for that @@ -88,12 +94,15 @@ impl World { /// /// If all the system parameters have already been initialized, by calling /// [`initialize()`][System::initialize] on the system, then this will work fine. - pub fn run_initialized_system>( - &self, - system: S, - ) -> SystemResult { + pub fn run_initialized_system<'system, Args, In, Out, S>(&self, system: S, input: In) -> Out + where + In: 'system, + Out: 'system, + S: IntoSystem, + S::Sys: 'system, + { let mut s = system.system(); - s.run(self) + s.run(self, input) } /// Initialize a resource of type `T` by inserting it's default value. @@ -251,44 +260,44 @@ mod tests { fn sanity_check() { let mut world = World::new(); - world.run_system(setup_world).unwrap(); + world.run_system(setup_world, ()); // Make sure our entities exist visit properly during iteration let test = || {}; - world.run_system(test).unwrap(); + world.run_system(test, ()); // Mutate and read some components - world.run_system(pos_vel_system).unwrap(); + world.run_system(pos_vel_system, ()); // Make sure the mutations were applied - world.run_system(test_pos_vel_1_run).unwrap(); + world.run_system(test_pos_vel_1_run, ()); } #[test] fn snapshot() { let mut world1 = World::new(); - world1.run_system(setup_world).unwrap(); + world1.run_system(setup_world, ()); // Snapshot world1 let mut snap = world1.clone(); // Make sure the snapshot represents world1's state - snap.run_system(test_after_setup_state).unwrap(); + snap.run_system(test_after_setup_state, ()); // Run the pos_vel system on world1 - world1.run_system(pos_vel_system).unwrap(); + world1.run_system(pos_vel_system, ()); // Make sure world1 has properly update - world1.run_system(test_pos_vel_1_run).unwrap(); + world1.run_system(test_pos_vel_1_run, ()); // Make sure the snapshot hasn't changed - snap.run_system(test_after_setup_state).unwrap(); + snap.run_system(test_after_setup_state, ()); // Run the pos vel system once on the snapshot - snap.run_system(pos_vel_system).unwrap(); + snap.run_system(pos_vel_system, ()); // Make sure the snapshot has updated - world1.run_system(test_pos_vel_1_run).unwrap(); + world1.run_system(test_pos_vel_1_run, ()); } #[test] diff --git a/framework_crates/bones_framework/src/localization.rs b/framework_crates/bones_framework/src/localization.rs index 4fa29233c9..2456b36999 100644 --- a/framework_crates/bones_framework/src/localization.rs +++ b/framework_crates/bones_framework/src/localization.rs @@ -118,7 +118,10 @@ impl SystemParam for Localization<'_, T> { .unwrap(), ) } - fn borrow((asset_server, field_idx): &mut Self::State) -> Self::Param<'_> { + fn borrow<'s>( + _world: &'s World, + (asset_server, field_idx): &'s mut Self::State, + ) -> Self::Param<'s> { const ERR: &str = "Could not find a `Handle` field on root asset, \ needed for `Localization` parameter to work"; let asset_server = asset_server.borrow(); diff --git a/framework_crates/bones_framework/src/params.rs b/framework_crates/bones_framework/src/params.rs index 1cf7d483af..f439769fef 100644 --- a/framework_crates/bones_framework/src/params.rs +++ b/framework_crates/bones_framework/src/params.rs @@ -18,7 +18,7 @@ impl<'a, T: HasSchema> SystemParam for Root<'a, T> { fn get_state(world: &World) -> Self::State { world.resources.get_cell::().unwrap() } - fn borrow(state: &mut Self::State) -> Self::Param<'_> { + fn borrow<'s>(_world: &'s World, state: &'s mut Self::State) -> Self::Param<'s> { Root(Ref::map(state.borrow(), |asset_server| asset_server.root())) } } diff --git a/framework_crates/bones_framework/src/render/ui.rs b/framework_crates/bones_framework/src/render/ui.rs index e36fa429b9..87b398a0d7 100644 --- a/framework_crates/bones_framework/src/render/ui.rs +++ b/framework_crates/bones_framework/src/render/ui.rs @@ -18,6 +18,46 @@ pub fn ui_plugin(_session: &mut Session) { #[derive(HasSchema, Clone, Debug, Default, Deref, DerefMut)] pub struct EguiCtx(pub egui::Context); +/// System parameter for rendering to egui. +#[derive(Deref)] +pub struct Egui<'a> { + /// The bones world reference, needed so that the `widget` method can work. + pub world: &'a World, + /// The egui context. + #[deref] + pub context: Ref<'a, egui::Context>, +} + +impl Egui<'_> { + /// Run a widget sub-system. + pub fn widget<'a, Args, S, Out>(&self, system: S, ui: &'a mut egui::Ui) -> Out + where + S: IntoSystem, + S::Sys: 'a, + { + self.world.run_initialized_system(system, ui) + } +} + +impl SystemParam for Egui<'_> { + type State = AtomicResource; + type Param<'s> = Egui<'s>; + + fn initialize(_world: &mut World) {} + + fn get_state(world: &World) -> Self::State { + world + .resources + .get_cell::() + .expect("`EguiCtx` resource doesn't exist") + } + + fn borrow<'s>(world: &'s World, state: &'s mut Self::State) -> Self::Param<'s> { + let context = Ref::map(state.borrow(), |x| &x.0); + Egui { world, context } + } +} + /// Resource that maps image handles to their associated egui textures. #[derive(HasSchema, Clone, Debug, Default, Deref, DerefMut)] pub struct EguiTextures(pub HashMap, egui::TextureId>); diff --git a/framework_crates/bones_lib/src/lib.rs b/framework_crates/bones_lib/src/lib.rs index 3ac77fb582..ae56ab8d57 100644 --- a/framework_crates/bones_lib/src/lib.rs +++ b/framework_crates/bones_lib/src/lib.rs @@ -107,7 +107,7 @@ impl Plugin for F { /// A session runner is in charge of advancing a [`Session`] simulation. pub trait SessionRunner: Sync + Send + 'static { /// Step the simulation once. - fn step(&mut self, world: &mut World, stages: &mut SystemStages) -> SystemResult; + fn step(&mut self, world: &mut World, stages: &mut SystemStages); } /// The default [`SessionRunner`], which just runs the systems once every time it is run. @@ -117,7 +117,7 @@ pub struct DefaultSessionRunner { pub has_init: bool, } impl SessionRunner for DefaultSessionRunner { - fn step(&mut self, world: &mut World, stages: &mut SystemStages) -> SystemResult { + fn step(&mut self, world: &mut World, stages: &mut SystemStages) { // Initialize systems if they have not been initialized yet. if unlikely(!self.has_init) { self.has_init = true; @@ -258,8 +258,7 @@ impl Game { // Step the current session's simulation using it's session runner current_session .runner - .step(&mut current_session.world, &mut current_session.stages) - .unwrap_or_else(|_| panic!("Error running session: {session_name}")); + .step(&mut current_session.world, &mut current_session.stages); // Pull the sessions back out of the world {