Skip to content

Commit

Permalink
Improved control-action binding model
Browse files Browse the repository at this point in the history
Input devices generate input events for their controls, where a
"control" is the physical button/stick/mechanism that actually generated
the event.

ControlManagers bind actions to these controls, and any input events for
that control will be tagged with the action. The engine can then filter
the input event stream by action to get the relevant input events. This
decouples device controls from engine actions, which makes it easier to
later implement control remapping.
  • Loading branch information
natebuckareff committed Mar 2, 2024
1 parent 8a3df29 commit 485c6f7
Show file tree
Hide file tree
Showing 7 changed files with 250 additions and 78 deletions.
21 changes: 21 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ glam = "0.25.0"
vma = "0.3.1"
image = "0.24.8"
gilrs = "0.10.4"
enumflags2 = "0.7.9"
180 changes: 141 additions & 39 deletions src/input/event.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use enumflags2::{bitflags, BitFlags};
use std::cell::OnceCell;
use std::collections::HashMap;
use std::hash::Hash;
use std::time::{Duration, Instant};
Expand All @@ -7,86 +9,186 @@ pub trait DeviceId: Clone + Copy + PartialEq + Eq {
fn kind(&self) -> Self::Kind;
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ControlId(u64);

#[derive(Debug, Clone, Copy)]
pub enum InputValue {
Digital(bool),
Analog(f64),
Analog2d(f64, f64),
}

#[bitflags]
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum InputKind {
Digital,
Analog,
Analog2d,
}

impl InputValue {
pub fn kind(&self) -> InputKind {
match &self {
InputValue::Digital(_) => InputKind::Digital,
InputValue::Analog(_) => InputKind::Analog,
InputValue::Analog2d(_, _) => InputKind::Analog2d,
}
}
}

#[derive(Debug)]
pub struct InputEvent<DId: DeviceId> {
pub struct InputEvent<DId: DeviceId, Action: Hash> {
pub index: u64,
pub created_at: Duration,
pub device_id: DId,
pub control_id: ControlId,
pub action: Action,
pub value: InputValue,
}

pub trait RawEvent<DId: DeviceId> {
type RawControl: Copy + Clone + Eq + PartialEq + Hash;
type Control: Control;
fn get_device_id(&self) -> DId;
fn get_raw_control(&self) -> Self::RawControl;
fn get_control(&self) -> Self::Control;
fn get_input_value(&self) -> InputValue;
}

pub struct ControlManager<DId: DeviceId, REvent: RawEvent<DId>> {
pub trait Control: Copy + Clone + Eq + PartialEq + Hash {
fn kind(&self) -> BitFlags<InputKind>;
}

pub struct InputManager<DId, REvent, Action>
where
DId: DeviceId,
REvent: RawEvent<DId>,
Action: Copy + Clone + Eq + Hash,
{
start_time: Instant,
control_ids: HashMap<REvent::RawControl, ControlId>,
control_ids_rev: HashMap<ControlId, REvent::RawControl>,
next_control_id: u64,
control_map: HashMap<REvent::Control, (Action, Option<BitFlags<InputKind>>)>,
control_map_rev: HashMap<Action, REvent::Control>,
wildcard_actions: Vec<(Action, Option<BitFlags<InputKind>>)>,
input_events: Vec<InputEvent<DId, Action>>,
next_index: u64,
}

impl<DId: DeviceId, REvent: RawEvent<DId>> ControlManager<DId, REvent> {
impl<DId, REvent, Action> InputManager<DId, REvent, Action>
where
DId: DeviceId,
REvent: RawEvent<DId>,
Action: Copy + Clone + Eq + Hash,
{
pub fn new(start_time: Instant) -> Self {
Self {
start_time,
control_ids: HashMap::new(),
control_ids_rev: HashMap::new(),
next_control_id: 0,
control_map: HashMap::new(),
control_map_rev: HashMap::new(),
wildcard_actions: vec![],
input_events: vec![],
next_index: 0,
}
}

pub fn get_control_id(&self, raw_control: &REvent::RawControl) -> Option<&ControlId> {
self.control_ids.get(raw_control)
pub fn set_action(
&mut self,
control: REvent::Control,
action: Action,
mask: Option<BitFlags<InputKind>>,
) {
self.control_map.insert(control, (action, mask));
self.control_map_rev.insert(action, control);
}

pub fn set_wildcard_action(&mut self, action: Action, mask: Option<BitFlags<InputKind>>) {
self.wildcard_actions.push((action, mask));
}

pub fn get_raw_control(&self, control_id: &ControlId) -> Option<&REvent::RawControl> {
self.control_ids_rev.get(control_id)
pub fn get_action(
&self,
control: &REvent::Control,
) -> Option<&(Action, Option<BitFlags<InputKind>>)> {
self.control_map.get(control)
}

pub fn get_input_event(&mut self, raw_event: &REvent) -> InputEvent<DId> {
let index = self.next_index;
self.next_index += 1;
pub fn get_control(&self, action: &Action) -> Option<&REvent::Control> {
self.control_map_rev.get(action)
}

pub fn update(&mut self, raw_event: &REvent) -> usize {
let mut count: usize = 0;
let device_id = raw_event.get_device_id();
let control_id = self._get_or_init_control_id(&raw_event.get_raw_control());
let value = raw_event.get_input_value();
let raw_control = raw_event.get_control();
let control_action = self.control_map.get(&raw_control);
let value: OnceCell<InputValue> = OnceCell::new();

InputEvent {
index,
created_at: self.start_time.elapsed(),
device_id,
control_id,
value,
if let Some((action, mask)) = control_action {
let value = value.get_or_init(|| raw_event.get_input_value());
if Self::_push_input_event(
&mut self.next_index,
&mut self.input_events,
self.start_time.elapsed(),
device_id,
*action,
*value,
mask,
) {
count += 1;
}
}

for (action, mask) in &self.wildcard_actions {
let value = value.get_or_init(|| raw_event.get_input_value());
if Self::_push_input_event(
&mut self.next_index,
&mut self.input_events,
self.start_time.elapsed(),
device_id,
*action,
*value,
mask,
) {
count += 1;
}
}

count
}

pub fn get_input_event_count(&self) -> usize {
self.input_events.len()
}

fn _get_or_init_control_id(&mut self, raw_control: &REvent::RawControl) -> ControlId {
match self.control_ids.get(&raw_control) {
None => {
let control_id = ControlId(self.next_control_id);
self.next_control_id += 1;
self.control_ids.insert(*raw_control, control_id.clone());
self.control_ids_rev.insert(control_id, raw_control.clone());
control_id
pub fn get_nth_last_input_event(&self, offset: usize) -> Option<&InputEvent<DId, Action>> {
if (offset + 1) > self.input_events.len() {
return None;
}
Some(&self.input_events[self.input_events.len() - (offset + 1)])
}

pub fn flush_input_events(&mut self) {
self.input_events.clear();
}

fn _push_input_event(
next_index: &mut u64,
input_events: &mut Vec<InputEvent<DId, Action>>,
created_at: Duration,
device_id: DId,
action: Action,
value: InputValue,
mask: &Option<BitFlags<InputKind>>,
) -> bool {
if let Some(mask) = mask {
if !mask.intersects(value.kind()) {
return false;
}
Some(control_id) => *control_id,
}
let index = *next_index;
*next_index += 1;
input_events.push(InputEvent {
index,
created_at,
device_id,
action,
value,
});
true
}
}
31 changes: 21 additions & 10 deletions src/input/gamepad.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::{InputValue, RawDeviceId, RawEvent};
use super::{Control, InputKind, InputValue, RawDeviceId, RawEvent};
use enumflags2::BitFlags;
use gilrs::{Event, EventType};

#[derive(Debug)]
Expand All @@ -17,28 +18,28 @@ impl RawGamepadEvent {
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RawGamepadControl {
pub enum GamepadControl {
Connection,
Button(gilrs::Button),
Axis(gilrs::Axis),
}

impl RawEvent<RawDeviceId> for RawGamepadEvent {
type RawControl = RawGamepadControl;
type Control = GamepadControl;

fn get_device_id(&self) -> RawDeviceId {
RawDeviceId::Gamepad(self.device_id)
}

fn get_raw_control(&self) -> Self::RawControl {
fn get_control(&self) -> Self::Control {
match self.event {
EventType::ButtonPressed(button, _) => RawGamepadControl::Button(button),
EventType::ButtonPressed(button, _) => GamepadControl::Button(button),
EventType::ButtonRepeated(_, _) => todo!(),
EventType::ButtonReleased(button, _) => RawGamepadControl::Button(button),
EventType::ButtonChanged(button, _, _) => RawGamepadControl::Button(button),
EventType::AxisChanged(axis, _, _) => RawGamepadControl::Axis(axis),
EventType::Connected => RawGamepadControl::Connection,
EventType::Disconnected => RawGamepadControl::Connection,
EventType::ButtonReleased(button, _) => GamepadControl::Button(button),
EventType::ButtonChanged(button, _, _) => GamepadControl::Button(button),
EventType::AxisChanged(axis, _, _) => GamepadControl::Axis(axis),
EventType::Connected => GamepadControl::Connection,
EventType::Disconnected => GamepadControl::Connection,
EventType::Dropped => todo!(),
}
}
Expand All @@ -56,3 +57,13 @@ impl RawEvent<RawDeviceId> for RawGamepadEvent {
}
}
}

impl Control for GamepadControl {
fn kind(&self) -> BitFlags<InputKind> {
match self {
GamepadControl::Connection => InputKind::Digital.into(),
GamepadControl::Button(_) => InputKind::Digital | InputKind::Analog,
GamepadControl::Axis(_) => InputKind::Analog.into(),
}
}
}
13 changes: 10 additions & 3 deletions src/input/kbd.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::{InputValue, RawDeviceId, RawEvent};
use super::{Control, InputKind, InputValue, RawDeviceId, RawEvent};
use enumflags2::BitFlags;

#[derive(Debug)]
pub struct RawKeyboardEvent {
Expand All @@ -7,17 +8,23 @@ pub struct RawKeyboardEvent {
}

impl RawEvent<RawDeviceId> for RawKeyboardEvent {
type RawControl = winit::keyboard::PhysicalKey;
type Control = winit::keyboard::PhysicalKey;

fn get_device_id(&self) -> RawDeviceId {
RawDeviceId::Keyboard(self.device_id)
}

fn get_raw_control(&self) -> Self::RawControl {
fn get_control(&self) -> Self::Control {
self.event.physical_key
}

fn get_input_value(&self) -> InputValue {
InputValue::Digital(self.event.state.is_pressed())
}
}

impl Control for winit::keyboard::PhysicalKey {
fn kind(&self) -> BitFlags<InputKind> {
InputKind::Digital.into()
}
}
Loading

0 comments on commit 485c6f7

Please sign in to comment.