diff --git a/CHANGELOG.md b/CHANGELOG.md index 619654e..098795f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Remove world access from conditions and modifiers. This means that you no longer can write game-specific conditions or modifiers. But it's much nicer (and faster) to just do it in observers instead. - Replace `with_held_timer` with `relative_speed` that just accepts a boolean. - Rename `HeldTimer` into `ConditionTimer`. - Use Use `trace!` instead of `debug!` for triggered events. diff --git a/Cargo.toml b/Cargo.toml index 08c64fc..d37ad55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,9 +14,7 @@ include = ["/src", "/tests", "/examples", "/LICENSE*"] [dependencies] bevy_enhanced_input_macros = { path = "macros", version = "0.1" } bevy = { version = "0.14", default-features = false, features = ["serialize"] } -bevy_egui = { version = "0.30", default-features = false, features = [ - "immutable_ctx", # Required for get read-only access in our exclusive system. -], optional = true } +bevy_egui = { version = "0.30", default-features = false, optional = true } serde = "1.0" bitflags = { version = "2.6", features = ["serde"] } interpolation = "0.3" diff --git a/README.md b/README.md index 8e43231..63e0f93 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Dynamic and contextual input mappings for Bevy, inspired by [Unreal Engine Enhan * Control how actions accumulate input from sources and consume it. * Layer multiple contexts on a single entity, controlled by priority. * Apply modifiers to inputs, such as dead zones, inversion, scaling, etc., or create custom modifiers by implementing a trait. -* Assign conditions for how and when an action is triggered, like "hold", "tap", "chord", etc. You can also create custom conditions, such as "on the ground". +* Assign conditions for how and when an action is triggered, like "hold", "tap", "chord", etc. You can also create custom conditions by implementing a trait. * React on actions with observers. ## Getting Started diff --git a/examples/context_switch.rs b/examples/context_switch.rs index 706877b..4ace1ed 100644 --- a/examples/context_switch.rs +++ b/examples/context_switch.rs @@ -135,7 +135,7 @@ impl InputContext for InCar { .with_wasd() .with_modifier(Normalize) .with_modifier(ScaleByDelta) - .with_modifier(Scalar::splat(DEFAULT_SPEED + 200.0)); // Make car faster. It's possible to get the value from a component by writing a custom modifier. + .with_modifier(Scalar::splat(DEFAULT_SPEED + 200.0)); // Make car faster. ctx.bind::().with(KeyCode::Enter); ctx diff --git a/src/input/input_reader.rs b/src/input/input_reader.rs index 4d4bcd3..74c64f0 100644 --- a/src/input/input_reader.rs +++ b/src/input/input_reader.rs @@ -28,8 +28,10 @@ pub(crate) struct InputReader<'w, 's> { mouse_motion: Local<'s, Vec2>, #[cfg(feature = "ui_priority")] interactions: Query<'w, 's, &'static Interaction>, + // In egui mutable reference is required to get contexts, + // unless `immutable_ctx` feature is enabled. #[cfg(feature = "egui_priority")] - egui: Query<'w, 's, &'static EguiContext>, + egui: Query<'w, 's, &'static mut EguiContext>, } impl InputReader<'_, '_> { @@ -47,12 +49,20 @@ impl InputReader<'_, '_> { } #[cfg(feature = "egui_priority")] - if self.egui.iter().any(|ctx| ctx.get().wants_keyboard_input()) { + if self + .egui + .iter_mut() + .any(|ctx| ctx.get_mut().wants_keyboard_input()) + { self.consumed.ui_wants_keyboard = true; } #[cfg(feature = "egui_priority")] - if self.egui.iter().any(|ctx| ctx.get().wants_pointer_input()) { + if self + .egui + .iter_mut() + .any(|ctx| ctx.get_mut().wants_pointer_input()) + { self.consumed.ui_wants_mouse = true; } @@ -286,7 +296,7 @@ mod tests { let key = KeyCode::Space; world.resource_mut::>().press(key); - let mut reader = state.get(&world); + let mut reader = state.get_mut(&mut world); assert_eq!(reader.value(key), ActionValue::Bool(true)); assert_eq!(reader.value(KeyCode::Escape), ActionValue::Bool(false)); assert_eq!( @@ -311,7 +321,7 @@ mod tests { .resource_mut::>() .press(button); - let mut reader = state.get(&world); + let mut reader = state.get_mut(&mut world); assert_eq!(reader.value(button), ActionValue::Bool(true)); assert_eq!(reader.value(MouseButton::Right), ActionValue::Bool(false)); assert_eq!( @@ -335,7 +345,7 @@ mod tests { world.send_event(MouseMotion { delta: value }); let input = Input::mouse_motion(); - let mut reader = state.get(&world); + let mut reader = state.get_mut(&mut world); reader.update_state(); assert_eq!(reader.value(input), ActionValue::Axis2D(value)); assert_eq!( @@ -361,7 +371,7 @@ mod tests { }); let input = Input::mouse_wheel(); - let mut reader = state.get(&world); + let mut reader = state.get_mut(&mut world); reader.update_state(); assert_eq!(reader.value(input), ActionValue::Axis2D(value)); assert_eq!( @@ -396,7 +406,7 @@ mod tests { button_type: other_button, }); - let mut reader = state.get(&world); + let mut reader = state.get_mut(&mut world); reader.set_gamepad(gamepad); assert_eq!(reader.value(button), ActionValue::Bool(true)); assert_eq!( @@ -433,7 +443,7 @@ mod tests { button_type: other_button, }); - let mut reader = state.get(&world); + let mut reader = state.get_mut(&mut world); assert_eq!(reader.value(button), ActionValue::Bool(true)); assert_eq!(reader.value(other_button), ActionValue::Bool(true)); assert_eq!( @@ -474,7 +484,7 @@ mod tests { value, ); - let mut reader = state.get(&world); + let mut reader = state.get_mut(&mut world); reader.set_gamepad(gamepad); assert_eq!(reader.value(axis), ActionValue::Axis1D(1.0)); assert_eq!( @@ -518,7 +528,7 @@ mod tests { value, ); - let mut reader = state.get(&world); + let mut reader = state.get_mut(&mut world); assert_eq!(reader.value(axis), ActionValue::Axis1D(1.0)); assert_eq!(reader.value(other_axis), ActionValue::Axis1D(1.0)); assert_eq!( @@ -547,7 +557,7 @@ mod tests { key, modifiers: modifier.into(), }; - let mut reader = state.get(&world); + let mut reader = state.get_mut(&mut world); assert_eq!(reader.value(input), ActionValue::Bool(true)); assert_eq!(reader.value(key), ActionValue::Bool(true)); assert_eq!( @@ -572,7 +582,7 @@ mod tests { key: other_key, modifiers: modifier.into(), }; - let mut reader = state.get(&world); + let mut reader = state.get_mut(&mut world); assert_eq!(reader.value(other_input), ActionValue::Bool(false)); assert_eq!(reader.value(other_key), ActionValue::Bool(true)); } @@ -592,7 +602,7 @@ mod tests { button, modifiers: modifier.into(), }; - let mut reader = state.get(&world); + let mut reader = state.get_mut(&mut world); assert_eq!(reader.value(input), ActionValue::Bool(true)); assert_eq!(reader.value(button), ActionValue::Bool(true)); assert_eq!( @@ -621,7 +631,7 @@ mod tests { let input = Input::MouseMotion { modifiers: modifier.into(), }; - let mut reader = state.get(&world); + let mut reader = state.get_mut(&mut world); reader.update_state(); assert_eq!(reader.value(input), ActionValue::Axis2D(value)); assert_eq!( @@ -659,7 +669,7 @@ mod tests { let input = Input::MouseWheel { modifiers: modifier.into(), }; - let mut reader = state.get(&world); + let mut reader = state.get_mut(&mut world); reader.update_state(); assert_eq!(reader.value(input), ActionValue::Axis2D(value)); assert_eq!( @@ -698,7 +708,7 @@ mod tests { window: Entity::PLACEHOLDER, }); - let mut reader = state.get(&world); + let mut reader = state.get_mut(&mut world); reader.update_state(); reader.consumed.ui_wants_keyboard = true; reader.consumed.ui_wants_mouse = true; diff --git a/src/input_context.rs b/src/input_context.rs index df65a0c..c2817ae 100644 --- a/src/input_context.rs +++ b/src/input_context.rs @@ -174,20 +174,19 @@ impl ContextInstances { pub(crate) fn update( &mut self, - world: &World, commands: &mut Commands, reader: &mut InputReader, - delta: f32, + time: &Time, ) { for group in &mut self.0 { match group { InstanceGroup::Exclusive { instances, .. } => { for (entity, ctx) in instances { - ctx.update(world, commands, reader, &[*entity], delta); + ctx.update(commands, reader, time, &[*entity]); } } InstanceGroup::Shared { entities, ctx, .. } => { - ctx.update(world, commands, reader, entities, delta); + ctx.update(commands, reader, time, entities); } } } diff --git a/src/input_context/context_instance.rs b/src/input_context/context_instance.rs index 335025d..b39e9ee 100644 --- a/src/input_context/context_instance.rs +++ b/src/input_context/context_instance.rs @@ -76,15 +76,14 @@ impl ContextInstance { pub(super) fn update( &mut self, - world: &World, commands: &mut Commands, reader: &mut InputReader, + time: &Time, entities: &[Entity], - delta: f32, ) { reader.set_gamepad(self.gamepad); for binding in &mut self.bindings { - binding.update(world, commands, reader, &mut self.actions, entities, delta); + binding.update(commands, reader, &mut self.actions, time, entities); } } @@ -254,19 +253,13 @@ impl ActionBind { fn update( &mut self, - world: &World, commands: &mut Commands, reader: &mut InputReader, actions: &mut ActionsData, + time: &Time, entities: &[Entity], - delta: f32, ) { trace!("updating action `{}`", self.action_name); - let ctx = ActionContext { - world, - entities, - actions, - }; reader.set_consume_input(self.consume_input); let mut tracker = TriggerTracker::new(ActionValue::zero(self.dim)); @@ -282,40 +275,23 @@ impl ActionBind { } let mut current_tracker = TriggerTracker::new(value); - current_tracker.apply_modifiers(&ctx, delta, &mut binding.modifiers); - current_tracker.apply_conditions(&ctx, delta, &mut binding.conditions); + current_tracker.apply_modifiers(time, &mut binding.modifiers); + current_tracker.apply_conditions(actions, time, &mut binding.conditions); tracker.merge(current_tracker, self.accumulation); } - tracker.apply_modifiers(&ctx, delta, &mut self.modifiers); - tracker.apply_conditions(&ctx, delta, &mut self.conditions); + tracker.apply_modifiers(time, &mut self.modifiers); + tracker.apply_conditions(actions, time, &mut self.conditions); let (state, value) = tracker.finish(); let action = actions .get_mut(&self.type_id) .expect("actions and bindings should have matching type IDs"); - action.update(commands, entities, state, value, delta); + action.update(commands, time, entities, state, value); } } -/// Read-only data for [`InputCondition`]s and [`InputModifier`]s during action evaluation. -#[non_exhaustive] -pub struct ActionContext<'a> { - /// Current world. - pub world: &'a World, - - /// The state of other actions within the currently evaluating context. - pub actions: &'a ActionsData, - - /// The entities for which the action is being evaluated. - /// - /// This can be either a single entity when [`InputContext::MODE`](super::InputContext::MODE) is - /// set to [`ContextMode::Exclusive`](super::ContextMode::Exclusive), - /// or multiple entities when using [`ContextMode::Shared`](super::ContextMode::Shared). - pub entities: &'a [Entity], -} - /// Associated input for [`ActionBind`]. #[derive(Debug)] pub struct InputBind { diff --git a/src/input_context/input_action.rs b/src/input_context/input_action.rs index 0ec5d1d..14b2685 100644 --- a/src/input_context/input_action.rs +++ b/src/input_context/input_action.rs @@ -47,21 +47,21 @@ impl ActionData { pub fn update( &mut self, commands: &mut Commands, + time: &Time, entities: &[Entity], state: ActionState, value: impl Into, - delta: f32, ) { // Add time from the previous frame if needed // before triggering events. match self.state { ActionState::None => (), ActionState::Ongoing => { - self.elapsed_secs += delta; + self.elapsed_secs += time.delta_seconds(); } ActionState::Fired => { - self.elapsed_secs += delta; - self.fired_secs += delta; + self.elapsed_secs += time.delta_seconds(); + self.fired_secs += time.delta_seconds(); } } diff --git a/src/input_context/input_condition.rs b/src/input_context/input_condition.rs index 17d0d96..5edec5b 100644 --- a/src/input_context/input_condition.rs +++ b/src/input_context/input_condition.rs @@ -11,49 +11,32 @@ pub mod tap; use std::fmt::Debug; -use super::{context_instance::ActionContext, input_action::ActionState}; +use bevy::prelude::*; + +use super::input_action::{ActionState, ActionsData}; use crate::action_value::ActionValue; pub const DEFAULT_ACTUATION: f32 = 0.5; /// Defines how input activates. /// -/// Most conditions analyze the input itself, checking for minimum actuation values +/// Conditions analyze the input, checking for minimum actuation values /// and validating patterns like short taps, prolonged holds, or the typical "press" /// or "release" events. /// /// Can be applied both to inputs and actions. /// See [`ActionBind::with_condition`](super::context_instance::ActionBind::with_condition) /// and [`InputBind::with_condition`](super::context_instance::InputBind::with_condition). -/// -/// You can create game-specific conditions: -/// -/// ``` -/// # use bevy::prelude::*; -/// use bevy_enhanced_input::prelude::*; -/// -/// #[derive(Debug, Clone, Copy)] -/// struct OnGround; -/// -/// impl InputCondition for OnGround { -/// fn evaluate(&mut self, ctx: &ActionContext, _delta: f32, _value: ActionValue) -> ActionState { -/// let entity = *ctx.entities.first().unwrap(); -/// let transform = ctx.world.get::(entity).unwrap(); -/// if transform.translation.z <= 0.1 { -/// ActionState::Fired -/// } else { -/// ActionState::None -/// } -/// } -/// -/// fn kind(&self) -> ConditionKind { -/// ConditionKind::Required -/// } -/// } -/// ``` pub trait InputCondition: Sync + Send + Debug + 'static { /// Returns calculates state. - fn evaluate(&mut self, ctx: &ActionContext, delta: f32, value: ActionValue) -> ActionState; + /// + /// `actions` argument a state of other actions within the currently evaluating context. + fn evaluate( + &mut self, + actions: &ActionsData, + time: &Time, + value: ActionValue, + ) -> ActionState; /// Returns how the condition is combined with others. fn kind(&self) -> ConditionKind { diff --git a/src/input_context/input_condition/blocked_by.rs b/src/input_context/input_condition/blocked_by.rs index 11cf19e..49dbbb4 100644 --- a/src/input_context/input_condition/blocked_by.rs +++ b/src/input_context/input_condition/blocked_by.rs @@ -5,10 +5,7 @@ use bevy::prelude::*; use super::{ConditionKind, InputCondition}; use crate::{ action_value::ActionValue, - input_context::{ - context_instance::ActionContext, - input_action::{ActionState, InputAction}, - }, + input_context::input_action::{ActionState, ActionsData, InputAction}, }; /// Requires another action to not be triggered within the same context. @@ -37,8 +34,13 @@ impl Clone for BlockedBy { impl Copy for BlockedBy {} impl InputCondition for BlockedBy { - fn evaluate(&mut self, ctx: &ActionContext, _delta: f32, _value: ActionValue) -> ActionState { - if let Some(action) = ctx.actions.action::() { + fn evaluate( + &mut self, + actions: &ActionsData, + _time: &Time, + _value: ActionValue, + ) -> ActionState { + if let Some(action) = actions.action::() { if action.state() == ActionState::Fired { return ActionState::None; } @@ -64,42 +66,32 @@ mod tests { use bevy_enhanced_input_macros::InputAction; use super::*; - use crate::{ - input_context::input_action::{ActionData, ActionsData}, - ActionValueDim, - }; + use crate::{input_context::input_action::ActionData, ActionValueDim}; #[test] fn blocked() { - let mut world = World::new(); + let mut condition = BlockedBy::::default(); let mut action = ActionData::new::(); - action.update(&mut world.commands(), &[], ActionState::Fired, true, 0.0); + let mut world = World::new(); + let time = Time::default(); + action.update(&mut world.commands(), &time, &[], ActionState::Fired, true); let mut actions = ActionsData::default(); actions.insert(TypeId::of::(), action); - let ctx = ActionContext { - world: &world, - actions: &actions, - entities: &[], - }; - let mut condition = BlockedBy::::default(); assert_eq!( - condition.evaluate(&ctx, 0.0, true.into()), + condition.evaluate(&actions, &time, true.into()), ActionState::None, ); } #[test] fn missing_action() { - let ctx = ActionContext { - world: &World::new(), - actions: &ActionsData::default(), - entities: &[], - }; - let mut condition = BlockedBy::::default(); + let actions = ActionsData::default(); + let time = Time::default(); + assert_eq!( - condition.evaluate(&ctx, 0.0, true.into()), + condition.evaluate(&actions, &time, true.into()), ActionState::Fired, ); } diff --git a/src/input_context/input_condition/chord.rs b/src/input_context/input_condition/chord.rs index 172adb0..64aed9d 100644 --- a/src/input_context/input_condition/chord.rs +++ b/src/input_context/input_condition/chord.rs @@ -5,10 +5,7 @@ use bevy::prelude::*; use super::{ConditionKind, InputCondition}; use crate::{ action_value::ActionValue, - input_context::{ - context_instance::ActionContext, - input_action::{ActionState, InputAction}, - }, + input_context::input_action::{ActionState, ActionsData, InputAction}, }; /// Requires action `A` to be triggered within the same context. @@ -37,8 +34,13 @@ impl Clone for Chord { impl Copy for Chord {} impl InputCondition for Chord { - fn evaluate(&mut self, ctx: &ActionContext, _delta: f32, _value: ActionValue) -> ActionState { - if let Some(action) = ctx.actions.action::() { + fn evaluate( + &mut self, + actions: &ActionsData, + _time: &Time, + _value: ActionValue, + ) -> ActionState { + if let Some(action) = actions.action::() { // Inherit state from the chorded action. action.state() } else { @@ -69,35 +71,28 @@ mod tests { #[test] fn chord() { - let mut world = World::new(); + let mut condition = Chord::::default(); let mut action = ActionData::new::(); - action.update(&mut world.commands(), &[], ActionState::Fired, true, 0.0); + let mut world = World::new(); + let time = Time::default(); + action.update(&mut world.commands(), &time, &[], ActionState::Fired, true); let mut actions = ActionsData::default(); actions.insert(TypeId::of::(), action); - let ctx = ActionContext { - world: &world, - actions: &actions, - entities: &[], - }; - let mut condition = Chord::::default(); assert_eq!( - condition.evaluate(&ctx, 0.0, true.into()), + condition.evaluate(&actions, &time, true.into()), ActionState::Fired, ); } #[test] fn missing_action() { - let ctx = ActionContext { - world: &World::new(), - actions: &ActionsData::default(), - entities: &[], - }; - let mut condition = Chord::::default(); + let actions = ActionsData::default(); + let time = Time::default(); + assert_eq!( - condition.evaluate(&ctx, 0.0, true.into()), + condition.evaluate(&actions, &time, true.into()), ActionState::None, ); } diff --git a/src/input_context/input_condition/condition_timer.rs b/src/input_context/input_condition/condition_timer.rs index e859628..a6906e6 100644 --- a/src/input_context/input_condition/condition_timer.rs +++ b/src/input_context/input_condition/condition_timer.rs @@ -12,13 +12,16 @@ pub struct ConditionTimer { } impl ConditionTimer { - pub fn update(&mut self, world: &World, mut delta: f32) { - if self.relative_speed { - let time = world.resource::>(); - delta *= time.relative_speed() - } + pub fn update(&mut self, time: &Time) { + // Time returns already scaled results. + // Unscale if configured. + let scale = if self.relative_speed { + 1.0 + } else { + time.relative_speed() + }; - self.duration += delta; + self.duration += time.delta_seconds() / scale; } pub fn reset(&mut self) { @@ -32,20 +35,32 @@ impl ConditionTimer { #[cfg(test)] mod tests { + use std::time::Duration; + use super::*; + #[test] + fn absolute() { + let mut time = Time::::default(); + time.set_relative_speed(0.5); + time.advance_by(Duration::from_millis(200 / 2)); // Advance needs to be scaled manually. + + let mut timer = ConditionTimer::default(); + timer.update(&time); + assert_eq!(timer.duration(), 0.2); + } + #[test] fn relative() { let mut time = Time::::default(); time.set_relative_speed(0.5); - let mut world = World::new(); - world.insert_resource(time); + time.advance_by(Duration::from_millis(200 / 2)); // Advance needs to be scaled manually. let mut timer = ConditionTimer { relative_speed: true, ..Default::default() }; - timer.update(&world, 1.0); - assert_eq!(timer.duration(), 0.5); + timer.update(&time); + assert_eq!(timer.duration(), 0.1); } } diff --git a/src/input_context/input_condition/down.rs b/src/input_context/input_condition/down.rs index 737a9fd..de901b0 100644 --- a/src/input_context/input_condition/down.rs +++ b/src/input_context/input_condition/down.rs @@ -1,7 +1,9 @@ +use bevy::prelude::*; + use super::{InputCondition, DEFAULT_ACTUATION}; use crate::{ action_value::ActionValue, - input_context::{context_instance::ActionContext, input_action::ActionState}, + input_context::input_action::{ActionState, ActionsData}, }; /// Returns [`ActionState::Fired`] when the input exceeds the actuation threshold. @@ -25,7 +27,12 @@ impl Default for Down { } impl InputCondition for Down { - fn evaluate(&mut self, _ctx: &ActionContext, _delta: f32, value: ActionValue) -> ActionState { + fn evaluate( + &mut self, + _actions: &ActionsData, + _time: &Time, + value: ActionValue, + ) -> ActionState { if value.is_actuated(self.actuation) { ActionState::Fired } else { @@ -36,23 +43,21 @@ impl InputCondition for Down { #[cfg(test)] mod tests { - use bevy::prelude::*; - use super::*; use crate::input_context::input_action::ActionsData; #[test] fn down() { - let ctx = ActionContext { - world: &World::new(), - actions: &ActionsData::default(), - entities: &[], - }; - let mut condition = Down::new(1.0); - assert_eq!(condition.evaluate(&ctx, 0.0, 0.0.into()), ActionState::None); + let actions = ActionsData::default(); + let time = Time::default(); + + assert_eq!( + condition.evaluate(&actions, &time, 0.0.into()), + ActionState::None + ); assert_eq!( - condition.evaluate(&ctx, 0.0, 1.0.into()), + condition.evaluate(&actions, &time, 1.0.into()), ActionState::Fired, ); } diff --git a/src/input_context/input_condition/hold.rs b/src/input_context/input_condition/hold.rs index ae0c579..5d3aa57 100644 --- a/src/input_context/input_condition/hold.rs +++ b/src/input_context/input_condition/hold.rs @@ -1,7 +1,9 @@ +use bevy::prelude::*; + use super::{condition_timer::ConditionTimer, InputCondition, DEFAULT_ACTUATION}; use crate::{ action_value::ActionValue, - input_context::{context_instance::ActionContext, input_action::ActionState}, + input_context::input_action::{ActionState, ActionsData}, }; /// Returns [`ActionState::Ongoing`] when the input becomes actuated and @@ -58,10 +60,15 @@ impl Hold { } impl InputCondition for Hold { - fn evaluate(&mut self, ctx: &ActionContext, delta: f32, value: ActionValue) -> ActionState { + fn evaluate( + &mut self, + _actions: &ActionsData, + time: &Time, + value: ActionValue, + ) -> ActionState { let actuated = value.is_actuated(self.actuation); if actuated { - self.timer.update(ctx.world, delta); + self.timer.update(time); } else { self.timer.reset(); } @@ -85,49 +92,57 @@ impl InputCondition for Hold { #[cfg(test)] mod tests { - use bevy::prelude::*; + use std::time::Duration; use super::*; use crate::input_context::input_action::ActionsData; #[test] fn hold() { - let ctx = ActionContext { - world: &World::new(), - actions: &ActionsData::default(), - entities: &[], - }; - let mut condition = Hold::new(1.0); + let actions = ActionsData::default(); + let mut time = Time::default(); + assert_eq!( - condition.evaluate(&ctx, 0.0, 1.0.into()), + condition.evaluate(&actions, &time, 1.0.into()), ActionState::Ongoing, ); + + time.advance_by(Duration::from_secs(1)); assert_eq!( - condition.evaluate(&ctx, 1.0, 1.0.into()), + condition.evaluate(&actions, &time, 1.0.into()), ActionState::Fired, ); assert_eq!( - condition.evaluate(&ctx, 1.0, 1.0.into()), + condition.evaluate(&actions, &time, 1.0.into()), ActionState::Fired, ); - assert_eq!(condition.evaluate(&ctx, 1.0, 0.0.into()), ActionState::None); assert_eq!( - condition.evaluate(&ctx, 0.0, 1.0.into()), + condition.evaluate(&actions, &time, 0.0.into()), + ActionState::None + ); + + time.advance_by(Duration::ZERO); + assert_eq!( + condition.evaluate(&actions, &time, 1.0.into()), ActionState::Ongoing, ); } #[test] fn one_shot() { - let ctx = ActionContext { - world: &World::new(), - actions: &ActionsData::default(), - entities: &[], - }; - let mut hold = Hold::new(1.0).one_shot(true); - assert_eq!(hold.evaluate(&ctx, 1.0, 1.0.into()), ActionState::Fired); - assert_eq!(hold.evaluate(&ctx, 1.0, 1.0.into()), ActionState::None); + let actions = ActionsData::default(); + let mut time = Time::default(); + time.advance_by(Duration::from_secs(1)); + + assert_eq!( + hold.evaluate(&actions, &time, 1.0.into()), + ActionState::Fired + ); + assert_eq!( + hold.evaluate(&actions, &time, 1.0.into()), + ActionState::None + ); } } diff --git a/src/input_context/input_condition/hold_and_release.rs b/src/input_context/input_condition/hold_and_release.rs index 8d6fba2..1c3343c 100644 --- a/src/input_context/input_condition/hold_and_release.rs +++ b/src/input_context/input_condition/hold_and_release.rs @@ -1,7 +1,9 @@ +use bevy::prelude::*; + use super::{condition_timer::ConditionTimer, InputCondition, DEFAULT_ACTUATION}; use crate::{ action_value::ActionValue, - input_context::{context_instance::ActionContext, input_action::ActionState}, + input_context::input_action::{ActionState, ActionsData}, }; /// Returns [`ActionState::Ongoing`] when input becomes actuated and [`ActionState::Fired`] @@ -44,11 +46,16 @@ impl HoldAndRelease { } impl InputCondition for HoldAndRelease { - fn evaluate(&mut self, ctx: &ActionContext, delta: f32, value: ActionValue) -> ActionState { + fn evaluate( + &mut self, + _actions: &ActionsData, + time: &Time, + value: ActionValue, + ) -> ActionState { // Evaluate the updated held duration prior to checking for actuation. // This stops us failing to trigger if the input is released on the // threshold frame due to held duration being 0. - self.timer.update(ctx.world, delta); + self.timer.update(time); let held_duration = self.timer.duration(); if value.is_actuated(self.actuation) { @@ -67,29 +74,36 @@ impl InputCondition for HoldAndRelease { #[cfg(test)] mod tests { - use bevy::prelude::*; + use std::time::Duration; use super::*; use crate::input_context::input_action::ActionsData; #[test] fn hold_and_release() { - let ctx = ActionContext { - world: &World::new(), - actions: &ActionsData::default(), - entities: &[], - }; + let mut condition = HoldAndRelease::new(1.0); + let actions = ActionsData::default(); + let mut time = Time::default(); - let mut modifier = HoldAndRelease::new(1.0); assert_eq!( - modifier.evaluate(&ctx, 0.0, 1.0.into()), + condition.evaluate(&actions, &time, 1.0.into()), ActionState::Ongoing, ); - assert_eq!(modifier.evaluate(&ctx, 1.0, 0.0.into()), ActionState::Fired); + + time.advance_by(Duration::from_secs(1)); assert_eq!( - modifier.evaluate(&ctx, 0.0, 1.0.into()), + condition.evaluate(&actions, &time, 0.0.into()), + ActionState::Fired + ); + + time.advance_by(Duration::ZERO); + assert_eq!( + condition.evaluate(&actions, &time, 1.0.into()), ActionState::Ongoing, ); - assert_eq!(modifier.evaluate(&ctx, 0.0, 0.0.into()), ActionState::None); + assert_eq!( + condition.evaluate(&actions, &time, 0.0.into()), + ActionState::None + ); } } diff --git a/src/input_context/input_condition/pressed.rs b/src/input_context/input_condition/pressed.rs index b111826..611382a 100644 --- a/src/input_context/input_condition/pressed.rs +++ b/src/input_context/input_condition/pressed.rs @@ -1,7 +1,9 @@ +use bevy::prelude::*; + use super::{InputCondition, DEFAULT_ACTUATION}; use crate::{ action_value::ActionValue, - input_context::{context_instance::ActionContext, input_action::ActionState}, + input_context::input_action::{ActionState, ActionsData}, }; /// Like [`super::down::Down`] but returns [`ActionState::Fired`] only once until the next actuation. @@ -31,7 +33,12 @@ impl Default for Pressed { } impl InputCondition for Pressed { - fn evaluate(&mut self, _ctx: &ActionContext, _delta: f32, value: ActionValue) -> ActionState { + fn evaluate( + &mut self, + _actions: &ActionsData, + _time: &Time, + value: ActionValue, + ) -> ActionState { let previosly_actuated = self.actuated; self.actuated = value.is_actuated(self.actuation); @@ -45,23 +52,21 @@ impl InputCondition for Pressed { #[cfg(test)] mod tests { - use bevy::prelude::*; - use super::*; use crate::input_context::input_action::ActionsData; #[test] fn pressed() { - let ctx = ActionContext { - world: &World::new(), - actions: &ActionsData::default(), - entities: &[], - }; - let mut condition = Pressed::default(); - assert_eq!(condition.evaluate(&ctx, 0.0, 0.0.into()), ActionState::None); + let actions = ActionsData::default(); + let time = Time::default(); + + assert_eq!( + condition.evaluate(&actions, &time, 0.0.into()), + ActionState::None + ); assert_eq!( - condition.evaluate(&ctx, 0.0, 1.0.into()), + condition.evaluate(&actions, &time, 1.0.into()), ActionState::Fired, ); } diff --git a/src/input_context/input_condition/pulse.rs b/src/input_context/input_condition/pulse.rs index 192c8cd..6c3c2fd 100644 --- a/src/input_context/input_condition/pulse.rs +++ b/src/input_context/input_condition/pulse.rs @@ -1,7 +1,9 @@ +use bevy::prelude::*; + use super::{condition_timer::ConditionTimer, InputCondition, DEFAULT_ACTUATION}; use crate::{ action_value::ActionValue, - input_context::{context_instance::ActionContext, input_action::ActionState}, + input_context::input_action::{ActionState, ActionsData}, }; /// Returns [`ActionState::Ongoing`] when input becomes actuated and [`ActionState::Fired`] @@ -69,9 +71,14 @@ impl Pulse { } impl InputCondition for Pulse { - fn evaluate(&mut self, ctx: &ActionContext, delta: f32, value: ActionValue) -> ActionState { + fn evaluate( + &mut self, + _actions: &ActionsData, + time: &Time, + value: ActionValue, + ) -> ActionState { if value.is_actuated(self.actuation) { - self.timer.update(ctx.world, delta); + self.timer.update(time); if self.trigger_limit == 0 || self.trigger_count < self.trigger_limit { let trigger_count = if self.trigger_on_start { @@ -102,67 +109,68 @@ impl InputCondition for Pulse { #[cfg(test)] mod tests { - use bevy::prelude::*; + use std::time::Duration; use super::*; use crate::input_context::input_action::ActionsData; #[test] fn tap() { - let ctx = ActionContext { - world: &World::new(), - actions: &ActionsData::default(), - entities: &[], - }; - let mut condition = Pulse::new(1.0); + let actions = ActionsData::default(); + let mut time = Time::default(); + assert_eq!( - condition.evaluate(&ctx, 0.0, 1.0.into()), + condition.evaluate(&actions, &time, 1.0.into()), ActionState::Fired, ); + + time.advance_by(Duration::from_millis(500)); assert_eq!( - condition.evaluate(&ctx, 0.5, 1.0.into()), + condition.evaluate(&actions, &time, 1.0.into()), ActionState::Ongoing, ); assert_eq!( - condition.evaluate(&ctx, 0.5, 1.0.into()), + condition.evaluate(&actions, &time, 1.0.into()), ActionState::Fired, ); + + time.advance_by(Duration::ZERO); assert_eq!( - condition.evaluate(&ctx, 0.0, 1.0.into()), + condition.evaluate(&actions, &time, 1.0.into()), ActionState::Ongoing, ); - assert_eq!(condition.evaluate(&ctx, 0.0, 0.0.into()), ActionState::None); + assert_eq!( + condition.evaluate(&actions, &time, 0.0.into()), + ActionState::None + ); } #[test] fn not_trigger_on_start() { - let ctx = ActionContext { - world: &World::new(), - actions: &ActionsData::default(), - entities: &[], - }; - let mut condition = Pulse::new(1.0).trigger_on_start(false); + let actions = ActionsData::default(); + let time = Time::default(); + assert_eq!( - condition.evaluate(&ctx, 0.0, 1.0.into()), + condition.evaluate(&actions, &time, 1.0.into()), ActionState::Ongoing, ); } #[test] fn trigger_limit() { - let ctx = ActionContext { - world: &World::new(), - actions: &ActionsData::default(), - entities: &[], - }; - let mut condition = Pulse::new(1.0).with_trigger_limit(1); + let actions = ActionsData::default(); + let time = Time::default(); + assert_eq!( - condition.evaluate(&ctx, 0.0, 1.0.into()), + condition.evaluate(&actions, &time, 1.0.into()), ActionState::Fired, ); - assert_eq!(condition.evaluate(&ctx, 0.0, 1.0.into()), ActionState::None); + assert_eq!( + condition.evaluate(&actions, &time, 1.0.into()), + ActionState::None + ); } } diff --git a/src/input_context/input_condition/released.rs b/src/input_context/input_condition/released.rs index 4e4ca91..43decce 100644 --- a/src/input_context/input_condition/released.rs +++ b/src/input_context/input_condition/released.rs @@ -1,7 +1,9 @@ +use bevy::prelude::*; + use super::{InputCondition, DEFAULT_ACTUATION}; use crate::{ action_value::ActionValue, - input_context::{context_instance::ActionContext, input_action::ActionState}, + input_context::input_action::{ActionState, ActionsData}, }; /// Returns [`ActionState::Ongoing`]` when the input exceeds the actuation threshold and @@ -30,7 +32,12 @@ impl Default for Released { } impl InputCondition for Released { - fn evaluate(&mut self, _ctx: &ActionContext, _delta: f32, value: ActionValue) -> ActionState { + fn evaluate( + &mut self, + _actions: &ActionsData, + _time: &Time, + value: ActionValue, + ) -> ActionState { let previosly_actuated = self.actuated; self.actuated = value.is_actuated(self.actuation); @@ -48,28 +55,25 @@ impl InputCondition for Released { #[cfg(test)] mod tests { - use bevy::prelude::*; - use super::*; - use crate::input_context::input_action::ActionsData; #[test] fn released() { - let ctx = ActionContext { - world: &World::new(), - actions: &ActionsData::default(), - entities: &[], - }; - let mut condition = Released::default(); - assert_eq!(condition.evaluate(&ctx, 0.0, 0.0.into()), ActionState::None); + let actions = ActionsData::default(); + let time = Time::default(); + assert_eq!( - condition.evaluate(&ctx, 0.0, 1.0.into()), - ActionState::Ongoing, + condition.evaluate(&actions, &time, 0.0.into()), + ActionState::None ); assert_eq!( - condition.evaluate(&ctx, 0.0, 0.0.into()), - ActionState::Fired, + condition.evaluate(&actions, &time, 1.0.into()), + ActionState::Ongoing + ); + assert_eq!( + condition.evaluate(&actions, &time, 0.0.into()), + ActionState::Fired ); } } diff --git a/src/input_context/input_condition/tap.rs b/src/input_context/input_condition/tap.rs index a4435d7..2959423 100644 --- a/src/input_context/input_condition/tap.rs +++ b/src/input_context/input_condition/tap.rs @@ -1,7 +1,9 @@ +use bevy::prelude::*; + use super::{condition_timer::ConditionTimer, InputCondition, DEFAULT_ACTUATION}; use crate::{ action_value::ActionValue, - input_context::{context_instance::ActionContext, input_action::ActionState}, + input_context::input_action::{ActionState, ActionsData}, }; /// Returns [`ActionState::Ongoing`] when input becomes actuated and [`ActionState::Fired`] @@ -46,12 +48,17 @@ impl Tap { } impl InputCondition for Tap { - fn evaluate(&mut self, ctx: &ActionContext, delta: f32, value: ActionValue) -> ActionState { + fn evaluate( + &mut self, + _actions: &ActionsData, + time: &Time, + value: ActionValue, + ) -> ActionState { let last_actuated = self.actuated; let last_held_duration = self.timer.duration(); self.actuated = value.is_actuated(self.actuation); if self.actuated { - self.timer.update(ctx.world, delta); + self.timer.update(time); } else { self.timer.reset(); } @@ -72,29 +79,37 @@ impl InputCondition for Tap { #[cfg(test)] mod tests { - use bevy::prelude::*; + use std::time::Duration; use super::*; - use crate::input_context::input_action::ActionsData; #[test] fn tap() { - let ctx = ActionContext { - world: &World::new(), - actions: &ActionsData::default(), - entities: &[], - }; - let mut condition = Tap::new(1.0); + let actions = ActionsData::default(); + let mut time = Time::default(); + assert_eq!( - condition.evaluate(&ctx, 0.0, 1.0.into()), + condition.evaluate(&actions, &time, 1.0.into()), ActionState::Ongoing, ); + + time.advance_by(Duration::from_secs(1)); assert_eq!( - condition.evaluate(&ctx, 1.0, 0.0.into()), + condition.evaluate(&actions, &time, 0.0.into()), ActionState::Fired, ); - assert_eq!(condition.evaluate(&ctx, 0.0, 0.0.into()), ActionState::None); - assert_eq!(condition.evaluate(&ctx, 2.0, 1.0.into()), ActionState::None); + + time.advance_by(Duration::ZERO); + assert_eq!( + condition.evaluate(&actions, &time, 0.0.into()), + ActionState::None + ); + + time.advance_by(Duration::from_secs(2)); + assert_eq!( + condition.evaluate(&actions, &time, 1.0.into()), + ActionState::None + ); } } diff --git a/src/input_context/input_modifier.rs b/src/input_context/input_modifier.rs index d1df764..06ad317 100644 --- a/src/input_context/input_modifier.rs +++ b/src/input_context/input_modifier.rs @@ -9,52 +9,25 @@ pub mod swizzle_axis; use std::fmt::Debug; -use super::context_instance::ActionContext; +use bevy::prelude::*; + use crate::action_value::ActionValue; /// Pre-processor that alter the raw input values. /// /// Input modifiers are useful for applying sensitivity settings, smoothing input over multiple frames, -/// or changing how input behaves based on the state of the player. +/// or changing how input maps to axes. /// /// Modifiers should preserve the original value dimention. /// /// Can be applied both to inputs and actions. /// See [`ActionBind::with_modifier`](super::context_instance::ActionBind::with_modifier) /// and [`InputBind::with_modifier`](super::context_instance::InputBind::with_modifier). -/// -/// You can create game-specific modifiers: -/// -/// ``` -/// # use bevy::prelude::*; -/// use bevy_enhanced_input::{ignore_incompatible, prelude::*}; -/// -/// /// Input modifier that applies sensitivity from [`Settings`]. -/// #[derive(Debug, Clone, Copy)] -/// struct Sensetivity; -/// -/// impl InputModifier for Sensetivity { -/// fn apply(&mut self, ctx: &ActionContext, _delta: f32, value: ActionValue) -> ActionValue { -/// let dim = value.dim(); -/// if dim == ActionValueDim::Bool { -/// ignore_incompatible!(value); -/// } -/// -/// let settings = ctx.world.resource::(); -/// ActionValue::Axis3D(value.as_axis3d() * settings.sensitivity).convert(dim) -/// } -/// } -/// -/// #[derive(Resource)] -/// struct Settings { -/// sensitivity: f32, -/// } -/// ``` pub trait InputModifier: Sync + Send + Debug + 'static { /// Returns pre-processed value. /// /// Called each frame. - fn apply(&mut self, ctx: &ActionContext, delta: f32, value: ActionValue) -> ActionValue; + fn apply(&mut self, time: &Time, value: ActionValue) -> ActionValue; } /// Simple helper to emit a warning if a dimension is not compatible with a modifier. diff --git a/src/input_context/input_modifier/dead_zone.rs b/src/input_context/input_modifier/dead_zone.rs index 184cc5f..6a38b0c 100644 --- a/src/input_context/input_modifier/dead_zone.rs +++ b/src/input_context/input_modifier/dead_zone.rs @@ -1,7 +1,7 @@ use bevy::prelude::*; use super::{ignore_incompatible, InputModifier}; -use crate::{action_value::ActionValue, input_context::context_instance::ActionContext}; +use crate::action_value::ActionValue; /// Input values within the range [Self::lower_threshold] -> [Self::upper_threshold] will be remapped from 0 -> 1. /// Values outside this range will be clamped. @@ -55,7 +55,7 @@ impl Default for DeadZone { } impl InputModifier for DeadZone { - fn apply(&mut self, _ctx: &ActionContext, _delta: f32, value: ActionValue) -> ActionValue { + fn apply(&mut self, _time: &Time, value: ActionValue) -> ActionValue { match value { ActionValue::Bool(_) => { ignore_incompatible!(value); @@ -105,90 +105,73 @@ pub enum DeadZoneKind { #[cfg(test)] mod tests { use super::*; - use crate::input_context::input_action::ActionsData; #[test] fn radial() { - let ctx = ActionContext { - world: &World::new(), - actions: &ActionsData::default(), - entities: &[], - }; - let mut modifier = DeadZone::new(DeadZoneKind::Radial); + let time = Time::default(); - assert_eq!(modifier.apply(&ctx, 0.0, true.into()), true.into()); - assert_eq!(modifier.apply(&ctx, 0.0, 1.0.into()), 1.0.into()); - assert_eq!(modifier.apply(&ctx, 0.0, 0.5.into()), 0.375.into()); - assert_eq!(modifier.apply(&ctx, 0.0, 0.2.into()), 0.0.into()); - assert_eq!(modifier.apply(&ctx, 0.0, 2.0.into()), 1.0.into()); + assert_eq!(modifier.apply(&time, true.into()), true.into()); + assert_eq!(modifier.apply(&time, 1.0.into()), 1.0.into()); + assert_eq!(modifier.apply(&time, 0.5.into()), 0.375.into()); + assert_eq!(modifier.apply(&time, 0.2.into()), 0.0.into()); + assert_eq!(modifier.apply(&time, 2.0.into()), 1.0.into()); assert_eq!( - modifier.apply(&ctx, 0.0, (Vec2::ONE * 0.5).into()), + modifier.apply(&time, (Vec2::ONE * 0.5).into()), (Vec2::ONE * 0.4482233).into() ); assert_eq!( - modifier.apply(&ctx, 0.0, Vec2::ONE.into()), + modifier.apply(&time, Vec2::ONE.into()), (Vec2::ONE * 0.70710677).into() ); assert_eq!( - modifier.apply(&ctx, 0.0, (Vec2::ONE * 0.2).into()), + modifier.apply(&time, (Vec2::ONE * 0.2).into()), (Vec2::ONE * 0.07322331).into() ); assert_eq!( - modifier.apply(&ctx, 0.0, (Vec3::ONE * 0.5).into()), + modifier.apply(&time, (Vec3::ONE * 0.5).into()), (Vec3::ONE * 0.48066244).into() ); assert_eq!( - modifier.apply(&ctx, 0.0, Vec3::ONE.into()), + modifier.apply(&time, Vec3::ONE.into()), (Vec3::ONE * 0.57735026).into() ); assert_eq!( - modifier.apply(&ctx, 0.0, (Vec3::ONE * 0.2).into()), + modifier.apply(&time, (Vec3::ONE * 0.2).into()), (Vec3::ONE * 0.105662435).into() ); } #[test] fn axial() { - let ctx = ActionContext { - world: &World::new(), - actions: &ActionsData::default(), - entities: &[], - }; - - let mut dead_zone = DeadZone::new(DeadZoneKind::Axial); + let mut modifier = DeadZone::new(DeadZoneKind::Axial); + let time = Time::default(); - assert_eq!(dead_zone.apply(&ctx, 0.0, true.into()), true.into()); - assert_eq!(dead_zone.apply(&ctx, 0.0, 1.0.into()), 1.0.into()); - assert_eq!(dead_zone.apply(&ctx, 0.0, 0.5.into()), 0.375.into()); - assert_eq!(dead_zone.apply(&ctx, 0.0, 0.2.into()), 0.0.into()); - assert_eq!(dead_zone.apply(&ctx, 0.0, 2.0.into()), 1.0.into()); + assert_eq!(modifier.apply(&time, true.into()), true.into()); + assert_eq!(modifier.apply(&time, 1.0.into()), 1.0.into()); + assert_eq!(modifier.apply(&time, 0.5.into()), 0.375.into()); + assert_eq!(modifier.apply(&time, 0.2.into()), 0.0.into()); + assert_eq!(modifier.apply(&time, 2.0.into()), 1.0.into()); assert_eq!( - dead_zone.apply(&ctx, 0.0, (Vec2::ONE * 0.5).into()), + modifier.apply(&time, (Vec2::ONE * 0.5).into()), (Vec2::ONE * 0.375).into() ); + assert_eq!(modifier.apply(&time, Vec2::ONE.into()), Vec2::ONE.into()); assert_eq!( - dead_zone.apply(&ctx, 0.0, Vec2::ONE.into()), - Vec2::ONE.into() - ); - assert_eq!( - dead_zone.apply(&ctx, 0.0, (Vec2::ONE * 0.2).into()), + modifier.apply(&time, (Vec2::ONE * 0.2).into()), Vec2::ZERO.into() ); assert_eq!( - dead_zone.apply(&ctx, 0.0, (Vec3::ONE * 0.5).into()), + modifier.apply(&time, (Vec3::ONE * 0.5).into()), (Vec3::ONE * 0.375).into() ); + assert_eq!(modifier.apply(&time, Vec3::ONE.into()), Vec3::ONE.into()); assert_eq!( - dead_zone.apply(&ctx, 0.0, Vec3::ONE.into()), - Vec3::ONE.into() - ); - assert_eq!( - dead_zone.apply(&ctx, 0.0, (Vec3::ONE * 0.2).into()), + modifier.apply(&time, (Vec3::ONE * 0.2).into()), Vec3::ZERO.into() ); } diff --git a/src/input_context/input_modifier/exponential_curve.rs b/src/input_context/input_modifier/exponential_curve.rs index c5c7855..35f240e 100644 --- a/src/input_context/input_modifier/exponential_curve.rs +++ b/src/input_context/input_modifier/exponential_curve.rs @@ -1,10 +1,7 @@ use bevy::prelude::*; use super::{ignore_incompatible, InputModifier}; -use crate::{ - action_value::{ActionValue, ActionValueDim}, - input_context::context_instance::ActionContext, -}; +use crate::action_value::{ActionValue, ActionValueDim}; /// Response curve exponential. /// @@ -31,7 +28,7 @@ impl ExponentialCurve { } impl InputModifier for ExponentialCurve { - fn apply(&mut self, _ctx: &ActionContext, _delta: f32, value: ActionValue) -> ActionValue { + fn apply(&mut self, _time: &Time, value: ActionValue) -> ActionValue { let dim = value.dim(); if dim == ActionValueDim::Bool { ignore_incompatible!(value); @@ -48,26 +45,21 @@ impl InputModifier for ExponentialCurve { #[cfg(test)] mod tests { use super::*; - use crate::input_context::input_action::ActionsData; #[test] fn exp() { - let ctx = ActionContext { - world: &World::new(), - actions: &ActionsData::default(), - entities: &[], - }; - + let time = Time::default(); let mut modifier = ExponentialCurve::splat(2.0); - assert_eq!(modifier.apply(&ctx, 0.0, true.into()), true.into()); - assert_eq!(modifier.apply(&ctx, 0.0, (-0.5).into()), (-0.25).into()); - assert_eq!(modifier.apply(&ctx, 0.0, 0.5.into()), 0.25.into()); + + assert_eq!(modifier.apply(&time, true.into()), true.into()); + assert_eq!(modifier.apply(&time, (-0.5).into()), (-0.25).into()); + assert_eq!(modifier.apply(&time, 0.5.into()), 0.25.into()); assert_eq!( - modifier.apply(&ctx, 0.0, (Vec2::ONE * 2.0).into()), + modifier.apply(&time, (Vec2::ONE * 2.0).into()), (Vec2::ONE * 4.0).into() ); assert_eq!( - modifier.apply(&ctx, 0.0, (Vec3::ONE * 2.0).into()), + modifier.apply(&time, (Vec3::ONE * 2.0).into()), (Vec3::ONE * 4.0).into() ); } diff --git a/src/input_context/input_modifier/negate.rs b/src/input_context/input_modifier/negate.rs index 35b94e8..88fbc7e 100644 --- a/src/input_context/input_modifier/negate.rs +++ b/src/input_context/input_modifier/negate.rs @@ -1,7 +1,7 @@ use bevy::prelude::*; use super::InputModifier; -use crate::{action_value::ActionValue, input_context::context_instance::ActionContext}; +use crate::action_value::ActionValue; /// Inverts value per axis. /// @@ -67,7 +67,7 @@ impl Default for Negate { } impl InputModifier for Negate { - fn apply(&mut self, _ctx: &ActionContext, _delta: f32, value: ActionValue) -> ActionValue { + fn apply(&mut self, _time: &Time, value: ActionValue) -> ActionValue { let x = if self.x { -1.0 } else { 1.0 }; let y = if self.y { -1.0 } else { 1.0 }; let z = if self.z { -1.0 } else { 1.0 }; @@ -80,30 +80,25 @@ impl InputModifier for Negate { #[cfg(test)] mod tests { use super::*; - use crate::input_context::input_action::ActionsData; #[test] fn negation() { - let ctx = ActionContext { - world: &World::new(), - actions: &ActionsData::default(), - entities: &[], - }; + let time = Time::default(); assert_eq!( - Negate::default().apply(&ctx, 0.0, Vec3::ONE.into()), + Negate::default().apply(&time, Vec3::ONE.into()), Vec3::NEG_ONE.into(), ); assert_eq!( - Negate::x(true).apply(&ctx, 0.0, Vec3::ONE.into()), + Negate::x(true).apply(&time, Vec3::ONE.into()), (-1.0, 1.0, 1.0).into(), ); assert_eq!( - Negate::y(true).apply(&ctx, 0.0, Vec3::ONE.into()), + Negate::y(true).apply(&time, Vec3::ONE.into()), (1.0, -1.0, 1.0).into(), ); assert_eq!( - Negate::z(true).apply(&ctx, 0.0, Vec3::ONE.into()), + Negate::z(true).apply(&time, Vec3::ONE.into()), (1.0, 1.0, -1.0).into(), ); } diff --git a/src/input_context/input_modifier/normalize.rs b/src/input_context/input_modifier/normalize.rs index b442679..e655bf2 100644 --- a/src/input_context/input_modifier/normalize.rs +++ b/src/input_context/input_modifier/normalize.rs @@ -1,17 +1,14 @@ use bevy::prelude::*; use super::{ignore_incompatible, InputModifier}; -use crate::{ - action_value::{ActionValue, ActionValueDim}, - input_context::context_instance::ActionContext, -}; +use crate::action_value::{ActionValue, ActionValueDim}; /// Normalizes input if possible or returns zero. #[derive(Clone, Copy, Debug)] pub struct Normalize; impl InputModifier for Normalize { - fn apply(&mut self, _ctx: &ActionContext, _delta: f32, value: ActionValue) -> ActionValue { + fn apply(&mut self, _time: &Time, value: ActionValue) -> ActionValue { let dim = value.dim(); if dim == ActionValueDim::Bool || dim == ActionValueDim::Axis1D { ignore_incompatible!(value); @@ -25,28 +22,20 @@ impl InputModifier for Normalize { #[cfg(test)] mod tests { use super::*; - use crate::input_context::input_action::ActionsData; #[test] fn normalization() { - let ctx = ActionContext { - world: &World::new(), - actions: &ActionsData::default(), - entities: &[], - }; + let time = Time::default(); - assert_eq!(Normalize.apply(&ctx, 0.0, true.into()), true.into()); - assert_eq!(Normalize.apply(&ctx, 0.0, 0.5.into()), 0.5.into()); + assert_eq!(Normalize.apply(&time, true.into()), true.into()); + assert_eq!(Normalize.apply(&time, 0.5.into()), 0.5.into()); + assert_eq!(Normalize.apply(&time, Vec2::ZERO.into()), Vec2::ZERO.into(),); assert_eq!( - Normalize.apply(&ctx, 0.0, Vec2::ZERO.into()), - Vec2::ZERO.into(), - ); - assert_eq!( - Normalize.apply(&ctx, 0.0, Vec2::ONE.into()), + Normalize.apply(&time, Vec2::ONE.into()), Vec2::ONE.normalize_or_zero().into(), ); assert_eq!( - Normalize.apply(&ctx, 0.0, Vec3::ONE.into()), + Normalize.apply(&time, Vec3::ONE.into()), Vec3::ONE.normalize_or_zero().into(), ); } diff --git a/src/input_context/input_modifier/scalar.rs b/src/input_context/input_modifier/scalar.rs index c9a056e..fb289e1 100644 --- a/src/input_context/input_modifier/scalar.rs +++ b/src/input_context/input_modifier/scalar.rs @@ -1,10 +1,7 @@ use bevy::prelude::*; use super::{ignore_incompatible, InputModifier}; -use crate::{ - action_value::{ActionValue, ActionValueDim}, - input_context::context_instance::ActionContext, -}; +use crate::action_value::{ActionValue, ActionValueDim}; /// Scales input by a set factor per axis. /// @@ -33,7 +30,7 @@ impl Scalar { } impl InputModifier for Scalar { - fn apply(&mut self, _ctx: &ActionContext, _delta: f32, value: ActionValue) -> ActionValue { + fn apply(&mut self, _time: &Time, value: ActionValue) -> ActionValue { let dim = value.dim(); if dim == ActionValueDim::Bool { ignore_incompatible!(value); @@ -46,25 +43,17 @@ impl InputModifier for Scalar { #[cfg(test)] mod tests { use super::*; - use crate::input_context::input_action::ActionsData; #[test] fn scaling() { - let ctx = ActionContext { - world: &World::new(), - actions: &ActionsData::default(), - entities: &[], - }; - let mut modifier = Scalar::splat(2.0); - assert_eq!(modifier.apply(&ctx, 0.0, true.into()), true.into()); - assert_eq!(modifier.apply(&ctx, 0.0, 1.0.into()), 2.0.into()); - assert_eq!( - modifier.apply(&ctx, 0.0, Vec2::ONE.into()), - (2.0, 2.0).into() - ); + let time = Time::default(); + + assert_eq!(modifier.apply(&time, true.into()), true.into()); + assert_eq!(modifier.apply(&time, 1.0.into()), 2.0.into()); + assert_eq!(modifier.apply(&time, Vec2::ONE.into()), (2.0, 2.0).into()); assert_eq!( - modifier.apply(&ctx, 0.0, Vec3::ONE.into()), + modifier.apply(&time, Vec3::ONE.into()), (2.0, 2.0, 2.0).into() ); } diff --git a/src/input_context/input_modifier/scale_by_delta.rs b/src/input_context/input_modifier/scale_by_delta.rs index dd5b7d6..e814697 100644 --- a/src/input_context/input_modifier/scale_by_delta.rs +++ b/src/input_context/input_modifier/scale_by_delta.rs @@ -1,10 +1,7 @@ use bevy::prelude::*; use super::{ignore_incompatible, InputModifier}; -use crate::{ - action_value::{ActionValue, ActionValueDim}, - input_context::context_instance::ActionContext, -}; +use crate::action_value::{ActionValue, ActionValueDim}; /// Multiplies the input value by delta time for this frame. /// @@ -13,38 +10,35 @@ use crate::{ pub struct ScaleByDelta; impl InputModifier for ScaleByDelta { - fn apply(&mut self, _ctx: &ActionContext, delta: f32, value: ActionValue) -> ActionValue { + fn apply(&mut self, time: &Time, value: ActionValue) -> ActionValue { let dim = value.dim(); if dim == ActionValueDim::Bool { ignore_incompatible!(value); } - ActionValue::Axis3D(value.as_axis3d() * delta).convert(dim) + ActionValue::Axis3D(value.as_axis3d() * time.delta_seconds()).convert(dim) } } #[cfg(test)] mod tests { + use std::time::Duration; + use super::*; - use crate::input_context::input_action::ActionsData; #[test] fn scaling() { - let ctx = ActionContext { - world: &World::new(), - actions: &ActionsData::default(), - entities: &[], - }; - - let delta = 0.5; - assert_eq!(ScaleByDelta.apply(&ctx, delta, true.into()), true.into()); - assert_eq!(ScaleByDelta.apply(&ctx, delta, 0.5.into()), 0.25.into()); + let mut time = Time::default(); + time.advance_by(Duration::from_millis(500)); + + assert_eq!(ScaleByDelta.apply(&time, true.into()), true.into()); + assert_eq!(ScaleByDelta.apply(&time, 0.5.into()), 0.25.into()); assert_eq!( - ScaleByDelta.apply(&ctx, delta, Vec2::ONE.into()), + ScaleByDelta.apply(&time, Vec2::ONE.into()), (0.5, 0.5).into() ); assert_eq!( - ScaleByDelta.apply(&ctx, delta, Vec3::ONE.into()), + ScaleByDelta.apply(&time, Vec3::ONE.into()), (0.5, 0.5, 0.5).into() ); } diff --git a/src/input_context/input_modifier/smooth_delta.rs b/src/input_context/input_modifier/smooth_delta.rs index 9e688ba..e19780e 100644 --- a/src/input_context/input_modifier/smooth_delta.rs +++ b/src/input_context/input_modifier/smooth_delta.rs @@ -3,10 +3,7 @@ use interpolation::Ease; pub use interpolation::EaseFunction; use super::{ignore_incompatible, InputModifier}; -use crate::{ - action_value::{ActionValue, ActionValueDim}, - input_context::context_instance::ActionContext, -}; +use crate::action_value::{ActionValue, ActionValueDim}; /// Normalized smooth delta /// @@ -39,7 +36,7 @@ impl SmoothDelta { } impl InputModifier for SmoothDelta { - fn apply(&mut self, _ctx: &ActionContext, delta: f32, value: ActionValue) -> ActionValue { + fn apply(&mut self, time: &Time, value: ActionValue) -> ActionValue { let dim = value.dim(); if dim == ActionValueDim::Bool { ignore_incompatible!(value); @@ -49,7 +46,7 @@ impl InputModifier for SmoothDelta { let target_value_delta = (value - self.old_value).normalize_or_zero(); self.old_value = value; - let alpha = (delta * self.speed).min(1.0); + let alpha = (time.delta_seconds() * self.speed).min(1.0); self.value_delta = match self.kind { SmoothKind::EaseFunction(ease_function) => { let ease_alpha = alpha.calc(ease_function); @@ -81,36 +78,29 @@ impl From for SmoothKind { #[cfg(test)] mod tests { + use std::time::Duration; + use super::*; - use crate::input_context::input_action::ActionsData; #[test] fn linear() { - let ctx = ActionContext { - world: &World::new(), - actions: &ActionsData::default(), - entities: &[], - }; - let mut modifier = SmoothDelta::new(SmoothKind::Linear, 1.0); - let delta = 0.1; - assert_eq!(modifier.apply(&ctx, delta, true.into()), true.into()); - assert_eq!(modifier.apply(&ctx, delta, 0.5.into()), 0.1.into()); - assert_eq!(modifier.apply(&ctx, delta, 1.0.into()), 0.19.into()); + let mut time = Time::default(); + time.advance_by(Duration::from_millis(100)); + + assert_eq!(modifier.apply(&time, true.into()), true.into()); + assert_eq!(modifier.apply(&time, 0.5.into()), 0.1.into()); + assert_eq!(modifier.apply(&time, 1.0.into()), 0.19.into()); } #[test] fn ease_function() { - let ctx = ActionContext { - world: &World::new(), - actions: &ActionsData::default(), - entities: &[], - }; - let mut modifier = SmoothDelta::new(EaseFunction::QuadraticIn, 1.0); - let delta = 0.2; - assert_eq!(modifier.apply(&ctx, delta, true.into()), true.into()); - assert_eq!(modifier.apply(&ctx, delta, 0.5.into()), 0.040000003.into()); - assert_eq!(modifier.apply(&ctx, delta, 1.0.into()), 0.0784.into()); + let mut time = Time::default(); + time.advance_by(Duration::from_millis(200)); + + assert_eq!(modifier.apply(&time, true.into()), true.into()); + assert_eq!(modifier.apply(&time, 0.5.into()), 0.040000003.into()); + assert_eq!(modifier.apply(&time, 1.0.into()), 0.0784.into()); } } diff --git a/src/input_context/input_modifier/swizzle_axis.rs b/src/input_context/input_modifier/swizzle_axis.rs index 52b8f3e..fdfa65d 100644 --- a/src/input_context/input_modifier/swizzle_axis.rs +++ b/src/input_context/input_modifier/swizzle_axis.rs @@ -1,7 +1,7 @@ use bevy::prelude::*; use super::{ignore_incompatible, InputModifier}; -use crate::{action_value::ActionValue, input_context::context_instance::ActionContext}; +use crate::action_value::ActionValue; /// Swizzle axis components of an input value. /// @@ -29,7 +29,7 @@ pub enum SwizzleAxis { } impl InputModifier for SwizzleAxis { - fn apply(&mut self, _ctx: &ActionContext, _delta: f32, value: ActionValue) -> ActionValue { + fn apply(&mut self, _time: &Time, value: ActionValue) -> ActionValue { match value { ActionValue::Bool(_) | ActionValue::Axis1D(_) => { ignore_incompatible!(value); @@ -61,172 +61,115 @@ impl InputModifier for SwizzleAxis { #[cfg(test)] mod tests { use super::*; - use crate::input_context::input_action::ActionsData; #[test] fn yxz() { - let ctx = ActionContext { - world: &World::new(), - actions: &ActionsData::default(), - entities: &[], - }; - let mut swizzle = SwizzleAxis::YXZ; - assert_eq!(swizzle.apply(&ctx, 0.0, true.into()), true.into()); - assert_eq!(swizzle.apply(&ctx, 0.0, 1.0.into()), 1.0.into()); - assert_eq!( - swizzle.apply(&ctx, 0.0, (0.0, 1.0).into()), - (1.0, 0.0).into(), - ); + let time = Time::default(); + + assert_eq!(swizzle.apply(&time, true.into()), true.into()); + assert_eq!(swizzle.apply(&time, 1.0.into()), 1.0.into()); + assert_eq!(swizzle.apply(&time, (0.0, 1.0).into()), (1.0, 0.0).into(),); assert_eq!( - swizzle.apply(&ctx, 0.0, (0.0, 1.0, 2.0).into()), + swizzle.apply(&time, (0.0, 1.0, 2.0).into()), (1.0, 0.0, 2.0).into(), ); } #[test] fn zyx() { - let ctx = ActionContext { - world: &World::new(), - actions: &ActionsData::default(), - entities: &[], - }; - let mut swizzle = SwizzleAxis::ZYX; - assert_eq!(swizzle.apply(&ctx, 0.0, true.into()), true.into()); - assert_eq!(swizzle.apply(&ctx, 0.0, 1.0.into()), 1.0.into()); - assert_eq!( - swizzle.apply(&ctx, 0.0, (0.0, 1.0).into()), - (0.0, 1.0).into(), - ); + let time = Time::default(); + + assert_eq!(swizzle.apply(&time, true.into()), true.into()); + assert_eq!(swizzle.apply(&time, 1.0.into()), 1.0.into()); + assert_eq!(swizzle.apply(&time, (0.0, 1.0).into()), (0.0, 1.0).into(),); assert_eq!( - swizzle.apply(&ctx, 0.0, (0.0, 1.0, 2.0).into()), + swizzle.apply(&time, (0.0, 1.0, 2.0).into()), (2.0, 1.0, 0.0).into(), ); } #[test] fn xzy() { - let ctx = ActionContext { - world: &World::new(), - actions: &ActionsData::default(), - entities: &[], - }; - let mut modifier = SwizzleAxis::XZY; - assert_eq!(modifier.apply(&ctx, 0.0, true.into()), true.into()); - assert_eq!(modifier.apply(&ctx, 0.0, 1.0.into()), 1.0.into()); - assert_eq!( - modifier.apply(&ctx, 0.0, (0.0, 1.0).into()), - (0.0, 0.0).into(), - ); + let time = Time::default(); + + assert_eq!(modifier.apply(&time, true.into()), true.into()); + assert_eq!(modifier.apply(&time, 1.0.into()), 1.0.into()); + assert_eq!(modifier.apply(&time, (0.0, 1.0).into()), (0.0, 0.0).into(),); assert_eq!( - modifier.apply(&ctx, 0.0, (0.0, 1.0, 2.0).into()), + modifier.apply(&time, (0.0, 1.0, 2.0).into()), (0.0, 2.0, 1.0).into(), ); } #[test] fn yzx() { - let ctx = ActionContext { - world: &World::new(), - actions: &ActionsData::default(), - entities: &[], - }; - let mut modifier = SwizzleAxis::YZX; - assert_eq!(modifier.apply(&ctx, 0.0, true.into()), true.into()); - assert_eq!(modifier.apply(&ctx, 0.0, 1.0.into()), 1.0.into()); - assert_eq!( - modifier.apply(&ctx, 0.0, (0.0, 1.0).into()), - (1.0, 0.0).into(), - ); + let time = Time::default(); + + assert_eq!(modifier.apply(&time, true.into()), true.into()); + assert_eq!(modifier.apply(&time, 1.0.into()), 1.0.into()); + assert_eq!(modifier.apply(&time, (0.0, 1.0).into()), (1.0, 0.0).into(),); assert_eq!( - modifier.apply(&ctx, 0.0, (0.0, 1.0, 2.0).into()), + modifier.apply(&time, (0.0, 1.0, 2.0).into()), (1.0, 2.0, 0.0).into(), ); } #[test] fn zxy() { - let ctx = ActionContext { - world: &World::new(), - actions: &ActionsData::default(), - entities: &[], - }; - let mut modifier = SwizzleAxis::ZXY; - assert_eq!(modifier.apply(&ctx, 0.0, true.into()), true.into()); - assert_eq!(modifier.apply(&ctx, 0.0, 1.0.into()), 1.0.into()); - assert_eq!( - modifier.apply(&ctx, 0.0, (0.0, 1.0).into()), - (0.0, 0.0).into(), - ); + let time = Time::default(); + + assert_eq!(modifier.apply(&time, true.into()), true.into()); + assert_eq!(modifier.apply(&time, 1.0.into()), 1.0.into()); + assert_eq!(modifier.apply(&time, (0.0, 1.0).into()), (0.0, 0.0).into(),); assert_eq!( - modifier.apply(&ctx, 0.0, (0.0, 1.0, 2.0).into()), + modifier.apply(&time, (0.0, 1.0, 2.0).into()), (2.0, 0.0, 1.0).into(), ); } #[test] fn xxx() { - let ctx = ActionContext { - world: &World::new(), - actions: &ActionsData::default(), - entities: &[], - }; - let mut modifier = SwizzleAxis::XXX; - assert_eq!(modifier.apply(&ctx, 0.0, true.into()), true.into()); - assert_eq!(modifier.apply(&ctx, 0.0, 1.0.into()), 1.0.into()); - assert_eq!( - modifier.apply(&ctx, 0.0, (0.0, 1.0).into()), - (0.0, 0.0).into(), - ); + let time = Time::default(); + + assert_eq!(modifier.apply(&time, true.into()), true.into()); + assert_eq!(modifier.apply(&time, 1.0.into()), 1.0.into()); + assert_eq!(modifier.apply(&time, (0.0, 1.0).into()), (0.0, 0.0).into(),); assert_eq!( - modifier.apply(&ctx, 0.0, (0.0, 1.0, 2.0).into()), + modifier.apply(&time, (0.0, 1.0, 2.0).into()), (0.0, 0.0, 0.0).into(), ); } #[test] fn yyy() { - let ctx = ActionContext { - world: &World::new(), - actions: &ActionsData::default(), - entities: &[], - }; - let mut modifier = SwizzleAxis::YYY; - assert_eq!(modifier.apply(&ctx, 0.0, true.into()), true.into()); - assert_eq!(modifier.apply(&ctx, 0.0, 1.0.into()), 1.0.into()); - assert_eq!( - modifier.apply(&ctx, 0.0, (0.0, 1.0).into()), - (1.0, 1.0).into(), - ); + let time = Time::default(); + + assert_eq!(modifier.apply(&time, true.into()), true.into()); + assert_eq!(modifier.apply(&time, 1.0.into()), 1.0.into()); + assert_eq!(modifier.apply(&time, (0.0, 1.0).into()), (1.0, 1.0).into(),); assert_eq!( - modifier.apply(&ctx, 0.0, (0.0, 1.0, 2.0).into()), + modifier.apply(&time, (0.0, 1.0, 2.0).into()), (1.0, 1.0, 1.0).into(), ); } #[test] fn zzz() { - let ctx = ActionContext { - world: &World::new(), - actions: &ActionsData::default(), - entities: &[], - }; - let mut modifier = SwizzleAxis::ZZZ; - assert_eq!(modifier.apply(&ctx, 0.0, true.into()), true.into()); - assert_eq!(modifier.apply(&ctx, 0.0, 1.0.into()), 1.0.into()); - assert_eq!( - modifier.apply(&ctx, 0.0, (0.0, 1.0).into()), - (0.0, 0.0).into(), - ); + let time = Time::default(); + + assert_eq!(modifier.apply(&time, true.into()), true.into()); + assert_eq!(modifier.apply(&time, 1.0.into()), 1.0.into()); + assert_eq!(modifier.apply(&time, (0.0, 1.0).into()), (0.0, 0.0).into(),); assert_eq!( - modifier.apply(&ctx, 0.0, (0.0, 1.0, 2.0).into()), + modifier.apply(&time, (0.0, 1.0, 2.0).into()), (2.0, 2.0, 2.0).into(), ); } diff --git a/src/input_context/trigger_tracker.rs b/src/input_context/trigger_tracker.rs index 5b92a3b..7fd270a 100644 --- a/src/input_context/trigger_tracker.rs +++ b/src/input_context/trigger_tracker.rs @@ -3,8 +3,7 @@ use std::cmp::Ordering; use bevy::prelude::*; use super::{ - context_instance::ActionContext, - input_action::{Accumulation, ActionState}, + input_action::{Accumulation, ActionState, ActionsData}, input_condition::{ConditionKind, InputCondition}, input_modifier::InputModifier, }; @@ -33,12 +32,11 @@ impl TriggerTracker { pub(super) fn apply_modifiers( &mut self, - ctx: &ActionContext, - delta: f32, + time: &Time, modifiers: &mut [Box], ) { for modifier in modifiers { - let new_value = modifier.apply(ctx, delta, self.value); + let new_value = modifier.apply(time, self.value); debug_assert_eq!( new_value.dim(), self.value.dim(), @@ -55,14 +53,14 @@ impl TriggerTracker { pub(super) fn apply_conditions( &mut self, - ctx: &ActionContext, - delta: f32, + actions: &ActionsData, + time: &Time, conditions: &mut [Box], ) { // Note: No early outs permitted! // All conditions must be evaluated to update their internal state/delta time. for condition in conditions { - let state = condition.evaluate(ctx, delta, self.value); + let state = condition.evaluate(actions, time, self.value); trace!("`{condition:?}` returns state `{state:?}`"); match condition.kind() { ConditionKind::Regular => { diff --git a/src/lib.rs b/src/lib.rs index a204998..e3d463f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,7 +21,7 @@ Instead of reacting to raw input data like "Released" or "Pressed", the crate pr like [`DeadZone`], [`Negate`], etc., but you can add your own by implementing [`InputModifier`]. [`Conditions`](input_context::input_condition) define how an action activates. We also provide built-in conditions, such as [`Pressed`], -[`Released`], [`Hold`], etc. You can also add your game-specific conditions like `CanJump` by implementing [`InputCondition`]. +[`Released`], [`Hold`], etc. You can also add your own by implementing [`InputCondition`]. # Quick start @@ -68,9 +68,7 @@ pub mod prelude { action_value::{ActionValue, ActionValueDim}, input::{GamepadDevice, Input, Modifiers}, input_context::{ - context_instance::{ - ActionBind, ActionContext, ContextInstance, GamepadStick, InputBind, - }, + context_instance::{ActionBind, ContextInstance, GamepadStick, InputBind}, input_action::{Accumulation, ActionEvent, ActionEventKind, ActionState, InputAction}, input_condition::{ blocked_by::*, chord::*, condition_timer::*, down::*, hold::*, hold_and_release::*, @@ -87,7 +85,7 @@ pub mod prelude { pub use bevy_enhanced_input_macros::InputAction; } -use bevy::{ecs::system::SystemState, input::InputSystem, prelude::*}; +use bevy::{input::InputSystem, prelude::*}; use input::input_reader::InputReader; use input_context::ContextInstances; @@ -106,17 +104,14 @@ impl Plugin for EnhancedInputPlugin { } impl EnhancedInputPlugin { - fn update(world: &mut World, state: &mut SystemState<(Commands, InputReader, Res