From 76f81d6b0258510924f74b25c1c01fa99f927bee Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Fri, 3 Nov 2023 21:07:02 -0400 Subject: [PATCH 1/3] Create an input module to cleanup code --- src/game.rs | 2 +- src/input.rs | 351 --------------------------------- src/input/buttons.rs | 95 +++++++++ src/input/events.rs | 109 ++++++++++ src/{ => input}/input_event.rs | 0 src/input/mod.rs | 11 ++ src/input/system.rs | 125 ++++++++++++ src/lib.rs | 1 - src/render/window.rs | 2 +- 9 files changed, 342 insertions(+), 354 deletions(-) delete mode 100755 src/input.rs create mode 100644 src/input/buttons.rs create mode 100644 src/input/events.rs rename src/{ => input}/input_event.rs (100%) create mode 100644 src/input/mod.rs create mode 100755 src/input/system.rs diff --git a/src/game.rs b/src/game.rs index 038aa42..05d55f2 100755 --- a/src/game.rs +++ b/src/game.rs @@ -12,7 +12,7 @@ use tracing_subscriber::{ use winit::{window::{WindowBuilder, Window}, event::{Event, WindowEvent, KeyboardInput, ElementState, VirtualKeyCode, DeviceEvent}, event_loop::{EventLoop, ControlFlow}}; -use crate::{render::{renderer::{Renderer, BasicRenderer}, window::WindowOptions}, input_event::InputEvent, ecs::{SimpleSystem, SystemDispatcher, EventQueue, Events}, plugin::Plugin, change_tracker::Ct}; +use crate::{render::{renderer::{Renderer, BasicRenderer}, window::WindowOptions}, input::InputEvent, ecs::{SimpleSystem, SystemDispatcher, EventQueue, Events}, plugin::Plugin, change_tracker::Ct}; pub struct Controls<'a> { pub world: &'a mut edict::World, diff --git a/src/input.rs b/src/input.rs deleted file mode 100755 index 46e61c5..0000000 --- a/src/input.rs +++ /dev/null @@ -1,351 +0,0 @@ -use std::{collections::{HashMap, hash_map::DefaultHasher}, hash::{Hash, Hasher}}; - -use glam::Vec2; -use tracing::debug; -use winit::event::{ElementState, MouseScrollDelta}; - -use crate::{ecs::{SimpleSystem, EventQueue}, input_event::InputEvent, plugin::Plugin,}; - -pub type KeyCode = winit::event::VirtualKeyCode; - -#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] -pub enum ButtonEvent { - Pressed(T), - Released(T), - JustPressed(T), -} - -impl ButtonEvent { - fn take_val(self) -> T { - match self { - ButtonEvent::Pressed(t) => t, - ButtonEvent::JustPressed(t) => t, - ButtonEvent::Released(t) => t, - } - } - - fn clone_val(&self) -> T { - self.clone().take_val() - } -} - -#[derive(Clone, Debug, PartialEq)] -pub struct MouseMotion { - pub delta: Vec2, -} - -#[derive(Clone, Debug, PartialEq)] -pub struct MouseWheel { - pub delta: Vec2, -} - -#[derive(Clone, Debug, PartialEq)] -pub struct MouseExact { - pub pos: Vec2, -} - -#[derive(Clone, Copy, Debug, PartialEq, Hash)] -pub enum TouchPhase { - Started, - Moved, - Ended, - Cancelled, -} - -impl From for TouchPhase { - fn from(value: winit::event::TouchPhase) -> Self { - match value { - winit::event::TouchPhase::Started => TouchPhase::Started, - winit::event::TouchPhase::Moved => TouchPhase::Moved, - winit::event::TouchPhase::Ended => TouchPhase::Ended, - winit::event::TouchPhase::Cancelled => TouchPhase::Cancelled, - } - } -} - -/// Translated `Force` from `winit` crate -#[derive(Clone, Debug, PartialEq)] -pub enum Force { - Calibrated { - force: f64, - max_possible_force: f64, - altitude_angle: Option, - }, - Normalized(f64), -} - -impl From for Force { - fn from(value: winit::event::Force) -> Self { - match value { - winit::event::Force::Calibrated { force, max_possible_force, altitude_angle } => Self::Calibrated { force, max_possible_force, altitude_angle }, - winit::event::Force::Normalized(v) => Self::Normalized(v), - } - } -} - -/// Translated `WindowEvent::Touch` from `winit` crate -#[derive(Clone, Debug, PartialEq)] -pub struct Touch { - pub phase: TouchPhase, - pub location: Vec2, - pub force: Option, - pub finger_id: u64, -} - -#[derive(Clone, Debug, PartialEq)] -pub struct Touches { - pub touches: Vec, -} - -impl Default for Touches { - fn default() -> Self { - Self::new() - } -} - -impl Touches { - pub fn new() -> Self { - Self { - touches: Vec::new() - } - } -} - -/// A mouse button event -/// -/// Translated `WindowEvent::MouseButton` from `winit` crate -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] -pub enum MouseButton { - Left, - Right, - Middle, - Other(u16), -} - -#[derive(Clone, Debug, PartialEq)] -pub enum MouseScrollUnit { - Line(Vec2), - Pixel(Vec2) -} - -#[derive(Clone, Debug, PartialEq)] -pub struct MouseScroll { - pub unit: MouseScrollUnit, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct CursorEnteredWindow; - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct CursorLeftWindow; - -#[derive(Clone)] -pub struct InputButtons { - // the u64 as the key is the hashed value of T. this makes it easier to - // search for a button and see its state - button_events: HashMap>, -} - -impl Default for InputButtons { - fn default() -> Self { - Self::new() - } -} - -impl InputButtons { - pub fn new() -> Self { - Self { - button_events: HashMap::new(), - } - } - - fn get_button_hash(button: &T) -> u64 { - let mut hasher = DefaultHasher::new(); - button.hash(&mut hasher); - hasher.finish() - } - - /// Add a button event to this input type - pub fn add_input(&mut self, button: ButtonEvent) { - let hash = Self::get_button_hash(&button.clone_val()); - - self.button_events.insert(hash, button); - } - - pub(crate) fn add_input_from_winit(&mut self, button: T, state: ElementState) { - let event = match state { - ElementState::Pressed if self.is_pressed(button.clone()) => ButtonEvent::Pressed(button), - ElementState::Pressed => ButtonEvent::JustPressed(button), - ElementState::Released => ButtonEvent::Released(button), - }; - - self.add_input(event); - } - - /// Returns true if the button is pressed (or was just pressed) - pub fn is_pressed(&self, button: T) -> bool - { - let hash = Self::get_button_hash(&button); - match self.button_events.get(&hash) { - Some(button_event) => match button_event { - // this if statement should always be true, but just in case ;) - ButtonEvent::Pressed(b) | ButtonEvent::JustPressed(b) if button == *b => true, - _ => false, - }, - None => false - } - } - - /// Returns true if the button was just pressed this tick - pub fn was_just_pressed(&self, button: T) -> bool { - let hash = Self::get_button_hash(&button); - match self.button_events.get(&hash) { - Some(button_event) => match button_event { - // this if statement should always be true, but just in case ;) - ButtonEvent::JustPressed(b) if button == *b => true, - _ => false, - }, - None => false - } - } -} - -trait InputStorage { - fn update_just_pressed(&mut self); -} - -impl InputStorage for InputButtons { - fn update_just_pressed(&mut self) { - self.button_events.retain(|_hash, button| { - match button { - // remove released, no need to keep those around. - ButtonEvent::Released(_) => { - return false; - }, - ButtonEvent::JustPressed(b) => { - *button = ButtonEvent::Pressed(b.clone()); - }, - _ => {}, - } - - true - }); - } -} - -#[derive(Default)] -pub struct InputSystem; - -impl InputSystem { - pub fn update(&mut self, event: &InputEvent, world: &mut edict::World) -> bool { - let event_queue = world.get_resource_mut::(); - if event_queue.is_none() { - return false; - } - let mut event_queue = event_queue.unwrap(); - - match event { - InputEvent::KeyboardInput { input, .. } => { - if let Some(code) = input.virtual_keycode { - drop(event_queue); - let e = world.with_resource(InputButtons::::new); - //let mut e = with_resource_mut(world, || InputButtons::::new()); - e.add_input_from_winit(code, input.state); - } - }, - InputEvent::MouseMotion { delta, .. } => { - let delta = MouseMotion { - delta: Vec2::new(delta.0 as f32, delta.1 as f32) - }; - - event_queue.trigger_event(delta); - }, - InputEvent::CursorMoved { position, .. } => { - let exact = MouseExact { - pos: Vec2::new(position.x as f32, position.y as f32) - }; - - event_queue.trigger_event(exact); - }, - InputEvent::CursorEntered { .. } => { - let event = CursorEnteredWindow {}; - event_queue.trigger_event(event); - }, - InputEvent::CursorLeft { .. } => { - let event = CursorLeftWindow {}; - event_queue.trigger_event(event); - }, - InputEvent::MouseWheel { delta, .. } => { - let event = match delta { - MouseScrollDelta::LineDelta(x, y) => MouseScroll { - unit: MouseScrollUnit::Line(Vec2::new(*x, *y)), - }, - MouseScrollDelta::PixelDelta(delta) => MouseScroll { - unit: MouseScrollUnit::Pixel(Vec2::new(delta.x as f32, delta.y as f32)), - }, - }; - - event_queue.trigger_event(event); - }, //MouseButton - InputEvent::MouseInput { button, state, .. } => { - let button_event = match button { - winit::event::MouseButton::Left => MouseButton::Left, - winit::event::MouseButton::Right => MouseButton::Right, - winit::event::MouseButton::Middle => MouseButton::Middle, - winit::event::MouseButton::Other(v) => MouseButton::Other(*v), - }; - - event_queue.trigger_event(button_event); - drop(event_queue); - - let e = world.with_resource(InputButtons::::new); - e.add_input_from_winit(button_event, *state); - }, - InputEvent::Touch(t) => { - drop(event_queue); - - let touch = Touch { - phase: TouchPhase::from(t.phase), - location: Vec2::new(t.location.x as f32, t.location.y as f32), - force: t.force.map(Force::from), - finger_id: t.id, - }; - - let touches = world.with_resource(Touches::new); - touches.touches.push(touch); - }, - _ => {}, - } - - false - } -} - -impl SimpleSystem for InputSystem { - fn execute_mut(&mut self, world: &mut edict::World) -> anyhow::Result<()> { - let queue = world.get_resource_mut::() - .and_then(|q| q.read_events::()); - - if queue.is_none() { - return Ok(()); - } - - let mut events = queue.unwrap(); - - while let Some(event) = events.pop_front() { - self.update(&event, world); - } - - Ok(()) - } -} - -/// Plugin that runs InputSystem -#[derive(Default)] -pub struct InputPlugin; - -impl Plugin for InputPlugin { - fn setup(&self, game: &mut crate::game::Game) { - game.with_system("input", InputSystem, &[]); - } -} \ No newline at end of file diff --git a/src/input/buttons.rs b/src/input/buttons.rs new file mode 100644 index 0000000..e279b03 --- /dev/null +++ b/src/input/buttons.rs @@ -0,0 +1,95 @@ +use std::{hash::{Hash, Hasher}, collections::{HashMap, hash_map::DefaultHasher}}; + +use winit::event::ElementState; + +#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] +pub enum ButtonEvent { + Pressed(T), + Released(T), + JustPressed(T), +} + +impl ButtonEvent { + fn take_val(self) -> T { + match self { + ButtonEvent::Pressed(t) => t, + ButtonEvent::JustPressed(t) => t, + ButtonEvent::Released(t) => t, + } + } + + fn clone_val(&self) -> T { + self.clone().take_val() + } +} + +#[derive(Clone)] +pub struct InputButtons { + // the u64 as the key is the hashed value of T. this makes it easier to + // search for a button and see its state + button_events: HashMap>, +} + +impl Default for InputButtons { + fn default() -> Self { + Self::new() + } +} + +impl InputButtons { + pub fn new() -> Self { + Self { + button_events: HashMap::new(), + } + } + + fn get_button_hash(button: &T) -> u64 { + let mut hasher = DefaultHasher::new(); + button.hash(&mut hasher); + hasher.finish() + } + + /// Add a button event to this input type + pub fn add_input(&mut self, button: ButtonEvent) { + let hash = Self::get_button_hash(&button.clone_val()); + + self.button_events.insert(hash, button); + } + + pub(crate) fn add_input_from_winit(&mut self, button: T, state: ElementState) { + let event = match state { + ElementState::Pressed if self.is_pressed(button.clone()) => ButtonEvent::Pressed(button), + ElementState::Pressed => ButtonEvent::JustPressed(button), + ElementState::Released => ButtonEvent::Released(button), + }; + + self.add_input(event); + } + + /// Returns true if the button is pressed (or was just pressed) + pub fn is_pressed(&self, button: T) -> bool + { + let hash = Self::get_button_hash(&button); + match self.button_events.get(&hash) { + Some(button_event) => match button_event { + // this if statement should always be true, but just in case ;) + ButtonEvent::Pressed(b) | ButtonEvent::JustPressed(b) if button == *b => true, + _ => false, + }, + None => false + } + } + + /// Returns true if the button was just pressed this tick + pub fn was_just_pressed(&self, button: T) -> bool { + let hash = Self::get_button_hash(&button); + match self.button_events.get(&hash) { + Some(button_event) => match button_event { + // this if statement should always be true, but just in case ;) + ButtonEvent::JustPressed(b) if button == *b => true, + _ => false, + }, + None => false + } + } +} \ No newline at end of file diff --git a/src/input/events.rs b/src/input/events.rs new file mode 100644 index 0000000..3124a8d --- /dev/null +++ b/src/input/events.rs @@ -0,0 +1,109 @@ +#[derive(Clone, Debug, PartialEq)] +pub struct MouseMotion { + pub delta: glam::Vec2, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct MouseWheel { + pub delta: glam::Vec2, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct MouseExact { + pub pos: glam::Vec2, +} + +#[derive(Clone, Copy, Debug, PartialEq, Hash)] +pub enum TouchPhase { + Started, + Moved, + Ended, + Cancelled, +} + +impl From for TouchPhase { + fn from(value: winit::event::TouchPhase) -> Self { + match value { + winit::event::TouchPhase::Started => TouchPhase::Started, + winit::event::TouchPhase::Moved => TouchPhase::Moved, + winit::event::TouchPhase::Ended => TouchPhase::Ended, + winit::event::TouchPhase::Cancelled => TouchPhase::Cancelled, + } + } +} + +/// Translated `Force` from `winit` crate +#[derive(Clone, Debug, PartialEq)] +pub enum Force { + Calibrated { + force: f64, + max_possible_force: f64, + altitude_angle: Option, + }, + Normalized(f64), +} + +impl From for Force { + fn from(value: winit::event::Force) -> Self { + match value { + winit::event::Force::Calibrated { force, max_possible_force, altitude_angle } => Self::Calibrated { force, max_possible_force, altitude_angle }, + winit::event::Force::Normalized(v) => Self::Normalized(v), + } + } +} + +/// Translated `WindowEvent::Touch` from `winit` crate +#[derive(Clone, Debug, PartialEq)] +pub struct Touch { + pub phase: TouchPhase, + pub location: glam::Vec2, + pub force: Option, + pub finger_id: u64, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Touches { + pub touches: Vec, +} + +impl Default for Touches { + fn default() -> Self { + Self::new() + } +} + +impl Touches { + pub fn new() -> Self { + Self { + touches: Vec::new() + } + } +} + +/// A mouse button event +/// +/// Translated `WindowEvent::MouseButton` from `winit` crate +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +pub enum MouseButton { + Left, + Right, + Middle, + Other(u16), +} + +#[derive(Clone, Debug, PartialEq)] +pub enum MouseScrollUnit { + Line(glam::Vec2), + Pixel(glam::Vec2) +} + +#[derive(Clone, Debug, PartialEq)] +pub struct MouseScroll { + pub unit: MouseScrollUnit, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct CursorEnteredWindow; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct CursorLeftWindow; \ No newline at end of file diff --git a/src/input_event.rs b/src/input/input_event.rs similarity index 100% rename from src/input_event.rs rename to src/input/input_event.rs diff --git a/src/input/mod.rs b/src/input/mod.rs new file mode 100644 index 0000000..d322eea --- /dev/null +++ b/src/input/mod.rs @@ -0,0 +1,11 @@ +pub mod system; +pub use system::*; + +pub mod input_event; +pub use input_event::*; + +pub mod events; +pub use events::*; + +pub mod buttons; +pub use buttons::*; \ No newline at end of file diff --git a/src/input/system.rs b/src/input/system.rs new file mode 100755 index 0000000..bd7f535 --- /dev/null +++ b/src/input/system.rs @@ -0,0 +1,125 @@ +use glam::Vec2; +use winit::event::MouseScrollDelta; + +use crate::{ecs::{SimpleSystem, EventQueue}, plugin::Plugin,}; + +use super::{events::*, InputButtons, InputEvent}; + +pub type KeyCode = winit::event::VirtualKeyCode; + +#[derive(Default)] +pub struct InputSystem; + +impl InputSystem { + pub fn process_event(&mut self, world: &mut edict::World, event: &InputEvent) -> bool { + let event_queue = world.get_resource_mut::(); + if event_queue.is_none() { + return false; + } + let mut event_queue = event_queue.unwrap(); + + match event { + InputEvent::KeyboardInput { input, .. } => { + if let Some(code) = input.virtual_keycode { + drop(event_queue); + let e = world.with_resource(InputButtons::::new); + //let mut e = with_resource_mut(world, || InputButtons::::new()); + e.add_input_from_winit(code, input.state); + } + }, + InputEvent::MouseMotion { delta, .. } => { + let delta = MouseMotion { + delta: Vec2::new(delta.0 as f32, delta.1 as f32) + }; + + event_queue.trigger_event(delta); + }, + InputEvent::CursorMoved { position, .. } => { + let exact = MouseExact { + pos: Vec2::new(position.x as f32, position.y as f32) + }; + + event_queue.trigger_event(exact); + }, + InputEvent::CursorEntered { .. } => { + let event = CursorEnteredWindow {}; + event_queue.trigger_event(event); + }, + InputEvent::CursorLeft { .. } => { + let event = CursorLeftWindow {}; + event_queue.trigger_event(event); + }, + InputEvent::MouseWheel { delta, .. } => { + let event = match delta { + MouseScrollDelta::LineDelta(x, y) => MouseScroll { + unit: MouseScrollUnit::Line(Vec2::new(*x, *y)), + }, + MouseScrollDelta::PixelDelta(delta) => MouseScroll { + unit: MouseScrollUnit::Pixel(Vec2::new(delta.x as f32, delta.y as f32)), + }, + }; + + event_queue.trigger_event(event); + }, //MouseButton + InputEvent::MouseInput { button, state, .. } => { + let button_event = match button { + winit::event::MouseButton::Left => MouseButton::Left, + winit::event::MouseButton::Right => MouseButton::Right, + winit::event::MouseButton::Middle => MouseButton::Middle, + winit::event::MouseButton::Other(v) => MouseButton::Other(*v), + }; + + event_queue.trigger_event(button_event); + drop(event_queue); + + let e = world.with_resource(InputButtons::::new); + e.add_input_from_winit(button_event, *state); + }, + InputEvent::Touch(t) => { + drop(event_queue); + + let touch = Touch { + phase: TouchPhase::from(t.phase), + location: Vec2::new(t.location.x as f32, t.location.y as f32), + force: t.force.map(Force::from), + finger_id: t.id, + }; + + let touches = world.with_resource(Touches::new); + touches.touches.push(touch); + }, + _ => {}, + } + + false + } +} + +impl SimpleSystem for InputSystem { + fn execute_mut(&mut self, world: &mut edict::World) -> anyhow::Result<()> { + let queue = world.get_resource_mut::() + .and_then(|q| q.read_events::()); + + if queue.is_none() { + return Ok(()); + } + + let mut events = queue.unwrap(); + + while let Some(event) = events.pop_front() { + self.process_event(world, &event); + } + + Ok(()) + } +} + +/// Plugin that runs InputSystem +#[derive(Default)] +pub struct InputPlugin; + +impl Plugin for InputPlugin { + fn setup(&self, game: &mut crate::game::Game) { + game.with_system("input", InputSystem, &[]); + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 0182ed7..b776596 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,6 @@ pub mod game; pub mod render; -pub mod input_event; pub mod resources; pub mod ecs; pub mod math; diff --git a/src/render/window.rs b/src/render/window.rs index 82700c5..3a42f98 100644 --- a/src/render/window.rs +++ b/src/render/window.rs @@ -6,7 +6,7 @@ use winit::{window::{Window, Fullscreen}, dpi::{LogicalPosition, LogicalSize, Ph pub use winit::window::{CursorGrabMode, CursorIcon, Icon, Theme, WindowButtons, WindowLevel}; -use crate::{plugin::Plugin, change_tracker::Ct, ecs::EventQueue, input_event::InputEvent}; +use crate::{plugin::Plugin, change_tracker::Ct, ecs::EventQueue, input::InputEvent}; #[derive(Default, Clone)] pub enum WindowMode { From 75c0377d9cf2e936407d6a73cb62363bbeb20916 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sat, 4 Nov 2023 11:34:27 -0400 Subject: [PATCH 2/3] Create structs for the input actions --- examples/testbed/src/main.rs | 18 ++- src/input/action.rs | 253 +++++++++++++++++++++++++++++++++++ src/input/buttons.rs | 16 ++- src/input/mod.rs | 5 +- src/lib.rs | 1 + 5 files changed, 286 insertions(+), 7 deletions(-) create mode 100644 src/input/action.rs diff --git a/examples/testbed/src/main.rs b/examples/testbed/src/main.rs index 4e415f1..fd8aeea 100644 --- a/examples/testbed/src/main.rs +++ b/examples/testbed/src/main.rs @@ -1,4 +1,4 @@ -use lyra_engine::{math::{self, Vec3}, ecs::{World, components::{transform::TransformComponent, camera::CameraComponent, model::ModelComponent, DeltaTime}, EventQueue, SimpleSystem, Component, Criteria, CriteriaSchedule, BatchedSystem}, math::Transform, input::{KeyCode, InputButtons, MouseMotion}, game::Game, plugin::Plugin, render::window::{CursorGrabMode, WindowOptions}, change_tracker::Ct}; +use lyra_engine::{math::{self, Vec3}, ecs::{World, components::{transform::TransformComponent, camera::CameraComponent, model::ModelComponent, DeltaTime}, EventQueue, SimpleSystem, Component, Criteria, CriteriaSchedule, BatchedSystem}, math::Transform, input::{KeyCode, InputButtons, MouseMotion, ActionHandler, Layout, Action, ActionKind, LayoutId, ActionMapping, Binding, ActionSource, ActionMappingId}, game::Game, plugin::Plugin, render::window::{CursorGrabMode, WindowOptions}, change_tracker::Ct}; use lyra_engine::assets::{ResourceManager, Model}; mod free_fly_camera; @@ -58,6 +58,22 @@ struct TpsAccumulator(f32); #[async_std::main] async fn main() { + let _action_handler = ActionHandler::new() + .add_layout(LayoutId::from(0)) + .add_action("forward_backward", Action::new(ActionKind::Button)) + .add_action("left_right", Action::new(ActionKind::Button)) + .add_mapping(ActionMapping::new(LayoutId::from(0), ActionMappingId::from(0)) + .bind("forward_backward", &[ + ActionSource::Keyboard(KeyCode::W).into_binding_modifier(1.0), + ActionSource::Keyboard(KeyCode::S).into_binding_modifier(-1.0) + ]) + .bind("left_right", &[ + ActionSource::Keyboard(KeyCode::A).into_binding_modifier(1.0), + ActionSource::Keyboard(KeyCode::D).into_binding_modifier(-1.0) + ]) + .finish() + ); + let setup_sys = |world: &mut World| -> anyhow::Result<()> { { let mut window_options = world.get_resource_mut::>().unwrap(); diff --git a/src/input/action.rs b/src/input/action.rs new file mode 100644 index 0000000..549a7db --- /dev/null +++ b/src/input/action.rs @@ -0,0 +1,253 @@ +use std::{collections::HashMap, cell::RefCell, borrow::BorrowMut}; + +use edict::action; + +use crate::castable_any::CastableAny; + +use super::{Button, KeyCode}; + +#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] +enum GamepadFormat { + DualAxis, + Joystick, +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] +enum GamepadButton { + FaceBottom, + FaceLeft, + FaceRight, + FaceTop, + VirtualConfirm, + VirtualDeny, + LThumbstick, + RThumbstick, + DPadUp, + DPadDown, + DPadLeft, + DPadRight, + LShoulder, + RShoulder, + LTrigger, + RTrigger, + Special, + LSpecial, + RSpecial, +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] +enum GamepadAxis { + LThumbstickX, + LThumbstickY, + RThumbstickX, + RThumbstickY, + LTrigger, + RTrigger, +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] +enum GamepadInput { + Button(GamepadButton), + Axis(GamepadAxis), +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub enum ActionSource { + Keyboard(KeyCode), + Gamepad(GamepadFormat, GamepadInput), +} + +impl ActionSource { + /// Convert this Source into a Binding. Uses a default Binding modifier value of `1.0`. + pub fn into_binding(self) -> Binding { + Binding::from_source(self) + } + + /// Convert this Source into a Binding, setting the modifier. + pub fn into_binding_modifier(self, modifier: f32) -> Binding { + Binding::from_source_modifier(self, modifier) + } +} + +#[derive(Clone, Debug)] +pub struct Binding { + pub source: ActionSource, + pub modifier: f32, +} + +impl Binding { + /// Create a binding from a Source. Uses a default value of `1.0` as the modifier. + pub fn from_source(source: ActionSource) -> Self { + Self { + source, + modifier: 1.0, + } + } + + /// Create a binding from a Source, with a modifier + pub fn from_source_modifier(source: ActionSource, modifier: f32) -> Self { + Self { + source, + modifier, + } + } +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub enum ActionKind { + Button, + Axis +} + +#[derive(Clone, Debug)] +pub struct Action { + kind: ActionKind, +} + +impl Action { + pub fn new(kind: ActionKind) -> Self { + Self { + kind, + } + } +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct LayoutId(u32); + +impl From for LayoutId { + fn from(value: u32) -> Self { + Self(value) + } +} + +impl Default for LayoutId { + fn default() -> Self { + Self(0) + } +} + +#[derive(Clone)] +pub struct Layout { + mappings: HashMap, +} + +impl Layout { + pub fn new() -> Self { + Self { + mappings: HashMap::new(), + } + } + + pub fn add_mapping(&mut self, mapping: ActionMapping) -> &mut Self { + self.mappings.insert(mapping.id, mapping); + + self + } +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct ActionMappingId(u32); + +impl From for ActionMappingId { + fn from(value: u32) -> Self { + Self(value) + } +} + +impl Default for ActionMappingId { + fn default() -> Self { + Self(0) + } +} + +#[derive(Clone)] +pub struct ActionMapping { + layout: LayoutId, + id: ActionMappingId, + action_binds: HashMap>, +} + +impl ActionMapping { + pub fn new(layout: LayoutId, id: ActionMappingId) -> Self { + Self { + layout, + id, + action_binds: HashMap::new(), + } + } + + /// Creates a binding for the action. + /// + /// If the action is not in this layout, this will panic! + /// + /// Parameters: + /// * `action_label` - The label corresponding to the action in this Layout. + /// * `bind` - The Binding to add to the Action. + pub fn bind(mut self, action_label: &str, bindings: &[Binding]) -> Self { + let mut bindings = bindings.to_vec(); + + let action_binds = self.action_binds.entry(action_label.to_string()) + .or_insert_with(|| Vec::new()); + action_binds.append(&mut bindings); + + self + } + + /// Creates multiple binding for the action. + /// + /// If the action is not in this layout, this will panic! + /// + /// Parameters: + /// * `action_label` - The label corresponding to the action in this Layout. + /// * `bindings` - The list of Bindings to add to the Action. + /* pub fn add_bindings(&mut self, action_label: String, bindings: &[Binding]) -> &mut Self { + let mut bindings = bindings.to_vec(); + let action_binds = self.action_binds.entry(action_label) + .or_insert_with(|| Vec::new()); + action_binds.append(&mut bindings); + + self + } */ + + pub fn finish(self) -> Self { + self + } +} + +pub struct ActionHandler { + actions: HashMap, + layouts: HashMap, + current_layout: LayoutId, +} + +impl ActionHandler { + pub fn new() -> Self { + Self { + actions: HashMap::new(), + layouts: HashMap::new(), + current_layout: LayoutId::default(), + } + } + + pub fn add_layout(&mut self, id: LayoutId) -> &mut Self { + self.layouts.insert(id, Layout::new()); + + self + } + + pub fn add_action(&mut self, label: &str, action: Action) -> &mut Self { + /* let layout = self.layouts.get_mut(&layout_id).unwrap(); + layout.actions.insert(label.to_string(), action); */ + self.actions.insert(label.to_string(), action); + + self + } + + pub fn add_mapping(&mut self, mapping: ActionMapping) -> &mut Self { + let layout = self.layouts.get_mut(&mapping.layout).unwrap(); + layout.add_mapping(mapping); + + self + } +} \ No newline at end of file diff --git a/src/input/buttons.rs b/src/input/buttons.rs index e279b03..e4205d2 100644 --- a/src/input/buttons.rs +++ b/src/input/buttons.rs @@ -2,14 +2,20 @@ use std::{hash::{Hash, Hasher}, collections::{HashMap, hash_map::DefaultHasher}} use winit::event::ElementState; +//pub trait Button : Clone + Hash + Eq + PartialEq + 'static {} +/* pub trait Button {} +impl Button for T {} */ + +pub trait Button = Clone + Hash + Eq + PartialEq + 'static; + #[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] -pub enum ButtonEvent { +pub enum ButtonEvent { Pressed(T), Released(T), JustPressed(T), } -impl ButtonEvent { +impl ButtonEvent { fn take_val(self) -> T { match self { ButtonEvent::Pressed(t) => t, @@ -24,19 +30,19 @@ impl ButtonEvent { } #[derive(Clone)] -pub struct InputButtons { +pub struct InputButtons { // the u64 as the key is the hashed value of T. this makes it easier to // search for a button and see its state button_events: HashMap>, } -impl Default for InputButtons { +impl Default for InputButtons { fn default() -> Self { Self::new() } } -impl InputButtons { +impl InputButtons { pub fn new() -> Self { Self { button_events: HashMap::new(), diff --git a/src/input/mod.rs b/src/input/mod.rs index d322eea..f03bd5f 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -8,4 +8,7 @@ pub mod events; pub use events::*; pub mod buttons; -pub use buttons::*; \ No newline at end of file +pub use buttons::*; + +pub mod action; +pub use action::*; \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index b776596..0892b77 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ #![feature(hash_extract_if)] #![feature(lint_reasons)] +#![feature(trait_alias)] pub mod game; pub mod render; From 8d6e675c82d9c8749c4827e10e12ccb2a7e6d26f Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sun, 5 Nov 2023 22:50:57 -0500 Subject: [PATCH 3/3] Implement a decent first pass of the input action system This still needs some work, mostly just names of things and finding a better way to add the InputActionPLugin and ActionHandler --- examples/testbed/src/main.rs | 56 +++++++--- src/input/action.rs | 198 +++++++++++++++++++++++++++++++++-- src/input/buttons.rs | 2 + 3 files changed, 228 insertions(+), 28 deletions(-) diff --git a/examples/testbed/src/main.rs b/examples/testbed/src/main.rs index fd8aeea..710d01d 100644 --- a/examples/testbed/src/main.rs +++ b/examples/testbed/src/main.rs @@ -1,8 +1,9 @@ -use lyra_engine::{math::{self, Vec3}, ecs::{World, components::{transform::TransformComponent, camera::CameraComponent, model::ModelComponent, DeltaTime}, EventQueue, SimpleSystem, Component, Criteria, CriteriaSchedule, BatchedSystem}, math::Transform, input::{KeyCode, InputButtons, MouseMotion, ActionHandler, Layout, Action, ActionKind, LayoutId, ActionMapping, Binding, ActionSource, ActionMappingId}, game::Game, plugin::Plugin, render::window::{CursorGrabMode, WindowOptions}, change_tracker::Ct}; +use lyra_engine::{math::{self, Vec3}, ecs::{World, components::{transform::TransformComponent, camera::CameraComponent, model::ModelComponent, DeltaTime}, EventQueue, SimpleSystem, Component, Criteria, CriteriaSchedule, BatchedSystem}, math::Transform, input::{KeyCode, InputButtons, MouseMotion, ActionHandler, Layout, Action, ActionKind, LayoutId, ActionMapping, Binding, ActionSource, ActionMappingId, InputActionPlugin, ActionState}, game::Game, plugin::Plugin, render::window::{CursorGrabMode, WindowOptions}, change_tracker::Ct}; use lyra_engine::assets::{ResourceManager, Model}; mod free_fly_camera; use free_fly_camera::{FreeFlyCameraPlugin, FreeFlyCamera}; +use tracing::debug; struct FixedTimestep { max_tps: u32, @@ -58,22 +59,6 @@ struct TpsAccumulator(f32); #[async_std::main] async fn main() { - let _action_handler = ActionHandler::new() - .add_layout(LayoutId::from(0)) - .add_action("forward_backward", Action::new(ActionKind::Button)) - .add_action("left_right", Action::new(ActionKind::Button)) - .add_mapping(ActionMapping::new(LayoutId::from(0), ActionMappingId::from(0)) - .bind("forward_backward", &[ - ActionSource::Keyboard(KeyCode::W).into_binding_modifier(1.0), - ActionSource::Keyboard(KeyCode::S).into_binding_modifier(-1.0) - ]) - .bind("left_right", &[ - ActionSource::Keyboard(KeyCode::A).into_binding_modifier(1.0), - ActionSource::Keyboard(KeyCode::D).into_binding_modifier(-1.0) - ]) - .finish() - ); - let setup_sys = |world: &mut World| -> anyhow::Result<()> { { let mut window_options = world.get_resource_mut::>().unwrap(); @@ -143,10 +128,47 @@ async fn main() { game.with_system("fixed", sys, &[]); fps_plugin(game); }; + + let action_handler_plugin = |game: &mut Game| { + let action_handler = ActionHandler::new() + .add_layout(LayoutId::from(0)) + .add_action("forward_backward", Action::new(ActionKind::Button)) + .add_action("left_right", Action::new(ActionKind::Button)) + .add_mapping(ActionMapping::new(LayoutId::from(0), ActionMappingId::from(0)) + .bind("forward_backward", &[ + ActionSource::Keyboard(KeyCode::W).into_binding_modifier(1.0), + ActionSource::Keyboard(KeyCode::S).into_binding_modifier(-1.0) + ]) + .bind("left_right", &[ + ActionSource::Keyboard(KeyCode::A).into_binding_modifier(1.0), + ActionSource::Keyboard(KeyCode::D).into_binding_modifier(-1.0) + ]) + .finish() + ); + + let test_system = |world: &mut World| -> anyhow::Result<()> { + let handler = world.get_resource::().unwrap(); + + if let Some(alpha) = handler.get_pressed_modifier("forward_backward") { + debug!("'forward_backward': {alpha}"); + } + + if let Some(alpha) = handler.get_pressed_modifier("left_right") { + debug!("'left_right': {alpha}"); + } + + Ok(()) + }; + + game.world().insert_resource(action_handler); + game.with_plugin(InputActionPlugin); + game.with_system("test_actions", test_system, &[]); + }; Game::initialize().await .with_plugin(lyra_engine::DefaultPlugins) .with_startup_system(setup_sys) + .with_plugin(action_handler_plugin) //.with_plugin(fps_plugin) .with_plugin(jiggle_plugin) .with_plugin(FreeFlyCameraPlugin) diff --git a/src/input/action.rs b/src/input/action.rs index 549a7db..187b50d 100644 --- a/src/input/action.rs +++ b/src/input/action.rs @@ -1,10 +1,10 @@ -use std::{collections::HashMap, cell::RefCell, borrow::BorrowMut}; +use std::{collections::HashMap, cell::RefCell, borrow::BorrowMut, ops::Deref}; -use edict::action; +use edict::{action, World}; -use crate::castable_any::CastableAny; +use crate::{castable_any::CastableAny, plugin::Plugin}; -use super::{Button, KeyCode}; +use super::{Button, KeyCode, InputButtons}; #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] enum GamepadFormat { @@ -93,6 +93,24 @@ impl Binding { } } +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum ActionState { + /// No input is being given by the user for the button/axis. + Idle, + /// The button is pressed + Pressed(f32), + /// The button was just pressed + JustPressed(f32), + /// The button was just released + JustReleased, + + //Released, + /// The axis moved + Axis(f32), + /// Some other state with a modifier + Other(f32), +} + #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] pub enum ActionKind { Button, @@ -101,13 +119,15 @@ pub enum ActionKind { #[derive(Clone, Debug)] pub struct Action { - kind: ActionKind, + pub kind: ActionKind, + pub state: ActionState, } impl Action { pub fn new(kind: ActionKind) -> Self { Self { kind, + state: ActionState::Idle, } } } @@ -130,12 +150,14 @@ impl Default for LayoutId { #[derive(Clone)] pub struct Layout { mappings: HashMap, + active_mapping: ActionMappingId, } impl Layout { pub fn new() -> Self { Self { mappings: HashMap::new(), + active_mapping: Default::default(), } } @@ -215,10 +237,12 @@ impl ActionMapping { } } +#[derive(Clone)] pub struct ActionHandler { - actions: HashMap, - layouts: HashMap, - current_layout: LayoutId, + pub actions: HashMap, + pub layouts: HashMap, + pub current_layout: LayoutId, + pub current_mapping: ActionMappingId, } impl ActionHandler { @@ -227,16 +251,17 @@ impl ActionHandler { actions: HashMap::new(), layouts: HashMap::new(), current_layout: LayoutId::default(), + current_mapping: ActionMappingId::default(), } } - pub fn add_layout(&mut self, id: LayoutId) -> &mut Self { + pub fn add_layout(mut self, id: LayoutId) -> Self { self.layouts.insert(id, Layout::new()); self } - pub fn add_action(&mut self, label: &str, action: Action) -> &mut Self { + pub fn add_action(mut self, label: &str, action: Action) -> Self { /* let layout = self.layouts.get_mut(&layout_id).unwrap(); layout.actions.insert(label.to_string(), action); */ self.actions.insert(label.to_string(), action); @@ -244,10 +269,161 @@ impl ActionHandler { self } - pub fn add_mapping(&mut self, mapping: ActionMapping) -> &mut Self { + pub fn add_mapping(mut self, mapping: ActionMapping) -> Self { let layout = self.layouts.get_mut(&mapping.layout).unwrap(); layout.add_mapping(mapping); self } + + /// Returns true if the action is pressed (or was just pressed). + /// + /// This will panic if the action name does not correspond to an action. + pub fn is_action_pressed(&self, action_name: &str) -> bool { + let action = self.actions.get(action_name) + .expect(&format!("Action {action_name} was not found")); + + match action.state { + ActionState::Pressed(_) | ActionState::JustPressed(_) => true, + _ => false + } + } + + /// Returns true if the action was just pressed. + /// + /// This will panic if the action name does not correspond to an action. + pub fn was_action_just_pressed(&self, action_name: &str) -> bool { + let action = self.actions.get(action_name) + .expect(&format!("Action {action_name} was not found")); + + match action.state { + ActionState::JustPressed(_) => true, + _ => false + } + } + + /// Returns true if the action was just released. + /// + /// This will panic if the action name does not correspond to an action. + pub fn was_action_just_released(&self, action_name: &str) -> bool { + let action = self.actions.get(action_name) + .expect(&format!("Action {action_name} was not found")); + + match action.state { + ActionState::JustReleased => true, + _ => false + } + } + + /// Returns an action's state. + /// + /// This will panic if the action name does not correspond to an action. + pub fn get_action_state(&self, action_name: &str) -> ActionState { + let action = self.actions.get(action_name) + .expect(&format!("Action {action_name} was not found")); + + action.state + } + + /// Returns the action's modifier if it is pressed (or was just pressed). + /// Returns `None` if the action's state is not `ActionState::Pressed` or `ActionState::JustPressed`. + /// + /// This will panic if the action name does not correspond to an action. + pub fn get_pressed_modifier(&self, action_name: &str) -> Option { + let action = self.actions.get(action_name) + .expect(&format!("Action {action_name} was not found")); + + match action.state { + ActionState::Pressed(v) | ActionState::JustPressed(v) => Some(v), + _ => None, + } + } + + /// Returns the action's modifier if it was just pressed. + /// Returns `None` if the action's state is not `ActionState::JustPressed`. + /// + /// This will panic if the action name does not correspond to an action. + pub fn get_just_pressed_modifier(&self, action_name: &str) -> Option { + let action = self.actions.get(action_name) + .expect(&format!("Action {action_name} was not found")); + + match action.state { + ActionState::JustPressed(v) => Some(v), + _ => None, + } + } + + /// Returns the action's modifier if its an updated axis. + /// Returns `None` if the action's state is not `ActionState::Axis`. + /// + /// This will panic if the action name does not correspond to an action. + pub fn get_axis_modifier(&self, action_name: &str) -> Option { + let action = self.actions.get(action_name) + .expect(&format!("Action {action_name} was not found")); + + match action.state { + ActionState::Axis(v) => Some(v), + _ => None, + } + } +} + +fn actions_system(world: &mut World) -> anyhow::Result<()> { + let keys = world.get_resource::>() + .map(|r| r.deref().clone()); + + if let Some(keys) = keys { + let mut handler = world.get_resource_mut::() + .expect("No Input Action handler was created in the world!"); + + //handler + let layout = handler.layouts.get(&handler.current_layout).expect("No active layout"); + let mapping = layout.mappings.get(&layout.active_mapping).expect("No active mapping"); + + for (action_name, binds) in mapping.action_binds.clone().iter() { // TODO: dont clone + let mut new_state = None; + + for bind in binds.iter() { + match bind.source { + ActionSource::Keyboard(key) => { + // JustPressed needs to be first, since is_pressed includes buttons that + // were just pressed. + if keys.was_just_pressed(key) { + new_state = Some(ActionState::JustPressed(bind.modifier)); + } else if keys.is_pressed(key) { + new_state = Some(ActionState::Pressed(bind.modifier)); + } + }, + ActionSource::Gamepad(_, _) => todo!(), + } + } + + let action = handler.actions.get_mut(action_name).expect("Action name for binding is invalid!"); + + if let Some(new_state) = new_state { + action.state = new_state; + } else { + match action.state { + ActionState::Idle => {}, + //ActionState::Pressed(_) => todo!(), + //ActionState::JustPressed(_) => todo!(), + ActionState::JustReleased => action.state = ActionState::Idle, + //ActionState::Released => action.state = ActionState::I, + _ => action.state = ActionState::JustReleased, + } + //action.state = ActionState::JustReleased; + } + } + } + + Ok(()) +} + +#[derive(Default, Clone, Copy)] +pub struct InputActionPlugin; + +impl Plugin for InputActionPlugin { + fn setup(&self, game: &mut crate::game::Game) { + game.with_system("input_actions", actions_system, &[]); + } } \ No newline at end of file diff --git a/src/input/buttons.rs b/src/input/buttons.rs index e4205d2..ce8dcc4 100644 --- a/src/input/buttons.rs +++ b/src/input/buttons.rs @@ -64,6 +64,8 @@ impl InputButtons { pub(crate) fn add_input_from_winit(&mut self, button: T, state: ElementState) { let event = match state { + // TODO: there is a pause between pressed events of a key when holding down, so it stays in a just pressed state for too long + // When a button is held down, this would change a just pressed button into a pressed button. ElementState::Pressed if self.is_pressed(button.clone()) => ButtonEvent::Pressed(button), ElementState::Pressed => ButtonEvent::JustPressed(button), ElementState::Released => ButtonEvent::Released(button),