Skip to content

Commit

Permalink
Add ability to filter entities for which action diffs should be gener…
Browse files Browse the repository at this point in the history
…ated (#601)

* Support query filters in SummarizedActionState::summarize

* Add filtered version of generate_action_diffs (with tests)

* Change dbg! to debug! for more controllable debug prints

* Fix unused variable in unit test

* Delete explicit type args in function call as it can be inferred

* Make separate filtered/nonfiltered summarize fns for SummarizedActionState

* Add release notes for filtered action diffs

* Improve documenting comments

* Add backticks to documentation, make clippy happy

* Move filtered action diffs to Usability (0.16) in RELEASES.md

* Improve unit test comments

* Fix indentation in RELEASES.md

* Add integration test for generate_action_diffs_filtered

* Add missing ActionDiff variant to filtered action diff test

* Reword filtered action diff release note for coherent style
  • Loading branch information
zhaop authored Oct 7, 2024
1 parent 2bb436d commit 36e7d96
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 4 deletions.
3 changes: 3 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
- removed `KeyboardVirtualDPad` and `GamepadVirtualDPad` in favor of `VirtualDPad`
- removed `KeyboardVirtualDPad3D` in favor of `VirtualDPad3D`
- added `threshold` value for `GamepadControlDirection`, `MouseMoveDirection`, and `MouseScrollDirection` to be considered pressed.
- added ability to filter entities to generate action diffs for:
- added new `generate_action_diffs_filtered<A, F>` system, which accepts a `QueryFilter`, so that only entities matching QueryFilter `F` (and with `ActionState<A>`) generate action diffs
- added new `summarize_filtered<F>` function for `SummarizedActionState<A>` (alongside the original `summarize`).

## Version 0.15.1

Expand Down
34 changes: 33 additions & 1 deletion src/action_diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use bevy::{
ecs::{
entity::{Entity, MapEntities},
event::Event,
query::QueryFilter,
},
math::{Vec2, Vec3},
prelude::{EntityMapper, EventWriter, Query, Res},
Expand Down Expand Up @@ -112,10 +113,19 @@ impl<A: Actionlike> SummarizedActionState<A> {
entities
}

/// Captures the raw values for each action in the current frame.
/// Captures the raw values for each action in the current frame, for all entities with `ActionState<A>`.
pub fn summarize(
global_action_state: Option<Res<ActionState<A>>>,
action_state_query: Query<(Entity, &ActionState<A>)>,
) -> Self {
Self::summarize_filtered(global_action_state, action_state_query)
}

/// Captures the raw values for each action in the current frame, for entities with `ActionState<A>`
/// matching the query filter.
pub fn summarize_filtered<F: QueryFilter>(
global_action_state: Option<Res<ActionState<A>>>,
action_state_query: Query<(Entity, &ActionState<A>), F>,
) -> Self {
let mut button_state_map = HashMap::default();
let mut axis_state_map = HashMap::default();
Expand Down Expand Up @@ -389,6 +399,9 @@ mod tests {
action_state
}

#[derive(Component)]
struct NotSummarized;

fn expected_summary(entity: Entity) -> SummarizedActionState<TestAction> {
let mut button_state_map = HashMap::default();
let mut axis_state_map = HashMap::default();
Expand Down Expand Up @@ -449,6 +462,25 @@ mod tests {
assert_eq!(summarized, expected_summary(entity));
}

#[test]
fn summarize_filtered_entities_from_component() {
// Spawn two entities, one to be summarized and one to be filtered out
let mut world = World::new();
let entity = world.spawn(test_action_state()).id();
world.spawn((test_action_state(), NotSummarized));

let mut system_state: SystemState<(
Option<Res<ActionState<TestAction>>>,
Query<(Entity, &ActionState<TestAction>), Without<NotSummarized>>,
)> = SystemState::new(&mut world);
let (global_action_state, action_state_query) = system_state.get(&world);
let summarized =
SummarizedActionState::summarize_filtered(global_action_state, action_state_query);

// Check that only the entity without NotSummarized was summarized
assert_eq!(summarized, expected_summary(entity));
}

#[test]
fn diffs_are_sent() {
let mut world = World::new();
Expand Down
28 changes: 26 additions & 2 deletions src/systems.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
use crate::prelude::updating::CentralInputStore;
#[cfg(feature = "mouse")]
use crate::user_input::{AccumulatedMouseMovement, AccumulatedMouseScroll};
use bevy::ecs::query::QueryFilter;
#[cfg(feature = "mouse")]
use bevy::input::mouse::{MouseMotion, MouseWheel};
use bevy::log::debug;

use crate::{
action_state::ActionState, clashing_inputs::ClashStrategy, input_map::InputMap, Actionlike,
Expand Down Expand Up @@ -169,13 +171,35 @@ pub fn filter_captured_input(
pub fn generate_action_diffs<A: Actionlike>(
global_action_state: Option<Res<ActionState<A>>>,
action_state_query: Query<(Entity, &ActionState<A>)>,
previous_action_state: Local<SummarizedActionState<A>>,
action_diff_events: EventWriter<ActionDiffEvent<A>>,
) {
generate_action_diffs_filtered(
global_action_state,
action_state_query,
previous_action_state,
action_diff_events,
)
}

/// Generates an [`Events`] stream of [`ActionDiff`s](crate::action_diff::ActionDiff) from the [`ActionState`] of certain entities.
///
/// This system is not part of the [`InputManagerPlugin`](crate::plugin::InputManagerPlugin) and must be added manually.
/// Generally speaking, this should be added as part of [`PostUpdate`](bevy::prelude::PostUpdate),
/// to ensure that all inputs have been processed and any manual actions have been sent.
///
/// This system accepts a [`QueryFilter`] to limit which entities should have action diffs generated.
pub fn generate_action_diffs_filtered<A: Actionlike, F: QueryFilter>(
global_action_state: Option<Res<ActionState<A>>>,
action_state_query: Query<(Entity, &ActionState<A>), F>,
mut previous_action_state: Local<SummarizedActionState<A>>,
mut action_diff_events: EventWriter<ActionDiffEvent<A>>,
) {
let current_action_state =
SummarizedActionState::summarize(global_action_state, action_state_query);
SummarizedActionState::summarize_filtered(global_action_state, action_state_query);
current_action_state.send_diffs(&previous_action_state, &mut action_diff_events);
dbg!(&previous_action_state, &current_action_state);
debug!("previous_action_state: {:?}", previous_action_state);
debug!("current_action_state: {:?}", current_action_state);
*previous_action_state = current_action_state;
}

Expand Down
58 changes: 57 additions & 1 deletion tests/action_diffs.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use bevy::{input::InputPlugin, prelude::*};
use leafwing_input_manager::action_diff::{ActionDiff, ActionDiffEvent};
use leafwing_input_manager::{prelude::*, systems::generate_action_diffs};
use leafwing_input_manager::{
prelude::*,
systems::{generate_action_diffs, generate_action_diffs_filtered},
};

#[derive(Actionlike, Clone, Copy, Debug, Reflect, PartialEq, Eq, Hash)]
enum Action {
Expand All @@ -11,6 +14,9 @@ enum Action {
DualAxis,
}

#[derive(Component)]
pub struct NoActionDiffs;

fn spawn_test_entity(mut commands: Commands) {
commands.spawn(ActionState::<Action>::default());
}
Expand Down Expand Up @@ -265,6 +271,56 @@ fn generate_axis_action_diffs() {
});
}

#[test]
fn generate_filtered_binary_action_diffs() {
let mut app = create_app();
app.add_systems(
PostUpdate,
generate_action_diffs_filtered::<Action, Without<NoActionDiffs>>,
);

let entity = app
.world_mut()
.query_filtered::<Entity, With<ActionState<Action>>>()
.single(app.world());
// Also spawn an entity that will not send action diffs
app.world_mut()
.spawn((ActionState::<Action>::default(), NoActionDiffs));

// Press both entities
for mut action_state in app
.world_mut()
.query::<&mut ActionState<Action>>()
.iter_mut(app.world_mut())
{
action_state.press(&Action::Button);
}
app.update();

// Expect only `entity` to have an action diff event
assert_action_diff_created(&mut app, |action_diff_event| {
assert_eq!(action_diff_event.owner, Some(entity));
assert_eq!(action_diff_event.action_diffs.len(), 1);
match action_diff_event.action_diffs.first().unwrap().clone() {
ActionDiff::Pressed { action } => {
assert_eq!(action, Action::Button);
}
ActionDiff::Released { .. } => {
panic!("Expected a `Pressed` variant got a `Released` variant")
}
ActionDiff::AxisChanged { .. } => {
panic!("Expected a `Pressed` variant got a `ValueChanged` variant")
}
ActionDiff::DualAxisChanged { .. } => {
panic!("Expected a `Pressed` variant got a `AxisPairChanged` variant")
}
ActionDiff::TripleAxisChanged { .. } => {
panic!("Expected a `Pressed` variant got a `TripleAxisChanged` variant")
}
}
});
}

#[test]
fn process_binary_action_diffs() {
let mut app = create_app();
Expand Down

0 comments on commit 36e7d96

Please sign in to comment.