leafwing_input_manager/
systems.rs

1//! The systems that power each [`InputManagerPlugin`](crate::plugin::InputManagerPlugin).
2
3use crate::prelude::updating::CentralInputStore;
4use bevy::ecs::query::QueryFilter;
5use bevy::log::debug;
6
7use crate::{
8    action_state::ActionState, clashing_inputs::ClashStrategy, input_map::InputMap, Actionlike,
9};
10
11use bevy::ecs::prelude::*;
12use bevy::prelude::Gamepad;
13use bevy::{
14    platform::time::Instant,
15    time::{Real, Time},
16};
17
18use crate::action_diff::{ActionDiffEvent, SummarizedActionState};
19
20/// We are about to enter the `Main` schedule, so we:
21/// - save all the changes applied to `state` into the `fixed_update_state`
22/// - switch to loading the `update_state`
23pub fn swap_to_update<A: Actionlike>(
24    mut query: Query<&mut ActionState<A>>,
25    action_state: Option<ResMut<ActionState<A>>>,
26) {
27    if let Some(mut action_state) = action_state {
28        action_state.swap_to_update_state();
29    }
30
31    for mut action_state in query.iter_mut() {
32        action_state.swap_to_update_state();
33    }
34}
35
36/// We are about to enter the `FixedMain` schedule, so we:
37/// - save all the changes applied to `state` into the `update_state`
38/// - switch to loading the `fixed_update_state`
39pub fn swap_to_fixed_update<A: Actionlike>(
40    mut query: Query<&mut ActionState<A>>,
41    action_state: Option<ResMut<ActionState<A>>>,
42) {
43    if let Some(mut action_state) = action_state {
44        action_state.swap_to_fixed_update_state();
45    }
46
47    for mut action_state in query.iter_mut() {
48        action_state.swap_to_fixed_update_state();
49    }
50}
51
52/// Advances actions timer.
53///
54/// Clears the just-pressed and just-released values of all [`ActionState`]s.
55/// Also resets the internal `pressed_this_tick` field, used to track whether to release an action.
56pub fn tick_action_state<A: Actionlike>(
57    mut query: Query<&mut ActionState<A>>,
58    action_state: Option<ResMut<ActionState<A>>>,
59    time: Res<Time<Real>>,
60    mut stored_previous_instant: Local<Option<Instant>>,
61) {
62    // If this is the very first tick, measure from the start of the app
63    let current_instant = time.last_update().unwrap_or_else(|| time.startup());
64    let previous_instant = stored_previous_instant.unwrap_or_else(|| time.startup());
65
66    // Only tick the ActionState resource if it exists
67    if let Some(mut action_state) = action_state {
68        action_state.tick(current_instant, previous_instant);
69    }
70
71    // Only tick the ActionState components if they exist
72    for mut action_state in query.iter_mut() {
73        // If `Time` has not ever been advanced, something has gone horribly wrong
74        // and the user probably forgot to add the `core_plugin`.
75        action_state.tick(current_instant, previous_instant);
76    }
77
78    // Store the previous time in the system
79    *stored_previous_instant = time.last_update();
80}
81
82/// Fetches the [`CentralInputStore`]
83/// to update [`ActionState`] according to the [`InputMap`].
84///
85/// Clashes will be resolved according to the [`ClashStrategy`] resource.
86pub fn update_action_state<A: Actionlike>(
87    input_store: Res<CentralInputStore>,
88    clash_strategy: Res<ClashStrategy>,
89    mut gamepads: Query<Entity, With<Gamepad>>,
90    action_state: Option<ResMut<ActionState<A>>>,
91    input_map: Option<Res<InputMap<A>>>,
92    mut query: Query<(&mut ActionState<A>, &InputMap<A>)>,
93) {
94    let resources = input_map
95        .zip(action_state)
96        .map(|(input_map, action_state)| (Mut::from(action_state), input_map.into_inner()));
97
98    for (mut action_state, input_map) in query.iter_mut().chain(resources) {
99        action_state.update(input_map.process_actions(
100            Some(gamepads.reborrow()),
101            &input_store,
102            *clash_strategy,
103        ));
104    }
105}
106
107#[cfg(feature = "ui")]
108/// Filters out all inputs that are captured by the UI.
109pub fn filter_captured_input(
110    mut mouse_buttons: ResMut<bevy::input::ButtonInput<bevy::input::mouse::MouseButton>>,
111    #[cfg(feature = "ui")] interactions: Query<&bevy::ui::Interaction>,
112) {
113    // If the user clicks on a button, do not apply it to the game state
114    #[cfg(feature = "ui")]
115    if interactions
116        .iter()
117        .any(|&interaction| interaction != bevy::ui::Interaction::None)
118    {
119        mouse_buttons.clear();
120    }
121}
122
123/// Generates an [`Events`] stream of [`ActionDiff`s](crate::action_diff::ActionDiff) from every [`ActionState`].
124///
125/// This system is not part of the [`InputManagerPlugin`](crate::plugin::InputManagerPlugin) and must be added manually.
126/// Generally speaking, this should be added as part of [`PostUpdate`](bevy::prelude::PostUpdate),
127/// to ensure that all inputs have been processed and any manual actions have been sent.
128pub fn generate_action_diffs<A: Actionlike>(
129    global_action_state: Option<Res<ActionState<A>>>,
130    action_state_query: Query<(Entity, &ActionState<A>)>,
131    previous_action_state: Local<SummarizedActionState<A>>,
132    action_diff_events: EventWriter<ActionDiffEvent<A>>,
133) {
134    generate_action_diffs_filtered(
135        global_action_state,
136        action_state_query,
137        previous_action_state,
138        action_diff_events,
139    )
140}
141
142/// Generates an [`Events`] stream of [`ActionDiff`s](crate::action_diff::ActionDiff) from the [`ActionState`] of certain entities.
143///
144/// This system is not part of the [`InputManagerPlugin`](crate::plugin::InputManagerPlugin) and must be added manually.
145/// Generally speaking, this should be added as part of [`PostUpdate`](bevy::prelude::PostUpdate),
146/// to ensure that all inputs have been processed and any manual actions have been sent.
147///
148/// This system accepts a [`QueryFilter`] to limit which entities should have action diffs generated.
149pub fn generate_action_diffs_filtered<A: Actionlike, F: QueryFilter>(
150    global_action_state: Option<Res<ActionState<A>>>,
151    action_state_query: Query<(Entity, &ActionState<A>), F>,
152    mut previous_action_state: Local<SummarizedActionState<A>>,
153    mut action_diff_events: EventWriter<ActionDiffEvent<A>>,
154) {
155    let current_action_state =
156        SummarizedActionState::summarize_filtered(global_action_state, action_state_query);
157    current_action_state.send_diffs(&previous_action_state, &mut action_diff_events);
158    debug!("previous_action_state: {:?}", previous_action_state);
159    debug!("current_action_state: {:?}", current_action_state);
160    *previous_action_state = current_action_state;
161}
162
163/// Release all inputs when an [`InputMap<A>`] is removed to prevent them from being held forever.
164///
165/// By default, [`InputManagerPlugin<A>`](crate::plugin::InputManagerPlugin) will run this on [`PostUpdate`](bevy::prelude::PostUpdate).
166/// For components you must remove the [`InputMap<A>`] before [`PostUpdate`](bevy::prelude::PostUpdate)
167/// or this will not run.
168pub fn release_on_input_map_removed<A: Actionlike>(
169    mut removed_components: RemovedComponents<InputMap<A>>,
170    input_map_resource: Option<ResMut<InputMap<A>>>,
171    action_state_resource: Option<ResMut<ActionState<A>>>,
172    mut input_map_resource_existed: Local<bool>,
173    mut action_state_query: Query<&mut ActionState<A>>,
174) {
175    let mut iter = action_state_query.iter_many_mut(removed_components.read());
176    while let Some(mut action_state) = iter.fetch_next() {
177        action_state.reset_all();
178    }
179
180    // Detect when an InputMap resource is removed.
181    if input_map_resource.is_some() {
182        // Store if the resource existed, so we know if it was removed later.
183        *input_map_resource_existed = true;
184    } else if *input_map_resource_existed {
185        // The input map does not exist, and our local is true,
186        // so we know the input map was removed.
187
188        if let Some(mut action_state) = action_state_resource {
189            action_state.reset_all();
190        }
191
192        // Reset our local so our removal detection is only triggered once.
193        *input_map_resource_existed = false;
194    }
195}
196
197/// Clears all values from the [`CentralInputStore`],
198/// making sure that it can read fresh inputs for the frame.
199pub fn clear_central_input_store(mut input_store: ResMut<CentralInputStore>) {
200    input_store.clear();
201}