From 5521d4a65931fc65fc6c6e2e60b73a007cf2e404 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sun, 25 Feb 2024 17:06:53 -0500 Subject: [PATCH] scripting: start work on exposing InputActions to lua, implement inserting reflected resource --- Cargo.lock | 40 ++++ examples/testbed/scripts/test.lua | 45 +++++ examples/testbed/src/main.rs | 21 +- lyra-ecs/src/world.rs | 6 +- lyra-game/src/input/action.rs | 123 +++++++++--- lyra-game/src/input/mod.rs | 180 +++++++++++++++++- lyra-game/src/input/system.rs | 2 - lyra-reflect/src/resource.rs | 15 +- lyra-scripting/Cargo.toml | 2 + lyra-scripting/elua | 2 +- lyra-scripting/src/lib.rs | 8 + lyra-scripting/src/lua/providers/ecs.rs | 3 +- lyra-scripting/src/lua/world.rs | 28 ++- .../src/lua/wrappers/input_actions.rs | 147 ++++++++++++++ lyra-scripting/src/lua/wrappers/mod.rs | 5 +- 15 files changed, 570 insertions(+), 57 deletions(-) create mode 100644 lyra-scripting/src/lua/wrappers/input_actions.rs diff --git a/Cargo.lock b/Cargo.lock index 4f54a87..23f32fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,6 +45,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + [[package]] name = "allocator-api2" version = "0.2.16" @@ -1558,11 +1567,13 @@ dependencies = [ "anyhow", "elua", "itertools 0.12.0", + "lazy_static", "lyra-ecs", "lyra-game", "lyra-reflect", "lyra-resource", "lyra-scripting-derive", + "regex", "thiserror", "tracing", "tracing-subscriber", @@ -2204,6 +2215,35 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + [[package]] name = "renderdoc-sys" version = "0.7.1" diff --git a/examples/testbed/scripts/test.lua b/examples/testbed/scripts/test.lua index 29f39ff..d7b91f7 100644 --- a/examples/testbed/scripts/test.lua +++ b/examples/testbed/scripts/test.lua @@ -6,6 +6,48 @@ function on_init() local e = world:spawn(pos, cube) print("spawned entity " .. tostring(e)) + + local action_handler_tbl = { + layouts = { 0 }, + actions = { + MoveForwardBackward = "Axis", + MoveLeftRight = "Axis", + MoveUpDown = "Axis", + LookLeftRight = "Axis", + LookUpDown = "Axis", + LookRoll = "Axis", + }, + mappings = { + { + layout = 0, + binds = { + MoveForwardBackward = { + "key:w=1.0", "key:s=-1.0" + }, + MoveLeftRight = { + "key:a=-1.0", "key:d=1.0" + }, + MoveUpDown = { + "key:c=1.0", "key:z=-1.0" + }, + LookLeftRight = { + "key:left=-1.0", "key:right=1.0", + "mouse:axis:x" + }, + LookUpDown = { + "key:up=-1.0", "key:down=1.0", + "mouse:axis:y", + }, + LookRoll = { + "key:e=-1.0", "key:q=1.0", + } + } + } + } + } + + local handler = ActionHandler.new(action_handler_tbl) + world:add_resource(handler) end --[[ function on_first() @@ -25,6 +67,9 @@ function on_update() return t end, Transform) + + --local input = world:resource(Input) + --input. end --[[ function on_post_update() diff --git a/examples/testbed/src/main.rs b/examples/testbed/src/main.rs index d1b9a99..d9d840a 100644 --- a/examples/testbed/src/main.rs +++ b/examples/testbed/src/main.rs @@ -240,7 +240,7 @@ async fn main() { }; let action_handler_plugin = |game: &mut Game| { - let action_handler = ActionHandler::new() + /* let action_handler = ActionHandler::builder() .add_layout(LayoutId::from(0)) .add_action(CommonActionLabel::MoveForwardBackward, Action::new(ActionKind::Axis)) @@ -250,7 +250,7 @@ async fn main() { .add_action(CommonActionLabel::LookUpDown, Action::new(ActionKind::Axis)) .add_action(CommonActionLabel::LookRoll, Action::new(ActionKind::Axis)) - .add_mapping(ActionMapping::new(LayoutId::from(0), ActionMappingId::from(0)) + .add_mapping(ActionMapping::builder(LayoutId::from(0), ActionMappingId::from(0)) .bind(CommonActionLabel::MoveForwardBackward, &[ ActionSource::Keyboard(KeyCode::W).into_binding_modifier(1.0), ActionSource::Keyboard(KeyCode::S).into_binding_modifier(-1.0) @@ -282,22 +282,9 @@ async fn main() { .finish() ); - /* #[allow(unused_variables)] - let test_system = |world: &mut World| -> anyhow::Result<()> { - let handler = world.get_resource::(); - - if let Some(alpha) = handler.get_axis_modifier("look_rotate") { - debug!("'look_rotate': {alpha}"); - } - - Ok(()) - }; */ - let world = game.world_mut(); world.add_resource(action_handler); - world.spawn((Vec3::new(0.5, 0.1, 3.0),)); - game.with_plugin(InputActionPlugin); - //game.with_system("input_test", test_system, &[]); + game.with_plugin(InputActionPlugin); */ }; let script_test_plugin = |game: &mut Game| { @@ -322,6 +309,6 @@ async fn main() { .with_plugin(script_test_plugin) //.with_plugin(fps_plugin) .with_plugin(jiggle_plugin) - .with_plugin(FreeFlyCameraPlugin) + //.with_plugin(FreeFlyCameraPlugin) .run().await; } diff --git a/lyra-ecs/src/world.rs b/lyra-ecs/src/world.rs index 1c89301..1e06f71 100644 --- a/lyra-ecs/src/world.rs +++ b/lyra-ecs/src/world.rs @@ -255,7 +255,8 @@ impl World { /// Will panic if the resource is not in the world. See [`try_get_resource`] for /// a function that returns an option. pub fn get_resource(&self) -> Ref { - self.resources.get(&TypeId::of::()).unwrap() + self.resources.get(&TypeId::of::()) + .expect(&format!("World is missing resource of type '{}'", std::any::type_name::())) .get() } @@ -277,7 +278,8 @@ impl World { /// Will panic if the resource is not in the world. See [`try_get_resource_mut`] for /// a function that returns an option. pub fn get_resource_mut(&self) -> RefMut { - self.resources.get(&TypeId::of::()).unwrap() + self.resources.get(&TypeId::of::()) + .expect(&format!("World is missing resource of type '{}'", std::any::type_name::())) .get_mut() } diff --git a/lyra-game/src/input/action.rs b/lyra-game/src/input/action.rs index db60930..aa756bf 100644 --- a/lyra-game/src/input/action.rs +++ b/lyra-game/src/input/action.rs @@ -2,6 +2,7 @@ use std::{collections::HashMap, ops::Deref, hash::{Hash, DefaultHasher, Hasher}, use glam::Vec2; use lyra_ecs::world::World; +use lyra_reflect::Reflect; use crate::{plugin::Plugin, game::GameStages, EventQueue}; @@ -213,7 +214,7 @@ impl Action { } #[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct LayoutId(u32); +pub struct LayoutId(pub u32); impl From for LayoutId { fn from(value: u32) -> Self { @@ -240,7 +241,7 @@ impl Layout { } #[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct ActionMappingId(u32); +pub struct ActionMappingId(pub u32); impl From for ActionMappingId { fn from(value: u32) -> Self { @@ -264,6 +265,10 @@ impl ActionMapping { } } + pub fn builder(layout: LayoutId, id: ActionMappingId) -> ActionMappingBuilder { + ActionMappingBuilder::new(ActionMapping::new(layout, id)) + } + /// Creates a binding for the action. /// /// If the action is not in this layout, this will panic! @@ -271,7 +276,7 @@ impl ActionMapping { /// Parameters: /// * `action` - The label corresponding to the action in this Layout. /// * `bind` - The Binding to add to the Action. - pub fn bind(mut self, action: L, bindings: &[Binding]) -> Self + pub fn bind(&mut self, action: L, bindings: &[Binding]) -> &mut Self where L: ActionLabel { @@ -283,32 +288,48 @@ impl ActionMapping { 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 } } -#[derive(Clone, Default)] +pub struct ActionMappingBuilder { + mapping: ActionMapping, +} + +impl ActionMappingBuilder { + fn new(mapping: ActionMapping) -> Self { + Self { + mapping, + } + } + + pub fn bind(mut self, action: L, bindings: &[Binding]) -> Self + where + L: ActionLabel + { + let mut bindings = bindings.to_vec(); + + let action_binds = self.mapping.action_binds.entry(action.label_hash()).or_default(); + action_binds.append(&mut bindings); + + self + } + + pub fn finish(self) -> ActionMapping { + self.mapping + } +} + +#[derive(Clone, Default, Reflect)] pub struct ActionHandler { + #[reflect(skip)] // TODO: dont just skip all these pub actions: HashMap, + #[reflect(skip)] pub layouts: HashMap, + #[reflect(skip)] pub current_layout: LayoutId, + #[reflect(skip)] pub current_mapping: ActionMappingId, } @@ -317,26 +338,31 @@ impl ActionHandler { Self::default() } - pub fn add_layout(mut self, id: LayoutId) -> Self { - self.layouts.insert(id, Layout::new()); - - self + pub fn builder() -> ActionHandlerBuilder { + ActionHandlerBuilder::default() } - pub fn add_action(mut self, label: L, action: Action) -> Self + pub fn add_layout(&mut self, id: LayoutId) { + self.layouts.insert(id, Layout::new()); + } + + pub fn action(&self, label: L) -> Option<&Action> + where + L: ActionLabel + { + self.actions.get(&label.label_hash()) + } + + pub fn add_action(&mut self, label: L, action: Action) where L: ActionLabel { self.actions.insert(label.label_hash(), action); - - self } - pub fn add_mapping(mut self, mapping: ActionMapping) -> Self { + pub fn add_mapping(&mut self, mapping: ActionMapping) { 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). @@ -443,6 +469,43 @@ impl ActionHandler { } } +#[derive(Default)] +pub struct ActionHandlerBuilder { + handler: ActionHandler, +} + +impl ActionHandlerBuilder { + pub fn new() -> Self { + Self::default() + } + + pub fn add_layout(mut self, id: LayoutId) -> Self { + self.handler.layouts.insert(id, Layout::new()); + + self + } + + pub fn add_action(mut self, label: L, action: Action) -> Self + where + L: ActionLabel + { + self.handler.actions.insert(label.label_hash(), action); + + self + } + + pub fn add_mapping(mut self, mapping: ActionMapping) -> Self { + let layout = self.handler.layouts.get_mut(&mapping.layout).unwrap(); + layout.add_mapping(mapping); + + self + } + + pub fn finish(self) -> ActionHandler { + self.handler + } +} + fn actions_system(world: &mut World) -> anyhow::Result<()> { let keys = world.try_get_resource::>() .map(|r| r.deref().clone()); diff --git a/lyra-game/src/input/mod.rs b/lyra-game/src/input/mod.rs index f03bd5f..bfa8175 100644 --- a/lyra-game/src/input/mod.rs +++ b/lyra-game/src/input/mod.rs @@ -11,4 +11,182 @@ pub mod buttons; pub use buttons::*; pub mod action; -pub use action::*; \ No newline at end of file +pub use action::*; + +pub type KeyCode = winit::event::VirtualKeyCode; + +/// Parses a [`KeyCode`] from a [`&str`]. +/// +/// There are some changes to a few keycodes. All the number keys `Key1`, `Key2`, etc., have +/// the `Key` prefix removed; so they are expected to be `1`, `2`, etc. +pub fn keycode_from_str(s: &str) -> Option { + let s = s.to_lowercase(); + let s = s.as_str(); + + match s { + "1" => Some(KeyCode::Key1), + "2" => Some(KeyCode::Key2), + "3" => Some(KeyCode::Key3), + "4" => Some(KeyCode::Key4), + "5" => Some(KeyCode::Key5), + "6" => Some(KeyCode::Key6), + "7" => Some(KeyCode::Key7), + "8" => Some(KeyCode::Key8), + "9" => Some(KeyCode::Key9), + "0" => Some(KeyCode::Key0), + "a" => Some(KeyCode::A), + "b" => Some(KeyCode::B), + "c" => Some(KeyCode::C), + "d" => Some(KeyCode::D), + "e" => Some(KeyCode::E), + "f" => Some(KeyCode::F), + "g" => Some(KeyCode::G), + "h" => Some(KeyCode::H), + "i" => Some(KeyCode::I), + "j" => Some(KeyCode::J), + "k" => Some(KeyCode::K), + "l" => Some(KeyCode::L), + "m" => Some(KeyCode::M), + "n" => Some(KeyCode::N), + "o" => Some(KeyCode::O), + "p" => Some(KeyCode::P), + "q" => Some(KeyCode::Q), + "r" => Some(KeyCode::R), + "s" => Some(KeyCode::S), + "t" => Some(KeyCode::T), + "u" => Some(KeyCode::U), + "v" => Some(KeyCode::V), + "w" => Some(KeyCode::W), + "x" => Some(KeyCode::X), + "y" => Some(KeyCode::Y), + "z" => Some(KeyCode::Z), + "escape" => Some(KeyCode::Escape), + "f1" => Some(KeyCode::F1), + "f2" => Some(KeyCode::F2), + "f3" => Some(KeyCode::F3), + "f4" => Some(KeyCode::F4), + "f5" => Some(KeyCode::F5), + "f6" => Some(KeyCode::F6), + "f7" => Some(KeyCode::F7), + "f8" => Some(KeyCode::F8), + "f9" => Some(KeyCode::F9), + "f10" => Some(KeyCode::F10), + "f11" => Some(KeyCode::F11), + "f12" => Some(KeyCode::F12), + "f13" => Some(KeyCode::F13), + "f14" => Some(KeyCode::F14), + "f15" => Some(KeyCode::F15), + "f16" => Some(KeyCode::F16), + "f17" => Some(KeyCode::F17), + "f18" => Some(KeyCode::F18), + "f19" => Some(KeyCode::F19), + "f20" => Some(KeyCode::F20), + "f21" => Some(KeyCode::F21), + "f22" => Some(KeyCode::F22), + "f23" => Some(KeyCode::F23), + "f24" => Some(KeyCode::F24), + "snapshot" => Some(KeyCode::Snapshot), + "scroll" => Some(KeyCode::Scroll), + "pause" => Some(KeyCode::Pause), + "insert" => Some(KeyCode::Insert), + "home" => Some(KeyCode::Home), + "delete" => Some(KeyCode::Delete), + "end" => Some(KeyCode::End), + "pagedown" => Some(KeyCode::PageDown), + "pageup" => Some(KeyCode::PageUp), + "left" => Some(KeyCode::Left), + "up" => Some(KeyCode::Up), + "right" => Some(KeyCode::Right), + "down" => Some(KeyCode::Down), + "back" => Some(KeyCode::Back), + "return" => Some(KeyCode::Return), + "space" => Some(KeyCode::Space), + "compose" => Some(KeyCode::Compose), + "caret" => Some(KeyCode::Caret), + "numlock" => Some(KeyCode::Numlock), + "numpad0" => Some(KeyCode::Numpad0), + "numpad1" => Some(KeyCode::Numpad1), + "numpad2" => Some(KeyCode::Numpad2), + "numpad3" => Some(KeyCode::Numpad3), + "numpad4" => Some(KeyCode::Numpad4), + "numpad5" => Some(KeyCode::Numpad5), + "numpad6" => Some(KeyCode::Numpad6), + "numpad7" => Some(KeyCode::Numpad7), + "numpad8" => Some(KeyCode::Numpad8), + "numpad9" => Some(KeyCode::Numpad9), + "numpadadd" => Some(KeyCode::NumpadAdd), + "numpaddivide" => Some(KeyCode::NumpadDivide), + "numpaddecimal" => Some(KeyCode::NumpadDecimal), + "numpadcomma" => Some(KeyCode::NumpadComma), + "numpadenter" => Some(KeyCode::NumpadEnter), + "numpadequals" => Some(KeyCode::NumpadEquals), + "numpadmultiply" => Some(KeyCode::NumpadMultiply), + "numpadsubtract" => Some(KeyCode::NumpadSubtract), + "abntc1" => Some(KeyCode::AbntC1), + "abntc2" => Some(KeyCode::AbntC2), + "apostrophe" => Some(KeyCode::Apostrophe), + "apps" => Some(KeyCode::Apps), + "asterisk" => Some(KeyCode::Asterisk), + "at" => Some(KeyCode::At), + "ax" => Some(KeyCode::Ax), + "backslash" => Some(KeyCode::Backslash), + "calculator" => Some(KeyCode::Calculator), + "capital" => Some(KeyCode::Capital), + "colon" => Some(KeyCode::Colon), + "comma" => Some(KeyCode::Comma), + "convert" => Some(KeyCode::Convert), + "equals" => Some(KeyCode::Equals), + "grave" => Some(KeyCode::Grave), + "kana" => Some(KeyCode::Kana), + "kanji" => Some(KeyCode::Kanji), + "lalt" => Some(KeyCode::LAlt), + "lbracket" => Some(KeyCode::LBracket), + "lcontrol" => Some(KeyCode::LControl), + "lshift" => Some(KeyCode::LShift), + "lwin" => Some(KeyCode::LWin), + "mail" => Some(KeyCode::Mail), + "mediaselect" => Some(KeyCode::MediaSelect), + "mediastop" => Some(KeyCode::MediaStop), + "minus" => Some(KeyCode::Minus), + "mute" => Some(KeyCode::Mute), + "mycomputer" => Some(KeyCode::MyComputer), + "navigateforward" => Some(KeyCode::NavigateForward), + "navigatebackward" => Some(KeyCode::NavigateBackward), + "nexttrack" => Some(KeyCode::NextTrack), + "noconvert" => Some(KeyCode::NoConvert), + "oem102" => Some(KeyCode::OEM102), + "period" => Some(KeyCode::Period), + "playpause" => Some(KeyCode::PlayPause), + "plus" => Some(KeyCode::Plus), + "power" => Some(KeyCode::Power), + "prevtrack" => Some(KeyCode::PrevTrack), + "ralt" => Some(KeyCode::RAlt), + "rbracket" => Some(KeyCode::RBracket), + "rcontrol" => Some(KeyCode::RControl), + "rshift" => Some(KeyCode::RShift), + "rwin" => Some(KeyCode::RWin), + "semicolon" => Some(KeyCode::Semicolon), + "slash" => Some(KeyCode::Slash), + "sleep" => Some(KeyCode::Sleep), + "stop" => Some(KeyCode::Stop), + "sysrq" => Some(KeyCode::Sysrq), + "tab" => Some(KeyCode::Tab), + "underline" => Some(KeyCode::Underline), + "unlabeled" => Some(KeyCode::Unlabeled), + "volumedown" => Some(KeyCode::VolumeDown), + "volumeup" => Some(KeyCode::VolumeUp), + "wake" => Some(KeyCode::Wake), + "webback" => Some(KeyCode::WebBack), + "webfavorites" => Some(KeyCode::WebFavorites), + "webforward" => Some(KeyCode::WebForward), + "webhome" => Some(KeyCode::WebHome), + "webrefresh" => Some(KeyCode::WebRefresh), + "websearch" => Some(KeyCode::WebSearch), + "webstop" => Some(KeyCode::WebStop), + "yen" => Some(KeyCode::Yen), + "copy" => Some(KeyCode::Copy), + "paste" => Some(KeyCode::Paste), + "cut" => Some(KeyCode::Cut), + _ => None + } +} \ No newline at end of file diff --git a/lyra-game/src/input/system.rs b/lyra-game/src/input/system.rs index 419d079..ec6b20f 100755 --- a/lyra-game/src/input/system.rs +++ b/lyra-game/src/input/system.rs @@ -8,8 +8,6 @@ use crate::{EventQueue, plugin::Plugin, game::GameStages}; use super::{events::*, InputButtons, InputEvent}; -pub type KeyCode = winit::event::VirtualKeyCode; - #[derive(Default)] pub struct InputSystem; diff --git a/lyra-reflect/src/resource.rs b/lyra-reflect/src/resource.rs index aa95293..ab7445c 100644 --- a/lyra-reflect/src/resource.rs +++ b/lyra-reflect/src/resource.rs @@ -1,4 +1,4 @@ -use std::{any::TypeId, cell::{Ref, RefMut}, ptr::NonNull}; +use std::{any::{Any, TypeId}, cell::{Ref, RefMut}, ptr::NonNull}; use lyra_ecs::{World, ResourceObject}; @@ -11,6 +11,7 @@ pub struct ReflectedResource { fn_reflect: for<'a> fn (world: &'a World) -> Option>, fn_reflect_mut: for<'a> fn (world: &'a mut World) -> Option>, fn_reflect_ptr: fn (world: &mut World) -> Option>, + fn_refl_insert: fn (world: &mut World, this: Box), } impl ReflectedResource { @@ -27,6 +28,11 @@ impl ReflectedResource { pub fn reflect_ptr(&self, world: &mut World) -> Option> { (self.fn_reflect_ptr)(world) } + + /// Insert the resource into the world. + pub fn insert(&self, world: &mut World, this: Box) { + (self.fn_refl_insert)(world, this) + } } impl FromType for ReflectedResource { @@ -45,6 +51,13 @@ impl FromType for ReflectedResource { world.try_get_resource_ptr::() .map(|ptr| ptr.cast::()) }, + fn_refl_insert: |world: &mut World, this: Box| { + let res = this as Box; + let res = res.downcast::() + .expect("Provided a non-matching type to ReflectedResource insert method!"); + let res = *res; + world.add_resource(res); + } } } } \ No newline at end of file diff --git a/lyra-scripting/Cargo.toml b/lyra-scripting/Cargo.toml index 2782c56..3582afd 100644 --- a/lyra-scripting/Cargo.toml +++ b/lyra-scripting/Cargo.toml @@ -23,6 +23,8 @@ tracing = "0.1.37" #mlua = { version = "0.9.2", features = ["lua54"], optional = true } # luajit maybe? elua = { path = "./elua", optional = true } itertools = "0.12.0" +regex = "1.10.3" +lazy_static = "1.4.0" [dev-dependencies] diff --git a/lyra-scripting/elua b/lyra-scripting/elua index 70e2985..beea6c3 160000 --- a/lyra-scripting/elua +++ b/lyra-scripting/elua @@ -1 +1 @@ -Subproject commit 70e2985cc44fdb30cdf2157c50d2f0e3385e08fa +Subproject commit beea6c33fcb2f5bd16f40e1919b61a89b4aaecb6 diff --git a/lyra-scripting/src/lib.rs b/lyra-scripting/src/lib.rs index 4ba4a98..6d6a77a 100644 --- a/lyra-scripting/src/lib.rs +++ b/lyra-scripting/src/lib.rs @@ -61,6 +61,14 @@ impl ReflectBranch { } } + /// Gets self as a [`ReflectedResource`], returning `None` if self is not an instance of it. + pub fn as_resource(&self) -> Option<&ReflectedResource> { + match self { + ReflectBranch::Resource(v) => Some(v), + _ => None + } + } + /// Returns a boolean indicating if `self` is a reflection of a Resource. pub fn is_resource(&self) -> bool { matches!(self, ReflectBranch::Resource(_)) diff --git a/lyra-scripting/src/lua/providers/ecs.rs b/lyra-scripting/src/lua/providers/ecs.rs index dd72a0e..edcb4ae 100644 --- a/lyra-scripting/src/lua/providers/ecs.rs +++ b/lyra-scripting/src/lua/providers/ecs.rs @@ -1,7 +1,7 @@ use lyra_ecs::ResourceObject; use lyra_reflect::Reflect; -use crate::{lua::{wrappers::{LuaDeltaTime, LuaModelComponent}, LuaContext, RegisterLuaType, FN_NAME_INTERNAL_REFLECT_TYPE}, ScriptApiProvider, ScriptBorrow, ScriptData, ScriptDynamicBundle, ScriptWorldPtr}; +use crate::{lua::{wrappers::{LuaActionHandler, LuaDeltaTime, LuaModelComponent}, LuaContext, RegisterLuaType, FN_NAME_INTERNAL_REFLECT_TYPE}, ScriptApiProvider, ScriptBorrow, ScriptData, ScriptDynamicBundle, ScriptWorldPtr}; #[derive(Default)] pub struct LyraEcsApiProvider; @@ -21,6 +21,7 @@ impl ScriptApiProvider for LyraEcsApiProvider { globals.set("World", ctx.create_proxy::()?)?; globals.set("DynamicBundle", ctx.create_proxy::()?)?; globals.set("ModelComponent", ctx.create_proxy::()?)?; + globals.set("ActionHandler", ctx.create_proxy::()?)?; let dt_table = create_reflect_table::(&ctx)?; globals.set("DeltaTime", dt_table)?; diff --git a/lyra-scripting/src/lua/world.rs b/lyra-scripting/src/lua/world.rs index 3cb081f..1e1a03a 100644 --- a/lyra-scripting/src/lua/world.rs +++ b/lyra-scripting/src/lua/world.rs @@ -1,7 +1,7 @@ use std::{ptr::NonNull, sync::Arc}; use crate::{ScriptBorrow, ScriptEntity, ScriptWorldPtr}; -use elua::AsLua; +use elua::{AnyUserdata, AsLua, Table}; use lyra_ecs::{query::dynamic::QueryDynamicType, CommandQueue, Commands, DynamicBundle, World}; use lyra_reflect::{ReflectWorldExt, RegisteredType, TypeRegistry}; use lyra_resource::ResourceManager; @@ -279,6 +279,32 @@ impl elua::Userdata for ScriptWorldPtr { Ok(elua::Value::Nil) } }) + .method_mut("add_resource", |_, this, res: elua::Value| { + let reflect = match res { + elua::Value::Userdata(ud) => ud + .execute_method::<_, ScriptBorrow>(FN_NAME_INTERNAL_REFLECT, ()) + .expect("Type does not implement 'reflect_type' properly"), + elua::Value::Table(t) => { + let f: elua::Function = t.get(FN_NAME_INTERNAL_REFLECT)?; + f.exec::<_, ScriptBorrow>(()) + .expect("Type does not implement 'reflect_type' properly") + } + _ => { + panic!("how"); + } + }; + + let data = reflect.data + .expect("Its expected that 'FN_NAME_INTERNAL_REFLECT' returns data in World:add_resource"); + + let res = reflect.reflect_branch.as_resource() + .ok_or(elua::Error::runtime("Provided type is not a resource!"))?; + + let world = this.as_mut(); + res.insert(world, data); + + Ok(()) + }) .method_mut("request_res", |_, this, path: String| { let world = this.as_mut(); let mut man = world.get_resource_mut::(); diff --git a/lyra-scripting/src/lua/wrappers/input_actions.rs b/lyra-scripting/src/lua/wrappers/input_actions.rs new file mode 100644 index 0000000..9325280 --- /dev/null +++ b/lyra-scripting/src/lua/wrappers/input_actions.rs @@ -0,0 +1,147 @@ +use lyra_game::input::{keycode_from_str, Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource, LayoutId, MouseAxis, MouseInput}; + +use lazy_static::lazy_static; +use regex::Regex; + +use crate::{lua::{FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE}, ScriptBorrow}; + +lazy_static! { + static ref BINDING_INPUT_RE: Regex = Regex::new(r"(\w+):(\w+)(?:=(-?\d+(?:.\d+)?))?").unwrap(); +} + +#[derive(Clone)] +pub struct LuaActionHandler { + handler: ActionHandler +} + +impl elua::Userdata for LuaActionHandler { + fn name() -> String { + "ActionHandler".to_string() + } + + fn build<'a>(_: &elua::State, builder: &mut elua::UserdataBuilder<'a, Self>) -> elua::Result<()> { + builder.function("new", |_, table: elua::Table| { + let mut handler = ActionHandler::new(); + + // create the layouts + let layouts = table.get::<_, elua::Table>("layouts") + .map_err(|_| elua::Error::runtime("missing 'layouts' in ActionHandler table"))?; + for layout_id in layouts.sequence_iter::() { + let layout_id = layout_id?; + + handler.add_layout(LayoutId(layout_id)); + } + + let actions = table.get::<_, elua::Table>("actions") + .map_err(|_| elua::Error::runtime("missing 'actions' in ActionHandler table"))?; + for pair in actions.pairs::() { + let (action_lbl, action_type) = pair?; + let action_type = action_type.to_lowercase(); + + let action_type = match action_type.as_str() { + "axis" => ActionKind::Axis, + "button" => ActionKind::Button, + _ => todo!("Handle invalid axis type"), + }; + + handler.add_action(action_lbl, Action::new(action_type)); + } + + let mappings= table.get::<_, elua::Table>("mappings") + .map_err(|_| elua::Error::runtime("missing 'mappings' in ActionHandler table"))?; + for (map_id, tbl) in mappings.sequence_iter::().enumerate() { + let tbl = tbl?; + + let layout_id = tbl.get::<_, u32>("layout")?; + let mut mapping = ActionMapping::new(LayoutId(layout_id), ActionMappingId(map_id as u32)); + + let binds_tbl = tbl.get::<_, elua::Table>("binds") + .map_err(|_| elua::Error::runtime("missing 'binds' in ActionHandler 'mappings' table"))?; + for pair in binds_tbl.pairs::() { + let (action_lbl, input_binds) = pair?; + + for input in input_binds.sequence_iter::() { + let input = input?.to_lowercase(); + + let action = handler.action(&action_lbl) + .ok_or(elua::Error::Runtime(format!("Unknown action specified in mapping binds: {}", action_lbl)))?; + + let mut binds = Vec::new(); + + let input_split: Vec<&str> = input.split(":").collect(); + let input_name = input_split[0]; + let button = input_split[1]; + + if action.kind == ActionKind::Axis { + if button == "axis" { + let axis_name = input_split[2]; + + let src = process_axis_string(input_name, axis_name) + .ok_or(elua::Error::Runtime(format!("invalid bind '{input_name}', unable to find device or axis for device")))?; + binds.push(src.into_binding()); + } else { + // splits 'down=1' into 'down' and '1' + let (button, val_str) = button.split_once("=") + .ok_or(elua::Error::Runtime(format!("invalid bind string for Axis Action: '{input}' (expected '=' with float)")))?; + + let val = val_str.parse::() + .map_err(|e| elua::Error::Runtime(format!("invalid bind string for Axis Action: '{input}' ({e})")))?; + + let src = process_keyboard_string(button) + .ok_or(elua::Error::Runtime(format!("invalid key in bind: '{button}'")))?; + binds.push(src.into_binding_modifier(val)); + } + } else { + todo!("Process bindings for Button Actions"); + } + + mapping.bind(action_lbl.clone(), &binds); + } + } + + handler.add_mapping(mapping); + } + + Ok(LuaActionHandler { + handler, + }) + }) + .method(FN_NAME_INTERNAL_REFLECT, |_, this, ()| { + Ok(ScriptBorrow::from_resource::(Some(this.handler.clone()))) + }) + .method(FN_NAME_INTERNAL_REFLECT_TYPE, |_, _, ()| { + Ok(ScriptBorrow::from_resource::(None)) + }); + + Ok(()) + } +} + +impl<'a> elua::FromLua<'a> for LuaActionHandler { + fn from_lua(_: &'a elua::State, val: elua::Value<'a>) -> elua::Result { + let tyname = val.type_name(); + let ud = val.as_userdata() + .ok_or(elua::Error::type_mismatch("ActionHandler", &tyname))?; + let handle = ud.as_ref::()?; + + Ok(handle.clone()) + } +} + +fn process_keyboard_string(key_name: &str) -> Option { + let key = keycode_from_str(key_name)?; + + Some(ActionSource::Keyboard(key)) +} + +fn process_axis_string(device: &str, axis_name: &str) -> Option { + match device { + "mouse" => match axis_name { + "y" => Some(ActionSource::Mouse(MouseInput::Axis(MouseAxis::Y))), + "x" => Some(ActionSource::Mouse(MouseInput::Axis(MouseAxis::X))), + "scroll" | "scrollwheel" => Some(ActionSource::Mouse(MouseInput::Axis(MouseAxis::ScrollWheel))), + _ => None, + }, + _ => None + } +} diff --git a/lyra-scripting/src/lua/wrappers/mod.rs b/lyra-scripting/src/lua/wrappers/mod.rs index 244419d..81b3255 100644 --- a/lyra-scripting/src/lua/wrappers/mod.rs +++ b/lyra-scripting/src/lua/wrappers/mod.rs @@ -8,4 +8,7 @@ pub mod res_handle; pub use res_handle::*; pub mod model_comp; -pub use model_comp::*; \ No newline at end of file +pub use model_comp::*; + +pub mod input_actions; +pub use input_actions::*; \ No newline at end of file