oxygengine_input/resources/
stack.rs

1use crate::{component::InputStackInstance, resources::controller::*};
2use core::{
3    ecs::{life_cycle::EntityChanges, Entity},
4    id::ID,
5    Scalar,
6};
7use serde::{Deserialize, Serialize};
8use std::collections::{HashMap, HashSet};
9
10pub type InputStackListenerId = ID<InputStackListener>;
11pub type InputStackMappings = HashSet<String>;
12pub type InputStackScaledMappings = HashMap<String, Scalar>;
13
14#[derive(Debug, Default, Clone, Serialize, Deserialize)]
15pub struct InputStackTrigger {
16    #[serde(default)]
17    pub consume: bool,
18    #[serde(default)]
19    mappings: InputStackMappings,
20    #[serde(skip)]
21    state: TriggerState,
22}
23
24impl InputStackTrigger {
25    pub fn new(mappings: impl Into<InputStackMappings>) -> Self {
26        Self {
27            consume: false,
28            mappings: mappings.into(),
29            state: Default::default(),
30        }
31    }
32
33    pub fn mappings(&self) -> &InputStackMappings {
34        &self.mappings
35    }
36
37    pub fn state(&self) -> TriggerState {
38        self.state
39    }
40}
41
42#[derive(Debug, Default, Clone, Serialize, Deserialize)]
43pub struct InputStackChannel {
44    #[serde(default)]
45    mappings: InputStackScaledMappings,
46    #[serde(skip)]
47    state: Scalar,
48}
49
50impl InputStackChannel {
51    pub fn new(mappings: impl Into<InputStackScaledMappings>) -> Self {
52        Self {
53            mappings: mappings.into(),
54            state: 0.0,
55        }
56    }
57
58    pub fn mappings(&self) -> &InputStackScaledMappings {
59        &self.mappings
60    }
61
62    pub fn state(&self) -> Scalar {
63        self.state
64    }
65}
66
67#[derive(Debug, Default, Clone, Serialize, Deserialize)]
68pub struct InputStackAxes {
69    #[serde(default)]
70    pub consume: bool,
71    #[serde(default)]
72    channels: Vec<InputStackChannel>,
73}
74
75impl InputStackAxes {
76    pub fn new(channels: impl Iterator<Item = impl Into<InputStackChannel>>) -> Self {
77        Self {
78            consume: false,
79            channels: channels.map(|item| item.into()).collect::<Vec<_>>(),
80        }
81    }
82
83    pub fn new_single(channel: impl Into<InputStackChannel>) -> Self {
84        Self {
85            consume: false,
86            channels: vec![channel.into()],
87        }
88    }
89
90    pub fn channels(&self) -> &[InputStackChannel] {
91        &self.channels
92    }
93
94    pub fn channels_state_or_default<const N: usize>(&self) -> [Scalar; N] {
95        let mut result = [0.0; N];
96        for (channel, result) in self.channels.iter().zip(result.iter_mut()) {
97            *result = channel.state;
98        }
99        result
100    }
101
102    pub fn channel_state_or_default(&self) -> Scalar {
103        self.channels
104            .get(0)
105            .map(|channel| channel.state)
106            .unwrap_or_default()
107    }
108}
109
110#[derive(Debug, Default, Clone, Serialize, Deserialize)]
111pub struct InputStackText {
112    pub consume: bool,
113    state: String,
114}
115
116impl InputStackText {
117    pub fn state(&self) -> &str {
118        &self.state
119    }
120}
121
122#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
123pub enum InputStackCombinationAction {
124    Trigger(String),
125    AxesMagnitude {
126        name: String,
127        threshold: Scalar,
128    },
129    AxesTargetValues {
130        name: String,
131        target: Vec<Scalar>,
132        threshold: Scalar,
133    },
134}
135
136#[derive(Debug, Default, Clone, Serialize, Deserialize)]
137pub struct InputStackCombination {
138    #[serde(default)]
139    pub continous: bool,
140    #[serde(default)]
141    actions: Vec<InputStackCombinationAction>,
142    #[serde(skip)]
143    state: bool,
144    #[serde(skip)]
145    last_state: bool,
146}
147
148impl InputStackCombination {
149    pub fn new(actions: impl Iterator<Item = impl Into<InputStackCombinationAction>>) -> Self {
150        Self {
151            continous: false,
152            actions: actions.map(|action| action.into()).collect(),
153            state: false,
154            last_state: false,
155        }
156    }
157
158    pub fn actions(&self) -> &[InputStackCombinationAction] {
159        &self.actions
160    }
161
162    pub fn pressed(&self) -> bool {
163        if self.continous {
164            self.state
165        } else {
166            self.state && !self.last_state
167        }
168    }
169
170    pub fn released(&self) -> bool {
171        if self.continous {
172            !self.state
173        } else {
174            !self.state && self.last_state
175        }
176    }
177}
178
179#[derive(Debug, Clone, Serialize, Deserialize)]
180pub struct InputStackListener {
181    #[serde(default)]
182    pub priority: usize,
183    #[serde(default = "InputStackListener::default_enabled")]
184    pub enabled: bool,
185    #[serde(skip)]
186    pub bound_entity: Option<Entity>,
187    #[serde(default)]
188    triggers: HashMap<String, InputStackTrigger>,
189    #[serde(default)]
190    axes: HashMap<String, InputStackAxes>,
191    #[serde(default)]
192    text: Option<InputStackText>,
193    #[serde(default)]
194    combinations: HashMap<String, InputStackCombination>,
195}
196
197impl Default for InputStackListener {
198    fn default() -> Self {
199        Self {
200            priority: 0,
201            enabled: Self::default_enabled(),
202            bound_entity: None,
203            triggers: Default::default(),
204            axes: Default::default(),
205            text: None,
206            combinations: Default::default(),
207        }
208    }
209}
210
211impl InputStackListener {
212    fn default_enabled() -> bool {
213        true
214    }
215
216    pub fn with_trigger(mut self, name: &str, trigger: InputStackTrigger) -> Self {
217        self.map_trigger(name, trigger);
218        self
219    }
220
221    pub fn map_trigger(&mut self, name: &str, trigger: InputStackTrigger) {
222        self.triggers.insert(name.to_owned(), trigger);
223    }
224
225    pub fn unmap_trigger(&mut self, name: &str) {
226        self.triggers.remove(name);
227    }
228
229    pub fn trigger(&self, name: &str) -> Option<&InputStackTrigger> {
230        self.triggers.get(name)
231    }
232
233    pub fn trigger_state_or_default(&self, name: &str) -> TriggerState {
234        self.trigger(name)
235            .map(|trigger| trigger.state())
236            .unwrap_or_default()
237    }
238
239    pub fn with_axes(mut self, name: &str, axes: InputStackAxes) -> Self {
240        self.map_axes(name, axes);
241        self
242    }
243
244    pub fn map_axes(&mut self, name: &str, axes: InputStackAxes) {
245        self.axes.insert(name.to_owned(), axes);
246    }
247
248    pub fn unmap_axes(&mut self, name: &str) {
249        self.axes.remove(name);
250    }
251
252    pub fn axes(&self, name: &str) -> Option<&InputStackAxes> {
253        self.axes.get(name)
254    }
255
256    pub fn axes_channels_or_default(&self, name: &str) -> &[InputStackChannel] {
257        self.axes(name)
258            .map(|axes| axes.channels())
259            .unwrap_or_default()
260    }
261
262    pub fn axes_state_or_default<const N: usize>(&self, name: &str) -> [Scalar; N] {
263        self.axes(name)
264            .map(|axes| axes.channels_state_or_default())
265            .unwrap_or_else(|| [0.0; N])
266    }
267
268    pub fn channel_state_or_default(&self, name: &str) -> Scalar {
269        self.axes(name)
270            .map(|axes| axes.channel_state_or_default())
271            .unwrap_or_default()
272    }
273
274    pub fn with_text(mut self, text: InputStackText) -> Self {
275        self.map_text(text);
276        self
277    }
278
279    pub fn map_text(&mut self, text: InputStackText) {
280        self.text = Some(text);
281    }
282
283    pub fn unmap_text(&mut self) {
284        self.text = None;
285    }
286
287    pub fn text(&self) -> Option<&InputStackText> {
288        self.text.as_ref()
289    }
290
291    pub fn text_state_or_default(&self) -> &str {
292        self.text().map(|text| text.state()).unwrap_or_default()
293    }
294
295    pub fn with_combination(mut self, name: &str, combination: InputStackCombination) -> Self {
296        self.map_combination(name, combination);
297        self
298    }
299
300    pub fn map_combination(&mut self, name: &str, combination: InputStackCombination) {
301        self.combinations.insert(name.to_owned(), combination);
302    }
303
304    pub fn unmap_combination(&mut self, name: &str) {
305        self.combinations.remove(name);
306    }
307
308    pub fn combination(&self, name: &str) -> Option<&InputStackCombination> {
309        self.combinations.get(name)
310    }
311
312    pub fn combination_pressed_or_default(&self, name: &str) -> bool {
313        self.combination(name)
314            .map(|combination| combination.pressed())
315            .unwrap_or_default()
316    }
317
318    pub fn combination_released_or_default(&self, name: &str) -> bool {
319        self.combination(name)
320            .map(|combination| combination.released())
321            .unwrap_or_default()
322    }
323}
324
325#[derive(Default)]
326pub struct InputStack {
327    listeners: HashMap<InputStackListenerId, InputStackListener>,
328}
329
330impl InputStack {
331    pub fn register(&mut self, listener: InputStackListener) -> InputStackListenerId {
332        let id = InputStackListenerId::new();
333        self.listeners.insert(id, listener);
334        id
335    }
336
337    pub fn unregister(&mut self, id: InputStackListenerId) {
338        self.listeners.remove(&id);
339    }
340
341    pub fn listeners(&self) -> impl Iterator<Item = &InputStackListener> {
342        self.listeners.values()
343    }
344
345    pub fn listener(&self, id: InputStackListenerId) -> Option<&InputStackListener> {
346        self.listeners.get(&id)
347    }
348
349    pub fn listener_by_instance(
350        &self,
351        instance: &InputStackInstance,
352    ) -> Option<&InputStackListener> {
353        instance.as_listener().and_then(|id| self.listener(id))
354    }
355
356    pub fn listeners_by_entity(&self, entity: Entity) -> impl Iterator<Item = &InputStackListener> {
357        self.listeners()
358            .filter(move |listener| listener.bound_entity == Some(entity))
359    }
360
361    pub fn process(&mut self, controller: &InputController, entity_changes: &EntityChanges) {
362        self.listeners.retain(|_, listener| {
363            listener
364                .bound_entity
365                .map(|entity| !entity_changes.has_despawned(entity))
366                .unwrap_or(true)
367        });
368
369        let mut consumed_triggers = HashSet::with_capacity(controller.triggers().count());
370        let mut consumed_axes = HashSet::with_capacity(controller.axes().count());
371        let mut consumed_text = false;
372        let mut stack = self.listeners.iter_mut().collect::<Vec<_>>();
373        stack.sort_by(|a, b| a.1.priority.cmp(&b.1.priority).reverse());
374
375        for (_, listener) in stack {
376            for trigger in listener.triggers.values_mut() {
377                trigger.state = trigger.state.release();
378                if listener.enabled {
379                    if let Some((mapping, state)) = trigger
380                        .mappings
381                        .iter()
382                        .filter(|m| !consumed_triggers.contains(m.as_str()))
383                        .map(|m| (m, controller.trigger_or_default(m)))
384                        .max_by(|a, b| a.1.priority().cmp(&b.1.priority()))
385                    {
386                        trigger.state = state;
387                        if trigger.consume {
388                            consumed_triggers.insert(mapping.to_owned());
389                        }
390                    }
391                }
392            }
393            for axes in listener.axes.values_mut() {
394                for channel in &mut axes.channels {
395                    channel.state = 0.0;
396                    if listener.enabled {
397                        if let Some((mapping, scale, value)) = channel
398                            .mappings
399                            .iter()
400                            .filter(|(m, _)| !consumed_axes.contains(m.as_str()))
401                            .map(|(m, s)| (m, s, controller.axis_or_default(m)))
402                            .max_by(|a, b| a.2.partial_cmp(&b.2).unwrap())
403                        {
404                            channel.state = value * scale;
405                            if axes.consume {
406                                consumed_axes.insert(mapping.to_owned());
407                            }
408                        }
409                    }
410                }
411            }
412            if let Some(text) = listener.text.as_mut() {
413                if !listener.enabled || consumed_text {
414                    text.state.clear();
415                } else {
416                    text.state = controller.text().to_owned();
417                    if text.consume {
418                        consumed_text = true;
419                    }
420                }
421            }
422            let mut combinations = std::mem::take(&mut listener.combinations);
423            for combination in combinations.values_mut() {
424                combination.last_state = combination.state;
425                combination.state = combination
426                    .actions
427                    .iter()
428                    .map(|action| match action {
429                        InputStackCombinationAction::Trigger(name) => {
430                            listener.trigger_state_or_default(name).is_pressed()
431                        }
432                        InputStackCombinationAction::AxesMagnitude { name, threshold } => {
433                            let axes = listener.axes_channels_or_default(name);
434                            let squared = axes
435                                .iter()
436                                .map(|channel| channel.state() * channel.state())
437                                .sum::<Scalar>();
438                            let magnitude = if !axes.is_empty() {
439                                squared / axes.len() as Scalar
440                            } else {
441                                0.0
442                            };
443                            magnitude > *threshold
444                        }
445                        InputStackCombinationAction::AxesTargetValues {
446                            name,
447                            target,
448                            threshold,
449                        } => {
450                            let axes = listener.axes_channels_or_default(name);
451                            let squared = target
452                                .iter()
453                                .zip(axes.iter())
454                                .map(|(target, channel)| {
455                                    let diff = (target - channel.state()).abs();
456                                    diff * diff
457                                })
458                                .sum::<Scalar>();
459                            let difference = if !axes.is_empty() {
460                                squared / axes.len() as Scalar
461                            } else {
462                                0.0
463                            };
464                            difference > *threshold
465                        }
466                    })
467                    .all(|v| v);
468            }
469            listener.combinations = combinations;
470        }
471    }
472}