From de14b6211bea7569dd9e7c4e93e78825f7122766 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Thu, 3 Oct 2024 19:07:11 -0400 Subject: [PATCH 01/15] lua: create type defs for Window and start on Vec2 --- examples/lua-scripting/scripts/test.lua | 12 +- .../scripts/lua/types/ecs/window.lua | 126 ++++++++++++++++++ lyra-scripting/scripts/lua/types/init.lua | 3 + .../scripts/lua/types/math/vec2.lua | 14 ++ lyra-scripting/scripts/lua/window.lua | 4 + lyra-scripting/src/lua/wrappers/window.rs | 4 - 6 files changed, 155 insertions(+), 8 deletions(-) create mode 100644 lyra-scripting/scripts/lua/types/ecs/window.lua create mode 100644 lyra-scripting/scripts/lua/types/init.lua create mode 100644 lyra-scripting/scripts/lua/types/math/vec2.lua diff --git a/examples/lua-scripting/scripts/test.lua b/examples/lua-scripting/scripts/test.lua index 7d7c881..4656343 100644 --- a/examples/lua-scripting/scripts/test.lua +++ b/examples/lua-scripting/scripts/test.lua @@ -1,4 +1,6 @@ -HAS_SETUP_WINDOW = false +--local win = require "scripts.window" + +local is_window_setup = false ---Return the userdata's name from its metatable. --- @@ -31,14 +33,16 @@ function on_init() end function on_first() - if not HAS_SETUP_WINDOW then - world:view(function (w) + if not is_window_setup then + world:view( + ---@param w Window + function (w) if w.cursor_grab == CursorGrabMode.NONE then w.cursor_grab = CursorGrabMode.LOCKED w.cursor_visible = false return w else - HAS_SETUP_WINDOW = true + is_window_setup = true print("Window setup") end end, Window) diff --git a/lyra-scripting/scripts/lua/types/ecs/window.lua b/lyra-scripting/scripts/lua/types/ecs/window.lua new file mode 100644 index 0000000..49a1cd9 --- /dev/null +++ b/lyra-scripting/scripts/lua/types/ecs/window.lua @@ -0,0 +1,126 @@ +---@class Window +Window = { + ---Gets or sets the window's focus. + ---@type boolean + focused = nil, + ---Gets or sets the window mode. + ---@type WindowMode + window_mode = nil, + ---Gets or sets the position of the top-left corner of the window. + --- + ---The top-left hand corner of the desktop is not necessarily the same + ---as the screen. If the user uses a desktop with multiple monitors, the top-left + ---hand corner of the desktop is the top-left hand corner of the monitor at the + ---top-left of the desktop. + --- + ---If this is `nil`, the position will be chosen by the windowing manager at creation, + ---then set when the window is created. + --- + ---@type Vec2? + position = nil, + ---@type Vec2 + physical_size = nil, + ---@type Vec2 + size = nil, + ---Gets/sets if the window has decorations. + ---@type boolean + decorated = nil, + ---Gets/sets the window's current maximized state. + ---@type boolean + maximized = nil, + ---Gets/sets the window's current minimized state. + --- + ---Is `nil` if the minimized state could not be determined. + --- + ---@type boolean? + minimized = nil, + ---Gets/sets the window's current resizable state + ---@type boolean + resizable = nil, + ---Gets/sets the window's current visibility state. + --- + ---Is `nil` when it could not be determined. + --- + ---@type boolean? + visible = nil, + + --TODO: resize_increments + + ---Gets the scale factor. + --- + ---You cannot set this field. + --- + ---@type number + scale_factor = nil, + ---Gets/sets the window's blur state. + ---@type boolean + blur = nil, + + --TODO: cursor appearance + + ---Gets/sets the window's cursor grab mode. + ---@type CursorGrabMode + cursor_grab = nil, + ---Gets/sets whether the window catches cursor events. + ---@type boolean + cursor_hittest = nil, + ---Gets/sets the cursor's visibility. + ---@type boolean + cursor_visible = nil, + ---Sets whether the window should get IME events. + --- + ---When IME is allowed, the window will receive Ime events, and during the preedit phase + ---the window will NOT get KeyboardInput events. The window should allow IME while + ---it is expecting text input. + --- + ---When IME is not allowed, the window won’t receive window ime events, and will receive + ---KeyboardInput events for every keypress instead. Not allowing IME is useful for games + ---for example. IME is not allowed by default. + --- + ---@type boolean + ime_allowed = nil, + ---Gets/sets the minimum size of the window. + ---@type Vec2? + min_size = nil, + ---Gets/sets the maximum size of the window. + ---@type Vec2? + max_size = nil, + ---Gets/sets the current window theme. + --- + ---Specify `nil` to reset the theme to the system default. May also be `nil` on + ---unsupported platforms. + --- + ---@type WindowTheme? + theme = nil, + ---Gets/sets the title of the window. + ---@type string + title = nil, + ---Gets/sets the window's transparency state. + ---@type boolean + transparent = nil, + + --TODO: window_icon + + ---Change the window level. + ---@type WindowLevel + window_level = nil, + ---Gets the window's occluded state (completely hidden from view). + ---@type boolean + occluded = nil, + ---Gets/sets the cursor position in the window in logical coordinates. + --- + ---The value is `nil` when the cursor is not in the window. + --- + ---@type Vec2? + cursor_position = nil, + ---Gets/sets the cursor position in the window in physical coordinates. + --- + ---The value is `nil` when the cursor is not in the window. + --- + ---@type Vec2? + physical_cursor_position = nil, + ---Checks if the mouse is inside the window + ---@param self Window + ---@return boolean + is_mouse_inside = function (self) return false end, +} diff --git a/lyra-scripting/scripts/lua/types/init.lua b/lyra-scripting/scripts/lua/types/init.lua new file mode 100644 index 0000000..e607503 --- /dev/null +++ b/lyra-scripting/scripts/lua/types/init.lua @@ -0,0 +1,3 @@ +require "math.vec2" + +require "ecs.window" \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/types/math/vec2.lua b/lyra-scripting/scripts/lua/types/math/vec2.lua new file mode 100644 index 0000000..a564513 --- /dev/null +++ b/lyra-scripting/scripts/lua/types/math/vec2.lua @@ -0,0 +1,14 @@ +---@class Vec2 +Vec2 = { + ---The x coordinate + ---@type number + x = nil, + ---The y coordinate + ---@type number + y = nil, + + ---Create a new `Vec2` + ---@param x number + ---@param y number + new = function (x, y) end +} \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/window.lua b/lyra-scripting/scripts/lua/window.lua index f1781ae..ca5092a 100644 --- a/lyra-scripting/scripts/lua/window.lua +++ b/lyra-scripting/scripts/lua/window.lua @@ -1,3 +1,4 @@ +---@enum WindowMode WindowMode = { WNDOWED = "windowed", BORDERLESS_FULLSCREEN = "borderless_fullscreen", @@ -5,17 +6,20 @@ WindowMode = { FULLSCREEN = "fullscreen", } +---@enum CursorGrabMode CursorGrabMode = { NONE = "none", CONFINED = "confined", LOCKED = "locked", } +---@enum WindowTheme WindowTheme = { LIGHT = "light", DARK = "dark", } +---@enum WindowLevel WindowLevel = { ALWAYS_ON_BOTTOM = "always_on_bottom", NORMAL = "normal", diff --git a/lyra-scripting/src/lua/wrappers/window.rs b/lyra-scripting/src/lua/wrappers/window.rs index 34f91ab..9a6ddee 100644 --- a/lyra-scripting/src/lua/wrappers/window.rs +++ b/lyra-scripting/src/lua/wrappers/window.rs @@ -135,10 +135,6 @@ wrap_lua_struct!( fields.add_field_method_get("scale_factor", |lua, this| { this.scale_factor.into_lua(lua) }); - fields.add_field_method_set("scale_factor", |_, this, val: f64| { - this.scale_factor = val; - Ok(()) - }); fields.add_field_method_get("blur", |lua, this| { this.blur.into_lua(lua) -- 2.40.1 From 06a4301c234f60e20a47d98b774b5b1c6562df7e Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Fri, 4 Oct 2024 15:07:42 -0400 Subject: [PATCH 02/15] lua: create type defs for Vec2, Vec3, Vec4, Quat, Transform, and DeltaTime --- examples/lua-scripting/scripts/test.lua | 36 ++-- lyra-math/src/transform.rs | 27 ++- lyra-scripting/scripts/lua/math/quat.lua | 182 ----------------- lyra-scripting/scripts/lua/math/transform.lua | 95 --------- lyra-scripting/scripts/lua/math/vec3.lua | 187 ----------------- .../scripts/lua/types/ecs/delta_time.lua | 15 ++ lyra-scripting/scripts/lua/types/init.lua | 7 +- .../scripts/lua/types/math/quat.lua | 188 ++++++++++++++++++ .../scripts/lua/types/math/transform.lua | 134 +++++++++++++ .../scripts/lua/types/math/vec2.lua | 121 ++++++++++- .../scripts/lua/types/math/vec3.lua | 139 +++++++++++++ .../scripts/lua/types/math/vec4.lua | 132 ++++++++++++ lyra-scripting/src/lua/wrappers/math.rs | 117 +++++++++-- 13 files changed, 866 insertions(+), 514 deletions(-) delete mode 100644 lyra-scripting/scripts/lua/math/quat.lua delete mode 100644 lyra-scripting/scripts/lua/math/transform.lua delete mode 100644 lyra-scripting/scripts/lua/math/vec3.lua create mode 100644 lyra-scripting/scripts/lua/types/ecs/delta_time.lua create mode 100644 lyra-scripting/scripts/lua/types/math/quat.lua create mode 100644 lyra-scripting/scripts/lua/types/math/transform.lua create mode 100644 lyra-scripting/scripts/lua/types/math/vec3.lua create mode 100644 lyra-scripting/scripts/lua/types/math/vec4.lua diff --git a/examples/lua-scripting/scripts/test.lua b/examples/lua-scripting/scripts/test.lua index 4656343..2bb8a16 100644 --- a/examples/lua-scripting/scripts/test.lua +++ b/examples/lua-scripting/scripts/test.lua @@ -1,5 +1,3 @@ ---local win = require "scripts.window" - local is_window_setup = false ---Return the userdata's name from its metatable. @@ -35,17 +33,18 @@ end function on_first() if not is_window_setup then world:view( - ---@param w Window - function (w) - if w.cursor_grab == CursorGrabMode.NONE then - w.cursor_grab = CursorGrabMode.LOCKED - w.cursor_visible = false - return w - else - is_window_setup = true - print("Window setup") - end - end, Window) + ---@param w Window + function (w) + if w.cursor_grab == CursorGrabMode.NONE then + w.cursor_grab = CursorGrabMode.LOCKED + w.cursor_visible = false + return w + else + is_window_setup = true + print("Window setup") + end + end, Window + ) end end @@ -70,10 +69,13 @@ function on_update() ---@type number local dt = world:resource(DeltaTime) - world:view(function (t) - t:translate(0, 0.15 * dt, 0) - return t - end, Transform) + world:view( + ---@param t Transform + function (t) + t:translate(0, 0.15 * dt, 0) + return t + end, Transform + ) end --[[ function on_post_update() diff --git a/lyra-math/src/transform.rs b/lyra-math/src/transform.rs index 35ffd84..83fd2b7 100755 --- a/lyra-math/src/transform.rs +++ b/lyra-math/src/transform.rs @@ -28,10 +28,6 @@ impl Default for Transform { // TODO: https://www.brainvoyager.com/bv/doc/UsersGuide/CoordsAndTransforms/SpatialTransformationMatrices.html -#[allow(dead_code)] -const ZERO_V3: Vec3 = Vec3::new(0.0, 0.0, 0.0); -const ONE_V3: Vec3 = Vec3::new(1.0, 1.0, 1.0); - #[allow(dead_code)] impl Transform { pub fn new(translation: Vec3, rotation: Quat, scale: Vec3) -> Self { @@ -43,33 +39,42 @@ impl Transform { } pub fn from_translation(translation: Vec3) -> Self { - Self::new(translation, Quat::IDENTITY, ONE_V3) + Self::new(translation, Quat::IDENTITY, Vec3::ONE) } pub fn from_xyz(x: f32, y: f32, z: f32) -> Self { - Self::new(Vec3::new(x, y, z), Quat::IDENTITY, ONE_V3) + Self::new(Vec3::new(x, y, z), Quat::IDENTITY, Vec3::ONE) } pub fn calculate_mat4(&self) -> Mat4 { Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.translation) } - /// Get the forward vector of the Transform. + /// Returns a normalized vector pointing in the direction the Transform is facing. + /// + /// This represents the front of the object can be used for movement, camera orientation, and + /// other directional calculations. pub fn forward(&self) -> Vec3 { (self.rotation * -Vec3::Z).normalize() } - /// Get the left vector of the Transform. + /// Returns a normalized vector pointing to the left side of the Transform. + /// + /// The vector is in local space. This represents the direction that is + /// perpendicular to the object's forward direction. pub fn left(&self) -> Vec3 { (self.rotation * Vec3::X).normalize() } - /// Get the up vector of the Transform. + /// Returns a normalized vector that indicates the upward direction of the Transform. + /// + /// This vector is commonly used to define an object's orientation and is essential for maintaining + /// consistent vertical alignment in 3D environments, such as for camera positioning and object alignment. pub fn up(&self) -> Vec3 { (self.rotation * Vec3::Y).normalize() } - /// Rotate this transform using a Quaternion + /// Rotate this transform using a Quaternion. pub fn rotate(&mut self, rotation: Quat) { self.rotation = (rotation * self.rotation).normalize(); } @@ -110,7 +115,7 @@ impl Transform { let mut res = *self; res.translation = self.translation.lerp(rhs.translation, alpha); // normalize rotation here to avoid panics - res.rotation = self.rotation.lerp(rhs.rotation.normalize(), alpha); + res.rotation = self.rotation.normalize().lerp(rhs.rotation.normalize(), alpha); res.scale = self.scale.lerp(rhs.scale, alpha); res } else { diff --git a/lyra-scripting/scripts/lua/math/quat.lua b/lyra-scripting/scripts/lua/math/quat.lua deleted file mode 100644 index b268834..0000000 --- a/lyra-scripting/scripts/lua/math/quat.lua +++ /dev/null @@ -1,182 +0,0 @@ ----@class Quat ----@field x number ----@field y number ----@field z number ----@field w number -Quat = { x = 0.0, y = 0.0, z = 0.0, w = 0.0 } -Quat.__index = Quat -Quat.__name = "Quat" - ---- Constructs a new Quaternion from x, y, z, and w. ----@param x number ----@param y number ----@param z number ----@param w number ----@return Quat -function Quat:new(x, y, z, w) - local q = {} - setmetatable(q, Quat) - - q.x = x - q.y = y - q.z = z - q.w = w - - return q -end - -Quat.IDENTITY = Quat:new(0, 0, 0, 1) - -function Quat:clone() - return Quat:new(self.x, self.y, self.z, self.w) -end - ---- Creates a quaternion from the angle, in radians, around the x axis. ---- @param rad number ---- @return Quat -function Quat:from_rotation_x(rad) - local sin = math.sin(rad * 0.5) - local cos = math.cos(rad * 0.5) - return Quat:new(sin, 0, 0, cos) -end - ---- Creates a quaternion from the angle, in radians, around the y axis. ---- @param rad number ---- @return Quat -function Quat:from_rotation_y(rad) - local sin = math.sin(rad * 0.5) - local cos = math.cos(rad * 0.5) - return Quat:new(0, sin, 0, cos) -end - ---- Creates a quaternion from the angle, in radians, around the z axis. ---- @param rad number ---- @return Quat -function Quat:from_rotation_z(rad) - local sin = math.sin(rad * 0.5) - local cos = math.cos(rad * 0.5) - return Quat:new(0, 0, sin, cos) -end - ---- Computes the dot product of `self`. ----@param rhs Quat ----@return number -function Quat:dot(rhs) - return (self.x * rhs.x) + (self.y * rhs.y) + (self.z * rhs.z) + (self.w * rhs.w) -end - ---- Computes the length of `self`. ----@return number -function Quat:length() - return math.sqrt(self:dot(self)) -end - ---- Compute the length of `self` squared. ----@return number -function Quat:length_squared() - return self:length() ^ 2 -end - ---- Normalizes `self` and returns the new Quat ----@return unknown -function Quat:normalize() - local length = self:length() - return self / length -end - ---- Multiplies two Quaternions together. Keep in mind that Quaternion multiplication is NOT ---- commutative so the order in which you multiply the quaternions matters. ----@param rhs Quat ----@return Quat -function Quat:mult_quat(rhs) - local x1, y1, z1, w1 = self.x, self.y, self.z, self.w - local x2, y2, z2, w2 = rhs.x, rhs.y, rhs.z, rhs.w - - local x = w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2 - local y = w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2 - local z = w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2 - local w = w1 * w2 - x1 * x2 - y1 * y2 - z1 * x2 - - return Quat:new(x, y, z, w) -end - ---- Multiplies `self` by a Vec3, returning the rotated Vec3 ----@param vec Vec3 ----@return Vec3 -function Quat:mult_vec3(vec) - local vec_quat = Quat:new(vec.x, vec.y, vec.z, 0) - local quat = self:mult_quat(vec_quat) - return Vec3:new(quat.x, quat.y, quat.z) -end - ---- Calculates the linear iterpolation between `self` and `rhs` based on the `alpha`. ---- When `alpha` is `0`, the result will be equal to `self`. When `s` is `1`, the result ---- will be equal to `rhs` ---- @param rhs Quat ---- @param alpha number ---- @return Quat -function Quat:lerp(rhs, alpha) - -- ensure alpha is [0, 1] - local alpha = math.max(0, math.min(1, alpha)) - - local x1, y1, z1, w1 = self.x, self.y, self.z, self.w - local x2, y2, z2, w2 = rhs.x, rhs.y, rhs.z, rhs.w - - local x = (1 - alpha) * x1 + alpha * x2 - local y = (1 - alpha) * y1 + alpha * y2 - local z = (1 - alpha) * z1 + alpha * z2 - local w = (1 - alpha) * w1 + alpha * w2 - - return Quat:new(x, y, z, w):normalize() -end - -function Quat:__add(rhs) - return Quat:new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z, self.w + rhs.w) -end - -function Quat:__sub(rhs) - return Quat:new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z, self.w - rhs.w) -end - -function Quat:__mul(rhs) - if type(rhs) == "number" then - return Quat:new(self.x * rhs, self.y * rhs, self.z * rhs, self.w * rhs) - elseif type(rhs) == "table" then - local name = rhs.__name - - if name == "Vec3" then - return self:mult_vec3(rhs) - elseif name == "Quat" then - return self:mult_quat(rhs) - else - assert(false, "Unknown usertype of rhs" .. name) - end - else - assert(false, "Unknown type of rhs" .. type(rhs)) - end -end - -function Quat:__div(rhs) - if type(rhs) == "number" then - return Quat:new(self.x / rhs, self.y / rhs, self.z / rhs, self.w / rhs) - else - assert(rhs.__name == "Quat", "Attempted to divide Quat by unknown type " .. rhs.__name) - return Quat:new(self.x / rhs.x, self.y / rhs.y, self.z / rhs.z, self.w / rhs.w) - end -end - -function Quat:__eq(rhs) - return self.x == rhs.x and self.y == rhs.y and self.z == rhs.z and self.w == rhs.w -end - -function Quat:__lt(rhs) - return self.x < rhs.x and self.y < rhs.y and self.z < rhs.z and self.w < rhs.w -end - -function Quat:__le(rhs) - return self.x <= rhs.x and self.y <= rhs.y and self.z <= rhs.z and self.w <= rhs.w -end - -function Quat:__tostring() - return "Quat(" .. self.x .. ", " .. self.y .. ", " .. self.z .. ", " .. self.w .. ")" -end \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/math/transform.lua b/lyra-scripting/scripts/lua/math/transform.lua deleted file mode 100644 index 7f3eb14..0000000 --- a/lyra-scripting/scripts/lua/math/transform.lua +++ /dev/null @@ -1,95 +0,0 @@ ----@class Transform ----@field translation Vec3 ----@field rotation Quat ----@field Scale Vec3 -Transform = { translation = Vec3.ZERO, rotation = Quat.IDENTITY, scale = Vec3.ONE } -Transform.__index = Transform -Transform.__name = "Transform" - -function Transform:new(translation, rotation, scale) - local t = {} - setmetatable(t, Transform) - - t.translation = translation - t.rotation = rotation - t.scale = scale - - return t -end - -function Transform:clone() - return Transform:new(self.translation:clone(), self.rotation:clone(), self.scale:clone()) -end - ---- Creates a new Transform with the translation at the vec3 ---- @param pos Vec3 -function Transform:from_vec3(pos) - local t = Transform:clone() -- copy of default transform - t.translation = pos - return t -end - -function Transform:from_xyz(x, y, z) - Transform:from_vec3(Vec3:new(x, y, z)) -end - ---- Calculates the forward vector of the Transform. ---- @return Vec3 -function Transform:forward() - return (self.rotation * Vec3.NEG_Z):normalize() -end - ---- Calculates the left vector of the Transform. ---- @return Vec3 -function Transform:left() - return (self.rotation * Vec3.X):normalize() -end - ---- Calculates the up vector of the Transform. ---- @return Vec3 -function Transform:up() - return (self.rotation * Vec3.Y):normalize() -end - ---- Rotates `self` using a Quaternion ---- @param quat Quat -function Transform:rotate(quat) - self.rotation = (quat * self.rotation):normalize() -end - ---- Rotates `self` around the x-axis ---- @param rad number -function Transform:rotate_x(rad) - self:rotate(Quat:from_rotation_x(rad)) -end - ---- Rotates `self` around the y-axis ---- @param rad number -function Transform:rotate_y(rad) - self:rotate(Quat:from_rotation_y(rad)) -end - ---- Rotates `self` around the z-axis ---- @param rad number -function Transform:rotate_z(rad) - self:rotate(Quat:from_rotation_z(rad)) -end - ---- Calculates the linear iterpolation between `self` and `rhs` based on the `alpha`. ---- When `alpha` is `0`, the result will be equal to `self`. When `s` is `1`, the result ---- will be equal to `rhs` ---- @param rhs Transform ---- @param alpha number ---- @return Transform -function Transform:lerp(rhs, alpha) - local res = self:clone() - res.translation = self.translation:lerp(rhs.translation, alpha) - res.rotation = self.rotation:lerp(rhs.rotation, alpha) - res.scale = self.scale:lerp(rhs.scale, alpha) - return res -end - -function Transform:__tostring() - return "Transform(pos=" .. tostring(self.translation) .. ", rot=" - .. tostring(self.rotation) .. ", scale=" .. tostring(self.scale) .. ")" -end \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/math/vec3.lua b/lyra-scripting/scripts/lua/math/vec3.lua deleted file mode 100644 index 5b5ff44..0000000 --- a/lyra-scripting/scripts/lua/math/vec3.lua +++ /dev/null @@ -1,187 +0,0 @@ ----@class Vec3 ----@field x number ----@field y number ----@field z number -Vec3 = { x = 0.0, y = 0.0, z = 0.0 } -Vec3.__index = Vec3 -Vec3.__name = "Vec3" - ---- Constructs a new vector ----@param x number ----@param y number ----@param z number ----@return Vec3 -function Vec3:new(x, y, z) - local v = {} - setmetatable(v, Vec3) - - v.x = x - v.y = y - v.z = z - - return v -end - ----Creates a copy of self ----@return Vec3 -function Vec3:clone() - return Vec3:new(self.x, self.y, self.z) -end - ---- Constructs a vector with all elements as parameter `x`. ----@param x number ----@return Vec3 -function Vec3:all(x) - return Vec3:new(x, x, x) -end - ---- A unit-length vector pointing alongside the positive X axis. -Vec3.X = Vec3:new(1, 0, 0) ---- A unit-length vector pointing alongside the positive Y axis. -Vec3.Y = Vec3:new(0, 1, 0) ---- A unit-length vector pointing alongside the positive Z axis. -Vec3.Z = Vec3:new(0, 0, 1) - ---- A unit-length vector pointing alongside the negative X axis. -Vec3.NEG_X = Vec3:new(-1, 0, 0) ---- A unit-length vector pointing alongside the negative Y axis. -Vec3.NEG_Y = Vec3:new(0, -1, 0) ---- A unit-length vector pointing alongside the negative Z axis. -Vec3.NEG_Z = Vec3:new(0, 0, -1) - ---- A vector of all zeros -Vec3.ZERO = Vec3:new(0, 0, 0) ---- A vector of all ones -Vec3.ONE = Vec3:new(1, 1, 1) - ---- Computes the absolute value of `self`. -function Vec3:abs() - self.x = math.abs(self.x) - self.y = math.abs(self.y) - self.z = math.abs(self.z) -end - ---- Computes the length of `self`. ----@return number -function Vec3:length() - return math.sqrt(self:dot(self)) -end - ----Moves `self` by the provided coordinates ----@param x number ----@param y number ----@param z number -function Vec3:move_by(x, y, z) - self.x = self.x + x - self.y = self.y + y - self.z = self.z + z -end - ---- Computes the dot product of `self` and `rhs`. ----@param rhs Vec3 ----@return number -function Vec3:dot(rhs) - assert(rhs.__name == "Vec3") - - return (self.x * rhs.x) + (self.y * rhs.y) + (self.z * rhs.z) -end - ---- Returns a vector that has the minimum value of each element of `self` and `rhs` ----@param rhs Vec3 ----@return Vec3 -function Vec3:min(rhs) - local x = math.min(self.x, rhs.x) - local y = math.min(self.y, rhs.y) - local z = math.min(self.z, rhs.z) - - return Vec3:new(x, y, z) -end - ---- Modifies `self` to be normalized to a length 1. -function Vec3:normalize() - local len_recip = 1.0 / self:length() - self.x = self.x * len_recip - self.y = self.y * len_recip - self.z = self.z * len_recip -end - ---- Calculates the linear iterpolation between `self` and `rhs` based on the `alpha`. ---- When `alpha` is `0`, the result will be equal to `self`. When `s` is `1`, the result ---- will be equal to `rhs` ---- @param rhs Vec3 ---- @param alpha number ---- @return Vec3 -function Vec3:lerp(rhs, alpha) - -- ensure alpha is [0, 1] - local alpha = math.max(0, math.min(1, alpha)) - - local res = self:clone() - res = res + ((rhs - res) * alpha) - return res -end - -function Vec3:__add(rhs) - if type(rhs) == "Vec3" then - return Vec3:new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z) - else - return Vec3:new(self.x + rhs, self.y + rhs, self.z + rhs) - end -end - -function Vec3:__sub(rhs) - if type(rhs) == "Vec3" then - return Vec3:new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z) - else - return Vec3:new(self.x - rhs, self.y - rhs, self.z - rhs) - end -end - -function Vec3:__mul(rhs) - if type(rhs) == "Vec3" then - return Vec3:new(self.x * rhs.x, self.y * rhs.y, self.z * rhs.z) - else - return Vec3:new(self.x * rhs, self.y * rhs, self.z * rhs) - end -end - -function Vec3:__div(rhs) - if type(rhs) == "Vec3" then - return Vec3:new(self.x / rhs.x, self.y / rhs.y, self.z / rhs.z) - else - return Vec3:new(self.x / rhs, self.y / rhs, self.z / rhs) - end -end - -function Vec3:__idiv(rhs) - if type(rhs) == "Vec3" then - return Vec3:new(self.x // rhs.x, self.y // rhs.y, self.z // rhs.z) - else - return Vec3:new(self.x // rhs, self.y // rhs, self.z // rhs) - end -end - -function Vec3:__unm() - return Vec3:new(-self.x, -self.y, -self.z) -end - -function Vec3:__pow(rhs) - if type(rhs) == "number" then - return Vec3:new(self.x ^ rhs, self.y ^ rhs, self.z ^ rhs) - end -end - -function Vec3:__eq(rhs) - return self.x == rhs.x and self.y == rhs.y and self.z == rhs.z -end - -function Vec3:__lt(rhs) - return self.x < rhs.x and self.y < rhs.y and self.z < rhs.z -end - -function Vec3:__le(rhs) - return self.x <= rhs.x and self.y <= rhs.y and self.z <= rhs.z -end - -function Vec3:__tostring() - return "Vec3(" .. self.x .. ", " .. self.y .. ", " .. self.z .. ")" -end \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/types/ecs/delta_time.lua b/lyra-scripting/scripts/lua/types/ecs/delta_time.lua new file mode 100644 index 0000000..dc29d16 --- /dev/null +++ b/lyra-scripting/scripts/lua/types/ecs/delta_time.lua @@ -0,0 +1,15 @@ +---@meta + +---@class DeltaTime +--- +---DeltaTime is an ECS world resource. When its requested from the world, a `number` +---is returned. +--- +---Example: +---```lua +------@type number +---local dt = world:resource(DeltaTime) +--- +---print(type(dt)) --> number +---``` +DeltaTime = {} \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/types/init.lua b/lyra-scripting/scripts/lua/types/init.lua index e607503..35bb190 100644 --- a/lyra-scripting/scripts/lua/types/init.lua +++ b/lyra-scripting/scripts/lua/types/init.lua @@ -1,3 +1,8 @@ require "math.vec2" +require "math.vec3" +require "math.vec4" +require "math.quat" +require "math.transform" -require "ecs.window" \ No newline at end of file +require "ecs.window" +require "ecs.delat_time" \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/types/math/quat.lua b/lyra-scripting/scripts/lua/types/math/quat.lua new file mode 100644 index 0000000..0ae6a03 --- /dev/null +++ b/lyra-scripting/scripts/lua/types/math/quat.lua @@ -0,0 +1,188 @@ +---@meta + +---@class Quat +---This is a Lua export of [`glam::Quat`](https://docs.rs/glam/latest/glam/f32/struct.Quat.html) +--- +---@operator add(self): self +---@operator sub(self): self +---@operator div(number): self +---@operator mul(self|Vec3|number): self +---@diagnostic disable-next-line: unknown-operator +---@operator eq: self +Quat = { + ---The x coordinate + ---@type number + x = nil, + + ---The y coordinate + ---@type number + y = nil, + + ---The z coordinate + ---@type number + z = nil, + + ---The w coordinate + ---@type number + w = nil, +} + +---Create a new `Quat` +---@param x number +---@param y number +---@param z number +---@param w number +---@return self +function Quat.new(x, y, z, w) end + +---Creates a quaternion from the angle (in radians) around the x axis. +---@param rad number +---@return self +function Quat.from_rotation_x(rad) end + +---Creates a quaternion from the angle (in radians) around the y axis. +---@param rad number +---@return self +function Quat.from_rotation_y(rad) end + +---Creates a quaternion from the angle (in radians) around the z axis. +---@param rad number +---@return self +function Quat.from_rotation_z(rad) end + +---Creates a quaternion from a `Vec4`. +---@param vec4 Vec4 +---@return self +function Quat.from_vec4(vec4) end + +---Create a quaternion for a normalized rotation axis and angle (in radians). +--- +---The axis must be a unit vector. +--- +---@param axis Vec3 +---@param rad number +---@return self +function Quat.from_axis_angle(axis, rad) end + +---Computes the dot product of self and rhs. +--- +---The dot product is equal to the cosine of the angle between two +---quaternion rotations. +--- +---@param rhs Quat +---@return number +function Quat:dot(rhs) end + +---Computes the length of self. +--- +---@return number +function Quat:length() end + +---Computes the squared length of self. +--- +---This is generally faster than length() as it avoids a square root operation. +--- +---@return number +function Quat:length_squared() end + +---Computes 1.0 / length(). +--- +---For valid results, self must not be of length zero. +---@return number +function length_recip() end + +---Returns `self` normalized to length `1.0`. +--- +---For valid results, `self` must not be of length zero. +--- +---@return self +function Quat:normalize() end + +---Multipies `self` with a `Quat` +---@param rhs Quat +function Quat:mult_quat(rhs) end + +---Multiplies `self` with a `Vec3` +---@param rhs Vec3 +function Quat:mult_vec3(rhs) end + +---Performs a linear interpolation between `self` and `rhs` based on `alpha`. +--- +---Both `Quat`s must be normalized. +--- +---When `alpha` is `0.0`, the result will be equal to `self`. When `alpha` is `1.0`, +---the result will be equal to `rhs`. +--- +---@param rhs Quat +---@param alpha number +function Quat:lerp(rhs, alpha) end + +---Performs a spherical linear interpolation between `self` and `rhs` based on `alpha`. +--- +---Both `Quat`s must be normalized. +--- +---When `alpha` is `0.0`, the result will be equal to `self`. When `alpha` is `1.0`, +---the result will be equal to `rhs`. +--- +---@param rhs Quat +---@param alpha number +function Quat:slerp(rhs, alpha) end + + +---Returns the inverse of a normalized quaternion. +--- +---Typically quaternion inverse returns the conjugate of a normalized quaternion. +---Because `self` is assumed to already be unit length this method does not +---normalize before returning the conjugate. +---@return self +function Quat:inverse() end + +---Returns `true` if, and only if, all elements are finite. If any element is either +---`NaN`, positive or negative infinity, this will return `false`. +--- +---@return boolean +function Quat:is_finite() end + +---@return boolean +function Quat:is_nan() end + +---Returns whether `self` is of length `1.0` or not. +--- +---Uses a precision threshold of `1e-6`. +---@return boolean +function Quat:is_normalized() end + +---@return boolean +function Quat:is_near_identity() end + +---Returns the angle (in radians) for the minimal rotation for transforming +---this quaternion into another. +--- +---Both quaternions must be normalized. +---@return number +function Quat:angle_between(rhs) end + +---Rotates towards `rhs` up to `max_angle` (in radians). +--- +---When `max_angle` is `0.0`, the result will be equal to `self`. When `max_angle` +---is equal to `self.angle_between(rhs)`, the result will be equal to `rhs`. +---If `max_angle` is negative, rotates towards the exact opposite of `rhs`. +---Will not go past the target. +--- +---Both quaternions must be normalized. +---@return self +function Quat:rotate_towards(rhs, max_angle) end + +---Returns true if the absolute difference of all elements between `self` and `rhs` is less +---than or equal to `max_abs_diff`. +--- +---This can be used to compare if two quaternions contain similar elements. It works best when +---comparing with a known value. The `max_abs_diff` that should be used used depends on the +---values being compared against. +--- +---For more see [comparing floating point numbers](https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/). +--- +---@param rhs Quat +---@param max_abs_diff number +---@return boolean +function Quat:abs_diff_eq(rhs, max_abs_diff) end diff --git a/lyra-scripting/scripts/lua/types/math/transform.lua b/lyra-scripting/scripts/lua/types/math/transform.lua new file mode 100644 index 0000000..0572eb8 --- /dev/null +++ b/lyra-scripting/scripts/lua/types/math/transform.lua @@ -0,0 +1,134 @@ +---@meta + +---@class Transform +--- +---A Transform represents a transformation of an object. A transform includes the position +---(called translation here), rotation, and scale. Rotation is represented using a Quaternion +---(or Quat for short). +--- +---Although Quats can be scary, they are much more robust than euler angles for games +---since they do not suffer from things like +---[gimbal-lock](https://en.wikipedia.org/wiki/Gimbal_lock). +--- +---This is a Lua export of [`lyra_math::Transform`]. +--- +---@operator add(Quat): self +---@operator mul(Vec3): self +---@diagnostic disable-next-line: unknown-operator +---@operator eq: self +Transform = { + ---The translation/position of the transform. + ---@type Vec3 + translation = nil, + ---The rotation of the transform. + ---@type Quat + rotation = nil, + ---The scale of the transform. + ---@type Vec3 + scale = nil, +} + +function Transform:__tostring() end + +---@return self +function Transform.default() end + +---Create a new transform with its components. +--- +---@param translation Vec3 +---@param rotation Quat +---@param scale Vec3 +---@return self +function Transform.new(translation, rotation, scale) end + +---Create a new transform with a `Vec3` translation. +---@param translation Vec3 +---@return self +function Transform.from_translation(translation) end + +---Create a new transform with a translation of `x`, `y`, and `z`. +--- +---@param x number +---@param y number +---@param z number +---@return self +function Transform.from_translation(x, y, z) end + +---Create a clone of `self` +---@return self +function Transform:clone() end + +---Returns a normalized vector pointing in the direction the Transform is facing. +--- +---This represents the front of the object can be used for movement, camera orientation, and +---other directional calculations. +--- +---@return Vec3 +function Transform:forward() end + +---Returns a normalized vector pointing to the left side of the Transform. +--- +---The vector is in local space. This represents the direction that is +---perpendicular to the object's forward direction. +--- +---@return Vec3 +function Transform:left() end + +---Returns a normalized vector that indicates the upward direction of the Transform. +--- +---This vector is commonly used to define an object's orientation and is essential for maintaining +---consistent vertical alignment in 3D environments, such as for camera positioning and object alignment. +---@return Vec3 +function Transform:up() end + +---Rotate `self` using a quaternion +---@param quat Quat +function Transform:rotate(quat) end + +---Rotate `self` around the x axis by **degrees**. +--- +---@param deg number The amount of **degrees** to rotate by. +function Transform:rotate_x(deg) end + +---Rotate `self` around the y axis by **degrees**. +--- +---@param deg number The amount of **degrees** to rotate by. +function Transform:rotate_y(deg) end + +---Rotate `self` around the z axis by **degrees**. +--- +---@param deg number The amount of **degrees** to rotate by. +function Transform:rotate_z(deg) end + +---Rotate `self` around the x axis by **radians** . +--- +---@param rad number The amount of **radians** to rotate by. +function Transform:rotate_x_rad(rad) end + +---Rotate `self` around the y axis by **radians** . +--- +---@param rad number The amount of **radians** to rotate by. +function Transform:rotate_y_rad(rad) end + +---Rotate `self` around the z axis by **radians** . +--- +---@param rad number The amount of **radians** to rotate by. +function Transform:rotate_z_rad(rad) end + +---Move `self` by `x`, `y`, and `z`. +--- +---@param x number +---@param y number +---@param z number +function Transform:translate(x, y, z) end + +---Performs a linear interpolation between `self` and `rhs` based on `alpha`. +--- +---This will normalize the rotation `Quat`. +--- +---When `alpha` is `0.0`, the result will be equal to `self`. When `alpha` is `1.0`, +---the result will be equal to `rhs`. +--- +---@param rhs Transform +---@param alpha number +function Transform:lerp(rhs, alpha) end \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/types/math/vec2.lua b/lyra-scripting/scripts/lua/types/math/vec2.lua index a564513..16fc596 100644 --- a/lyra-scripting/scripts/lua/types/math/vec2.lua +++ b/lyra-scripting/scripts/lua/types/math/vec2.lua @@ -1,14 +1,125 @@ +---@meta + ---@class Vec2 +---This is a Lua export of [`glam::Vec2`](https://docs.rs/glam/latest/glam/f32/struct.Vec2.html) +--- +---@operator add(self|number): self +---@operator sub(self|number): self +---@operator div(self|number): self +---@operator mul(self|number): self +---@operator mod(self|number): self +---@operator unm: self +---@diagnostic disable-next-line: unknown-operator +---@operator eq: self Vec2 = { ---The x coordinate ---@type number x = nil, + ---The y coordinate ---@type number y = nil, - ---Create a new `Vec2` - ---@param x number - ---@param y number - new = function (x, y) end -} \ No newline at end of file + ---A constant `Vec2` with coordinates as `f32::NAN`. + ---@type Vec2 + NAN = nil, + + ---A constant `Vec2` with `x` as `-1.0`. + ---@type Vec2 + NEG_X = nil, + + ---A constant `Vec2` with `y` as `-1.0`. + ---@type Vec2 + NEG_Y = nil, + + ---A constant `Vec2` with both components as `-1.0`. + ---@type Vec2 + NEG_ONE = nil, + + ---A constant `Vec2` with `x` as `1.0`. + ---@type Vec2 + POS_X = nil, + + ---A constant `Vec2` with `y` as `1.0`. + ---@type Vec2 + POS_Y = nil, + + ---A constant `Vec2` with both components as `1.0`. + ---@type Vec2 + ONE = nil, + + ---A constant `Vec2` with both components as `0.0`. + ---@type Vec2 + ZERO = nil, +} + +function Vec2:__tostring() end + +---Create a new `Vec2` +---@param x number +---@param y number +---@return self +function Vec2.new(x, y) end + +---Returns a vector with a length no less than min and no more than max. +---@param min number the minimum value to clamp the length to +---@param max number the maximum value to clamp the length to +---@return self +function Vec2:clamp_length(min, max) end + +---Returns true if the absolute difference of all elements between `self` and `rhs` is less +---than or equal to `max_abs_diff`. +--- +---This can be used to compare if two vectors contain similar elements. It works best when +---comparing with a known value. The `max_abs_diff` that should be used used depends on the +---values being compared against. +--- +---For more see [comparing floating point numbers](https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/). +--- +---@param rhs Vec2 The other `Vec2` to compare to. +---@param max_abs_diff number Maximum absolute difference between `self` and `rhs`. +---@return boolean +function Vec2:abs_diff_eq(rhs, max_abs_diff) end + +---Returns a vector containing the smallest integer greater than or equal to a number for each +---element of self. +---@return self +function Vec2:ceil() end + +---Returns the angle of rotation (in radians) from `self` to `rhs` in the range [-π, +π]. +--- +---The inputs do not need to be unit vectors however they must be non-zero. +--- +---@param rhs Vec2 The other `Vec2` to get the angle to. +---@return number +function Vec2:angle_to(rhs) end + +---Returns a vector containing the absolute value of each element of `self`. +--- +---@return self +function Vec2:abs() end + +---Component-wise clamping of values. +--- +---Each element in `min` must be less-or-equal to the corresponding element in `max`. +--- +---@param min self The minimum `Vec2` components to clamp the components of `self` to. +---@param max self The maximum `Vec2` components to clamp the components of `self` to. +---@return self +function Vec2:clamp(min, max) end + +---Converts `self` to an array `[x, y]` +--- +---@return number[] +function Vec2:to_array() end + +---Move `self` by `x` and `y` values. +--- +---@param x number +---@param y number +function Vec2:move_by(x, y) end + +---Move `self` by a `Vec2`. +--- +---@param rhs Vec2 +function Vec2:move_by(rhs) end diff --git a/lyra-scripting/scripts/lua/types/math/vec3.lua b/lyra-scripting/scripts/lua/types/math/vec3.lua new file mode 100644 index 0000000..107d4ac --- /dev/null +++ b/lyra-scripting/scripts/lua/types/math/vec3.lua @@ -0,0 +1,139 @@ +---@meta + +---@class Vec3 +---This is a Lua export of [`glam::Vec3`](https://docs.rs/glam/latest/glam/f32/struct.Vec3.html) +--- +---@operator add(self|number): self +---@operator sub(self|number): self +---@operator div(self|number): self +---@operator mul(self|number): self +---@operator mod(self|number): self +---@operator unm: self +---@diagnostic disable-next-line: unknown-operator +---@operator eq: self +Vec3 = { + ---The x coordinate + ---@type number + x = nil, + + ---The y coordinate + ---@type number + y = nil, + + ---The z coordinate + ---@type number + z = nil, + + ---A constant `Vec3` with coordinates as `f32::NAN`. + ---@type Vec3 + NAN = nil, + + ---A constant `Vec3` with `x` as `-1.0`. + ---@type Vec3 + NEG_X = nil, + + ---A constant `Vec3` with `y` as `-1.0`. + ---@type Vec3 + NEG_Y = nil, + + ---A constant `Vec3` with `z` as `-1.0`. + ---@type Vec3 + NEG_Z = nil, + + ---A constant `Vec3` with all components as `-1.0`. + ---@type Vec3 + NEG_ONE = nil, + + ---A constant `Vec3` with `x` as `1.0`. + ---@type Vec3 + POS_X = nil, + + ---A constant `Vec3` with `y` as `1.0`. + ---@type Vec3 + POS_Y = nil, + + ---A constant `Vec3` with `z` as `1.0`. + ---@type Vec3 + POS_Z = nil, + + ---A constant `Vec3` with all components as `1.0`. + ---@type Vec3 + ONE = nil, + + ---A constant `Vec3` with all components as `0.0`. + ---@type Vec3 + ZERO = nil, +} + +function Vec3:__tostring() end + +---Create a new `Vec3` +---@param x number +---@param y number +---@param z number +---@return self +function Vec3.new(x, y, z) end + +---Returns a vector with a length no less than min and no more than max. +---@param min number the minimum value to clamp the length to +---@param max number the maximum value to clamp the length to +---@return self +function Vec3:clamp_length(min, max) end + +---Returns true if the absolute difference of all elements between `self` and `rhs` is less +---than or equal to `max_abs_diff`. +--- +---This can be used to compare if two vectors contain similar elements. It works best when +---comparing with a known value. The `max_abs_diff` that should be used used depends on the +---values being compared against. +--- +---For more see [comparing floating point numbers](https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/). +--- +---@param rhs Vec3 The other `Vec3` to compare to. +---@param max_abs_diff number Maximum absolute difference between `self` and `rhs`. +---@return boolean +function Vec3:abs_diff_eq(rhs, max_abs_diff) end + +---Returns a vector containing the smallest integer greater than or equal to a number for each +---element of self. +---@return self +function Vec3:ceil() end + +---Returns the angle (in radians) between two vectors in the range [-π, +π]. +--- +---The inputs do not need to be unit vectors however they must be non-zero. +--- +---@param rhs Vec3 The other `Vec3` to get the angle to. +---@return number +function Vec3:angle_between(rhs) end + +---Returns a vector containing the absolute value of each element of `self`. +--- +---@return self +function Vec3:abs() end + +---Component-wise clamping of values. +--- +---Each element in `min` must be less-or-equal to the corresponding element in `max`. +--- +---@param min self The minimum `Vec3` components to clamp the components of `self` to. +---@param max self The maximum `Vec3` components to clamp the components of `self` to. +---@return self +function Vec3:clamp(min, max) end + +---Converts `self` to an array `[x, y, z]` +--- +---@return number[] +function Vec3:to_array() end + +---Move `self` by `x`, `y`, and `z` values. +--- +---@param x number +---@param y number +---@param z number +function Vec3:move_by(x, y, z) end + +---Move `self` by a `Vec3`. +--- +---@param rhs Vec3 +function Vec3:move_by(rhs) end diff --git a/lyra-scripting/scripts/lua/types/math/vec4.lua b/lyra-scripting/scripts/lua/types/math/vec4.lua new file mode 100644 index 0000000..be72e90 --- /dev/null +++ b/lyra-scripting/scripts/lua/types/math/vec4.lua @@ -0,0 +1,132 @@ +---@meta + +---@class Vec4 +---This is a Lua export of [`glam::Vec4`](https://docs.rs/glam/latest/glam/f32/struct.Vec4.html) +--- +---@operator add(self|number): self +---@operator sub(self|number): self +---@operator div(self|number): self +---@operator mul(self|number): self +---@operator mod(self|number): self +---@operator unm: self +---@diagnostic disable-next-line: unknown-operator +---@operator eq: self +Vec4 = { + ---The x coordinate + ---@type number + x = nil, + + ---The y coordinate + ---@type number + y = nil, + + ---The z coordinate + ---@type number + z = nil, + + ---The w coordinate + ---@type number + w = nil, + + ---A constant `Vec4` with coordinates as `f32::NAN`. + ---@type Vec4 + NAN = nil, + + ---A constant `Vec4` with `x` as `-1.0`. + ---@type Vec4 + NEG_X = nil, + + ---A constant `Vec4` with `y` as `-1.0`. + ---@type Vec4 + NEG_Y = nil, + + ---A constant `Vec4` with `z` as `-1.0`. + ---@type Vec4 + NEG_Z = nil, + + ---A constant `Vec4` with `w` as `-1.0`. + ---@type Vec4 + NEG_W = nil, + + ---A constant `Vec4` with all components as `-1.0`. + ---@type Vec4 + NEG_ONE = nil, + + ---A constant `Vec4` with `x` as `1.0`. + ---@type Vec4 + POS_X = nil, + + ---A constant `Vec4` with `y` as `1.0`. + ---@type Vec4 + POS_Y = nil, + + ---A constant `Vec4` with `z` as `1.0`. + ---@type Vec4 + POS_Z = nil, + + ---A constant `Vec4` with `w` as `1.0`. + ---@type Vec4 + POS_W = nil, + + ---A constant `Vec4` with all components as `1.0`. + ---@type Vec4 + ONE = nil, + + ---A constant `Vec4` with all components as `0.0`. + ---@type Vec4 + ZERO = nil, +} + +function Vec4:__tostring() end + +---Create a new `Vec4` +---@param x number +---@param y number +---@param z number +---@param w number +---@return self +function Vec4.new(x, y, z, w) end + +---Returns a vector with a length no less than min and no more than max. +---@param min number the minimum value to clamp the length to +---@param max number the maximum value to clamp the length to +---@return self +function Vec4:clamp_length(min, max) end + +---Returns true if the absolute difference of all elements between `self` and `rhs` is less +---than or equal to `max_abs_diff`. +--- +---This can be used to compare if two vectors contain similar elements. It works best when +---comparing with a known value. The `max_abs_diff` that should be used used depends on the +---values being compared against. +--- +---For more see [comparing floating point numbers](https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/). +--- +---@param rhs Vec4 The other `Vec4` to compare to. +---@param max_abs_diff number Maximum absolute difference between `self` and `rhs`. +---@return boolean +function Vec4:abs_diff_eq(rhs, max_abs_diff) end + +---Returns a vector containing the smallest integer greater than or equal to a number for each +---element of self. +---@return self +function Vec4:ceil() end + +---Returns a vector containing the absolute value of each element of `self`. +--- +---@return self +function Vec4:abs() end + +---Component-wise clamping of values. +--- +---Each element in `min` must be less-or-equal to the corresponding element in `max`. +--- +---@param min self The minimum `Vec4` components to clamp the components of `self` to. +---@param max self The maximum `Vec4` components to clamp the components of `self` to. +---@return self +function Vec4:clamp(min, max) end + +---Converts `self` to an array `[x, y, z]` +--- +---@return number[] +function Vec4:to_array() end diff --git a/lyra-scripting/src/lua/wrappers/math.rs b/lyra-scripting/src/lua/wrappers/math.rs index 7279ac5..41967fb 100644 --- a/lyra-scripting/src/lua/wrappers/math.rs +++ b/lyra-scripting/src/lua/wrappers/math.rs @@ -1,4 +1,6 @@ -use crate::lyra_engine; +use std::sync::Arc; + +use crate::{lua::Error, lyra_engine}; use lyra_game::math; use lyra_scripting_derive::{lua_vec_wrap_extension, wrap_lua_struct}; use mlua::FromLuaMulti; @@ -29,7 +31,14 @@ wrap_lua_struct!( } else if let Ok(v) = Self::from_lua_multi(vals_clone, lua) { this.0 += v.0; } else { - todo!("handle invalid argument error"); + return Err(mlua::Error::BadArgument { + to: Some("Vec2:move_by".into()), + pos: 2, + name: None, + cause: Arc::new(mlua::Error::runtime( + "expected (number, number, number) or (Vec2), received neither" + )), + }); } Ok(()) @@ -62,7 +71,14 @@ wrap_lua_struct!( } else if let Ok(v) = Self::from_lua_multi(vals_clone, lua) { this.0 += v.0; } else { - todo!("handle invalid argument error"); + return Err(mlua::Error::BadArgument { + to: Some("Vec3:move_by".into()), + pos: 2, + name: None, + cause: Arc::new(mlua::Error::runtime( + "expected (number, number, number) or (Vec3), received neither" + )), + }); } Ok(()) @@ -122,10 +138,27 @@ wrap_lua_struct!( Ok(Self(q)) }); + methods.add_function("from_vec4", |_, v: LuaVec4| { + Ok(Self(math::Quat::from_vec4(*v))) + }); + + methods.add_function("from_axis_angle", |_, (axis, angle): (LuaVec3, f32)| { + let q = math::Quat::from_axis_angle(*axis, angle); + Ok(Self(q)) + }); + methods.add_method("dot", |_, this, (rhs,): (Self,)| { Ok(this.dot(rhs.0)) }); + methods.add_method("conjugate", |_, this, ()| { + Ok(Self(this.conjugate())) + }); + + methods.add_method("inverse", |_, this, ()| { + Ok(Self(this.inverse())) + }); + methods.add_method("length", |_, this, ()| { Ok(this.length()) }); @@ -134,20 +167,50 @@ wrap_lua_struct!( Ok(this.length_squared()) }); - methods.add_method_mut("normalize", |_, this, ()| { - this.0 = this.normalize(); - Ok(()) + methods.add_method("length_recip", |_, this, ()| { + Ok(this.length_recip()) }); - methods.add_method_mut("mult_quat", |_, this, (rhs,): (Self,)| { - this.0 *= rhs.0; - Ok(()) + methods.add_method("normalize", |_, this, ()| { + Ok(Self(this.normalize())) + }); + + methods.add_method("mult_quat", |_, this, (rhs,): (Self,)| { + Ok(Self(this.0 * rhs.0)) }); methods.add_method("mult_vec3", |_, this, (rhs,): (LuaVec3,)| { Ok(LuaVec3(this.0 * rhs.0)) }); + methods.add_method("is_finite", |_, this, ()| { + Ok(this.is_finite()) + }); + + methods.add_method("is_nan", |_, this, ()| { + Ok(this.is_nan()) + }); + + methods.add_method("is_normalized", |_, this, ()| { + Ok(this.is_normalized()) + }); + + methods.add_method("is_near_identity", |_, this, ()| { + Ok(this.is_near_identity()) + }); + + methods.add_method("angle_between", |_, this, rhs: LuaQuat| { + Ok(this.angle_between(*rhs)) + }); + + methods.add_method("rotate_towards", |_, this, (rhs, max_angle): (LuaQuat, f32)| { + Ok(Self(this.rotate_towards(*rhs, max_angle))) + }); + + methods.add_method("abs_diff_eq", |_, this, (rhs, max_abs_diff): (LuaQuat, f32)| { + Ok(this.abs_diff_eq(*rhs, max_abs_diff)) + }); + // manually implemented here since multiplying may not return `Self`. methods.add_meta_method(mlua::MetaMethod::Mul, |lua, this, (val,): (mlua::Value,)| { use mlua::IntoLua; @@ -169,7 +232,15 @@ wrap_lua_struct!( .into_lua(lua) }, _ => { - todo!() + let t = val.type_name(); + Err(mlua::Error::BadArgument { + to: Some("Quat:__mul".into()), + pos: 2, + name: None, + cause: Arc::new(mlua::Error::external( + Error::type_mismatch("Vec3, Quat, or Number", t) + )), + }) } } }); @@ -177,6 +248,10 @@ wrap_lua_struct!( methods.add_method("lerp", |_, this, (rhs, alpha): (Self, f32)| { Ok(Self(this.lerp(*rhs, alpha))) }); + + methods.add_method("slerp", |_, this, (rhs, alpha): (Self, f32)| { + Ok(Self(this.slerp(*rhs, alpha))) + }); } ); @@ -193,12 +268,22 @@ wrap_lua_struct!( Ok(Self(math::Transform::new(*pos, *rot, *scale))) }); - methods.add_function("from_translation", |_, (pos,): (LuaVec3,)| { - Ok(Self(math::Transform::from_translation(*pos))) - }); - - methods.add_function("from_xyz", |_, (x, y, z)| { - Ok(Self(math::Transform::from_xyz(x, y, z))) + methods.add_function("from_translation", |lua, vals: mlua::MultiValue| { + let vals_clone = vals.clone(); + if let Ok((x, y, z)) = <(f32, f32, f32) as FromLuaMulti>::from_lua_multi(vals, lua) { + Ok(Self(math::Transform::from_xyz(x, y, z))) + } else if let Ok(v) = LuaVec3::from_lua_multi(vals_clone, lua) { + Ok(Self(math::Transform::from_translation(*v))) + } else { + Err(mlua::Error::BadArgument { + to: Some("Transform:from_translation".into()), + pos: 2, + name: None, + cause: Arc::new(mlua::Error::runtime( + "expected (number, number, number) or (Vec3), received neither" + )), + }) + } }); methods.add_method("clone", |_, this, ()| { -- 2.40.1 From 140ca506d6b50c564eeadfe11cefb92a0ac0b886 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Fri, 4 Oct 2024 23:48:58 -0400 Subject: [PATCH 03/15] lua: create type defs for World, Entity, ActionHandler, all asset handlers, add globals file --- examples/lua-scripting/scripts/test.lua | 2 +- lyra-game/src/input/action.rs | 57 ++++---- lyra-scripting/scripts/lua/action_handler.lua | 9 ++ lyra-scripting/scripts/lua/asset_handle.lua | 12 ++ .../scripts/lua/types/asset/gltf_handle.lua | 23 ++++ .../scripts/lua/types/asset/handle.lua | 43 ++++++ .../lua/types/asset/material_handle.lua | 102 ++++++++++++++ .../scripts/lua/types/asset/mesh_handle.lua | 14 ++ .../scripts/lua/types/asset/scene_handle.lua | 9 ++ .../lua/types/asset/texture_handle.lua | 4 + .../scripts/lua/types/ecs/action_handler.lua | 125 ++++++++++++++++++ .../scripts/lua/types/ecs/delta_time.lua | 2 +- .../scripts/lua/types/ecs/entity.lua | 4 + .../scripts/lua/types/ecs/window.lua | 2 +- .../scripts/lua/types/ecs/world.lua | 75 +++++++++++ lyra-scripting/scripts/lua/types/globals.lua | 5 + lyra-scripting/scripts/lua/types/init.lua | 4 +- .../scripts/lua/types/math/quat.lua | 2 +- .../scripts/lua/types/math/transform.lua | 2 +- .../scripts/lua/types/math/vec2.lua | 2 +- .../scripts/lua/types/math/vec3.lua | 2 +- .../scripts/lua/types/math/vec4.lua | 2 +- lyra-scripting/src/lua/providers/ecs.rs | 8 +- lyra-scripting/src/lua/world.rs | 2 +- lyra-scripting/src/lua/wrappers/asset/mod.rs | 3 + .../src/lua/wrappers/input_actions.rs | 20 ++- 26 files changed, 485 insertions(+), 50 deletions(-) create mode 100644 lyra-scripting/scripts/lua/action_handler.lua create mode 100644 lyra-scripting/scripts/lua/asset_handle.lua create mode 100644 lyra-scripting/scripts/lua/types/asset/gltf_handle.lua create mode 100644 lyra-scripting/scripts/lua/types/asset/handle.lua create mode 100644 lyra-scripting/scripts/lua/types/asset/material_handle.lua create mode 100644 lyra-scripting/scripts/lua/types/asset/mesh_handle.lua create mode 100644 lyra-scripting/scripts/lua/types/asset/scene_handle.lua create mode 100644 lyra-scripting/scripts/lua/types/asset/texture_handle.lua create mode 100644 lyra-scripting/scripts/lua/types/ecs/action_handler.lua create mode 100644 lyra-scripting/scripts/lua/types/ecs/entity.lua create mode 100644 lyra-scripting/scripts/lua/types/ecs/world.lua create mode 100644 lyra-scripting/scripts/lua/types/globals.lua diff --git a/examples/lua-scripting/scripts/test.lua b/examples/lua-scripting/scripts/test.lua index 2bb8a16..4b4c9d6 100644 --- a/examples/lua-scripting/scripts/test.lua +++ b/examples/lua-scripting/scripts/test.lua @@ -17,7 +17,7 @@ end function on_init() - local cube = world:request_res("../assets/cube-texture-embedded.gltf") + local cube = world:request_asset("../assets/cube-texture-embedded.gltf") --[[@as GltfHandle]] print("Loaded textured cube (" .. udname(cube) .. ")") cube:wait_until_loaded() diff --git a/lyra-game/src/input/action.rs b/lyra-game/src/input/action.rs index 15a2b10..1ad1db2 100644 --- a/lyra-game/src/input/action.rs +++ b/lyra-game/src/input/action.rs @@ -358,66 +358,61 @@ impl ActionHandler { /// 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: L) -> bool + /// Returns `None` if the action was not found. + pub fn is_action_pressed(&self, action: L) -> Option where L: ActionLabel { - let action = self.actions.get(&action.label_hash()) - .unwrap_or_else(|| panic!("Action {action:?} was not found")); + let action = self.actions.get(&action.label_hash())?; - matches!(action.state, ActionState::Pressed(_) | ActionState::JustPressed(_)) + Some(matches!(action.state, ActionState::Pressed(_) | ActionState::JustPressed(_))) } /// 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: L) -> bool + /// Returns `None` if the action was not found. + pub fn was_action_just_pressed(&self, action: L) -> Option where L: ActionLabel { - let action = self.actions.get(&action.label_hash()) - .unwrap_or_else(|| panic!("Action {action:?} was not found")); + let action = self.actions.get(&action.label_hash())?; - matches!(action.state, ActionState::JustPressed(_)) + Some(matches!(action.state, ActionState::JustPressed(_))) } /// 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: L) -> bool + /// Returns `None` if the action was not found. + pub fn was_action_just_released(&self, action: L) -> Option where L: ActionLabel { - let action = self.actions.get(&action.label_hash()) - .unwrap_or_else(|| panic!("Action {action:?} was not found")); + let action = self.actions.get(&action.label_hash())?; - matches!(action.state, ActionState::JustReleased) + Some(matches!(action.state, ActionState::JustReleased)) } /// 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: L) -> ActionState + /// Returns `None` if the action was not found. + pub fn get_action_state(&self, action: L) -> Option where L: ActionLabel { - let action = self.actions.get(&action.label_hash()) - .unwrap_or_else(|| panic!("Action {action:?} was not found")); + let action = self.actions.get(&action.label_hash())?; - action.state + Some(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. + /// Returns `None` if the action's state is not `ActionState::Pressed`, `ActionState::JustPressed`, + /// or if the action was not found. pub fn get_pressed_modifier(&self, action: L) -> Option where L: ActionLabel { - let action = self.actions.get(&action.label_hash()) - .unwrap_or_else(|| panic!("Action {action:?} was not found")); + let action = self.actions.get(&action.label_hash())?; match action.state { ActionState::Pressed(v) | ActionState::JustPressed(v) => Some(v), @@ -426,15 +421,14 @@ impl ActionHandler { } /// 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. + /// Returns `None` if the action's state is not `ActionState::JustPressed`, + /// or if the action was not found. pub fn get_just_pressed_modifier(&self, action: L) -> Option where L: ActionLabel { - let action = self.actions.get(&action.label_hash()) - .unwrap_or_else(|| panic!("Action {action:?} was not found")); + let action = self.actions.get(&action.label_hash())?; match action.state { ActionState::JustPressed(v) => Some(v), @@ -443,15 +437,14 @@ impl ActionHandler { } /// 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. + /// Returns `None` if the action's state is not `ActionState::Axis`, + /// or if the action was not found. pub fn get_axis_modifier(&self, action: L) -> Option where L: ActionLabel { - let action = self.actions.get(&action.label_hash()) - .unwrap_or_else(|| panic!("Action {action:?} was not found")); + let action = self.actions.get(&action.label_hash())?; match action.state { ActionState::Axis(v) => Some(v), diff --git a/lyra-scripting/scripts/lua/action_handler.lua b/lyra-scripting/scripts/lua/action_handler.lua new file mode 100644 index 0000000..c4c0b8f --- /dev/null +++ b/lyra-scripting/scripts/lua/action_handler.lua @@ -0,0 +1,9 @@ +---@enum ActionState +ActionState = { + IDLE = "idle", + PRESSED = "pressed", + JUST_PRESSED = "just_pressed", + JUST_RELEASED = "just_released", + AXIS = "axis", + OTHER = "other", +} \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/asset_handle.lua b/lyra-scripting/scripts/lua/asset_handle.lua new file mode 100644 index 0000000..00d276e --- /dev/null +++ b/lyra-scripting/scripts/lua/asset_handle.lua @@ -0,0 +1,12 @@ +---@enum HandleState +HandleState = { + LOADING = "loading", + READY = "ready", + ERROR = "error", +} + +---@enum ActionKind +ActionKind = { + BUTTON = "button", + AXIS = "axis", +} \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/types/asset/gltf_handle.lua b/lyra-scripting/scripts/lua/types/asset/gltf_handle.lua new file mode 100644 index 0000000..3478e36 --- /dev/null +++ b/lyra-scripting/scripts/lua/types/asset/gltf_handle.lua @@ -0,0 +1,23 @@ +---@meta + +---@class GltfHandle: Handle +--- +---A handle to a GLTF asset. +GltfHandle = { + +} + +---Get a list of scenes in the GLTF file. +--- +---@return SceneHandle[] +function GltfHandle:scenes() end + +---Get a list of materials in the GLTF file. +--- +---@return MaterialHandle[] +function GltfHandle:materials() end + +---Get a list of meshes in the GLTF file. +--- +---@return MeshHandle[] +function GltfHandle:meshes() end \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/types/asset/handle.lua b/lyra-scripting/scripts/lua/types/asset/handle.lua new file mode 100644 index 0000000..1c39963 --- /dev/null +++ b/lyra-scripting/scripts/lua/types/asset/handle.lua @@ -0,0 +1,43 @@ +---@meta + +---@class Handle: userdata +--- +---A handle to an asset. Assets are loaded asynchronously, so you cannot immediately +---use them after you request them from the World. +Handle = { + ---The path the asset was loaded from. + --- + ---@type string + path = nil, + + ---The version of the resource. + --- + ---Increments every time a resource is loaded. + --- + ---@type number + version = nil, + + ---The unique id of the resource. + --- + ---This is not changed for the entire lifetime of the handle, it does not change + ---when an asset is reloaded. + --- + ---@type string + uuid = nil, + + ---Current state of the asset handle. + ---@type HandleState + state = nil, +} + +---Returns true if the asset is watched for auto-reloading. +--- +---@return boolean +function Handle:is_watched() end + +---Returns true if the asset is loaded. +---@return boolean +function Handle:is_loaded() end + +---Blocks execution until the asset and its dependencies are loaded. +function Handle:wait_until_loaded() end \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/types/asset/material_handle.lua b/lyra-scripting/scripts/lua/types/asset/material_handle.lua new file mode 100644 index 0000000..d84d49c --- /dev/null +++ b/lyra-scripting/scripts/lua/types/asset/material_handle.lua @@ -0,0 +1,102 @@ +---@meta + +---@class MaterialHandle: Handle +--- +---A handle to a material asset in a GLTF file. +MaterialHandle = { + ---The unique id of the GPU shader. + ---@type number? + shader_uuid = nil, + + ---The name of the material from GLTF. + ---@type string? + name = nil, + + ---@type boolean + double_sided = nil, + + ---The RGBA base color of the model. + --- + ---If a texture is supplied with `base_color_texture`, this value will tint the + ---texture. If a texture is not provided, this value would be the color of + ---the Material. + --- + ---@type Vec4 + base_color = nil, + + ---The metalness of the material + --- + ---From 0.0 (non-metal) to 1.0 (metal). + --- + ---@type number + metallic = nil, + + ---The roughness of the material + --- + ---From 0.0 (smooth) to 1.0 (rough) + --- + ---@type number + roughness = nil, + + ---The base color texture of the model. + ---@type TextureHandle? + base_color_texture = nil, + + ---The metallic-roughness texture. + --- + ---The metalness values are sampled from the B channel. The roughness values are sampled from + ---the G channel. These values are linear. If other channels are present (R or A), they are + ---ignored for metallic-roughness calculations. + ---@type TextureHandle? + metallic_roughness_texture = nil, + + ---A set of parameter values that are used to define the specular-glossiness material model + ---from Physically-Based Rendering (PBR) methodology. + ---GLTF extension: [KHR_materials_pbrSpecularGlossiness](https://kcoley.github.io/glTF/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness) + ---@type TextureHandle? + pbr_glossiness = nil, + + ---The optional alpha cutoff value of the material. + --- + ---This will be used instead of the renderer's default. + --- + ---@type number? + alpha_cutoff = nil, + + ---The alpha rendering mode of the material. + --- + ---The material's alpha rendering + ---mode enumeration specifying the interpretation of the alpha value of the main + ---factor and texture. + --- + ---* In `Opaque` mode (default) the alpha value is ignored + --- and the rendered output is fully opaque. + ---* In `Mask` mode, the rendered + --- output is either fully opaque or fully transparent depending on the alpha + --- value and the specified alpha cutoff value. + ---* In `Blend` mode, the alpha value is used to composite the source and + --- destination areas and the rendered output is combined with the background + --- using the normal painting operation (i.e. the Porter and Duff over + --- operator). + --- + ---@type AlphaMode + alpha_mode = nil, + + ---@type Specular? + specular = nil, +} + +---@enum AlphaMode +AlphaMode = { + OPAQUE = "opaque", + MASK = "mask", + BLEND = "blend", +} + +---@class PbrGlossiness +---TODO: implement +PbrGlossiness = {} + +---@class Specular +---TODO: implement +Specular = {} \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/types/asset/mesh_handle.lua b/lyra-scripting/scripts/lua/types/asset/mesh_handle.lua new file mode 100644 index 0000000..3ba7017 --- /dev/null +++ b/lyra-scripting/scripts/lua/types/asset/mesh_handle.lua @@ -0,0 +1,14 @@ +---@meta + +---@class MeshHandle: Handle +--- +---A handle to a mesh in a GLTF file. +MeshHandle = { + ---The material of the mesh + ---@type MaterialHandle + material = nil, +} + +---Get the indices in the mesh. +---@return number[] +function MeshHandle:indices() end \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/types/asset/scene_handle.lua b/lyra-scripting/scripts/lua/types/asset/scene_handle.lua new file mode 100644 index 0000000..ed00b82 --- /dev/null +++ b/lyra-scripting/scripts/lua/types/asset/scene_handle.lua @@ -0,0 +1,9 @@ +---@meta + +---@class SceneHandle: Handle +--- +---A handle to a scene asset in a GLTF file. +SceneHandle = { + +} + diff --git a/lyra-scripting/scripts/lua/types/asset/texture_handle.lua b/lyra-scripting/scripts/lua/types/asset/texture_handle.lua new file mode 100644 index 0000000..7a63b8b --- /dev/null +++ b/lyra-scripting/scripts/lua/types/asset/texture_handle.lua @@ -0,0 +1,4 @@ +---@meta + +---@class TextureHandle +TextureHandle = {} \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/types/ecs/action_handler.lua b/lyra-scripting/scripts/lua/types/ecs/action_handler.lua new file mode 100644 index 0000000..b7fd169 --- /dev/null +++ b/lyra-scripting/scripts/lua/types/ecs/action_handler.lua @@ -0,0 +1,125 @@ +---@meta + +---@class ActionHandler: userdata +ActionHandler = {} + +--- Create a new `ActionHandler`. +--- +--- ```lua +--- local handler = ActionHandler.new { +--- -- A list of layout IDs +--- layouts = { 0 }, +--- actions = { +--- -- A list of action names and the `ActionKind`s. +--- -- Actions can be buttons or axes. +--- MoveForwardBackward = ActionKind.AXIS, +--- MoveLeftRight = ActionKind.AXIS, +--- MoveUpDown = ActionKind.AXIS, +--- LookLeftRight = ActionKind.AXIS, +--- LookUpDown = ActionKind.AXIS, +--- LookRoll = ActionKind.AXIS, +--- ObjectsMoveUpDown = ActionKind.AXIS +--- }, +--- mappings = { +--- -- Each entry here is a mapping of actions for a layout. +--- -- This can be used so that when the current layout is changed, +--- -- the mapping would also change. +--- { +--- -- Specify the layout id that this mapping is for. +--- layout = 0, +--- binds = { +--- -- This is an Action bind. A bind is used to bind an input to an action. +--- -- These actions are defined above in "actions". +--- MoveForwardBackward = { +--- -- This is how you bind a button. In this case the button is a key. +--- -- "key" is the device the bind comes from, then after the colon is the +--- -- input name, in this case a specific key. We specify a modifier to the bind +--- -- after the equal sign. +--- "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", +--- -- Here is a bind to an axis. +--- -- We use "mouse", for the device the bind is from, then "axis" to specify +--- -- that we want to bind a specific axis, then we use the name of the axis, +--- -- in this case "x". +--- -- So this binds to the x axis of the mouse. +--- "mouse:axis:x" +--- }, +--- LookUpDown = { +--- "key:up=-1.0", "key:down=1.0", +--- -- Here we bind to the y axis of the mouse. +--- "mouse:axis:y", +--- }, +--- LookRoll = { +--- "key:e=-1.0", "key:q=1.0", +--- }, +--- ObjectsMoveUpDown = { +--- "key:u=1.0", "key:j=-1.0" +--- } +--- } +--- } +--- } +--- } +--- +--- -- Add the handler to the world so the host will process it. +--- world:add_resource(handler) +--- ``` +--- +---@param table table See above example to see the format of this table. +function ActionHandler.new(table) end + +---Returns the action's modifier if its an updated axis. +--- +---Returns `nil` if the action's state is not `ActionState::Axis`, or if the +---action was not found. +---@param action string +---@return number? +function ActionHandler:get_axis(action) end + +---Returns true if the action is pressed (or was just pressed). +--- +---Returns `nil` if the action's was not found. +---@param action string +---@return boolean? +function ActionHandler:is_pressed(action) end + +---Returns true if the action was **just** pressed. +--- +---Returns `nil` if the action was not found +--- +---@param action string +---@return boolean? +function ActionHandler:was_just_pressed(action) end + +---Returns true if the action was just released. +--- +---Returns `nil` if the action was not found +--- +---@param action string +---@return boolean? +function ActionHandler:was_just_released(action) end + +---Returns the action's modifier if it was just pressed. +--- +---Returns `nil` if the action's state is not `ActionState.JUST_PRESSED`, +---or if the action was not found. +--- +---@param action string +---@return number? +function ActionHandler:get_just_pressed(action) end + +---Returns the current state of the action. +--- +---The first element in the returned tuple is the state enum, and the second +---is the state modifier. The modifer will be `nil` if the state is "idle" +--- +---@return [ActionState, number?] +function ActionHandler:get_action_state(action) end \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/types/ecs/delta_time.lua b/lyra-scripting/scripts/lua/types/ecs/delta_time.lua index dc29d16..2efd0ee 100644 --- a/lyra-scripting/scripts/lua/types/ecs/delta_time.lua +++ b/lyra-scripting/scripts/lua/types/ecs/delta_time.lua @@ -1,6 +1,6 @@ ---@meta ----@class DeltaTime +---@class DeltaTime: userdata --- ---DeltaTime is an ECS world resource. When its requested from the world, a `number` ---is returned. diff --git a/lyra-scripting/scripts/lua/types/ecs/entity.lua b/lyra-scripting/scripts/lua/types/ecs/entity.lua new file mode 100644 index 0000000..52e3c00 --- /dev/null +++ b/lyra-scripting/scripts/lua/types/ecs/entity.lua @@ -0,0 +1,4 @@ +---@meta + +---@class Entity: userdata +Entity = {} \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/types/ecs/window.lua b/lyra-scripting/scripts/lua/types/ecs/window.lua index 49a1cd9..cedacb7 100644 --- a/lyra-scripting/scripts/lua/types/ecs/window.lua +++ b/lyra-scripting/scripts/lua/types/ecs/window.lua @@ -1,4 +1,4 @@ ----@class Window +---@class Window: userdata Window = { ---Gets or sets the window's focus. ---@type boolean diff --git a/lyra-scripting/scripts/lua/types/ecs/world.lua b/lyra-scripting/scripts/lua/types/ecs/world.lua new file mode 100644 index 0000000..5cd8f32 --- /dev/null +++ b/lyra-scripting/scripts/lua/types/ecs/world.lua @@ -0,0 +1,75 @@ +---@meta + +---@class World: userdata +World = {} + +---Spawn an entity with components. +--- +---@param ... userdata The components to spawn on the new entity, currently must be userdata. +---@return Entity +function World:spawn(...) end + +--- Query components from the world. +--- +--- The `system` parameter is a function with the requested components. The function +--- is ran every time for an entity. If you modify a component and want the changes to be +--- stored, return it in the function. The order of the returned components do not matter. +--- +--- Example: +--- ```lua +--- ---@type number +--- local dt = world:resource(DeltaTime) +--- +--- world:view( +--- ---@param t Transform +--- function (t) +--- -- Move the transform of the entity a bit +--- t:translate(0, 0.15 * dt, 0) +--- -- Since the transform was modified, it must be returned so +--- -- the engine can store the changes. +--- return t +--- end, +--- -- Specify the requested components here +--- Transform +--- ) +--- ``` +--- +---@param system fun(...): ... +---@param ... userdata +function World:view(system, ...) end + +---Get an ECS resource. +--- +---Returns `nil` if the resource was not found in the world. Many resources will +---return userdata, however some may return Lua types like `DeltaTime` +---returning a `number`. +--- +---Example: +---```lua +------@type number +---local dt = world:resource(DeltaTime) +--- +---print(type(dt)) --> number +---``` +--- +---@param resource userdata This shouldn't be an instance of userdata. +---@return any? +function World:resource(resource) end + +---Add a resource to the world. +--- +---If the resource already exists, it will be overwritten. +--- +---@param resource userdata +function World:add_resource(resource) end + +---Request an asset. +--- +---Assets are loaded asyncronously, so you must wait before trying to access fields on +---the asset. You can spawn an entity with it when its still loading. +--- +---Returns an asset handle to the requested resource type +--- +---@param path string +---@return Handle asset An asset handle to the requested resource type. +function World:request_asset(path) end \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/types/globals.lua b/lyra-scripting/scripts/lua/types/globals.lua new file mode 100644 index 0000000..d1ebafb --- /dev/null +++ b/lyra-scripting/scripts/lua/types/globals.lua @@ -0,0 +1,5 @@ +---@meta + +---The world global that is provided to every Lua script. +---@type World +world = nil \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/types/init.lua b/lyra-scripting/scripts/lua/types/init.lua index 35bb190..845a306 100644 --- a/lyra-scripting/scripts/lua/types/init.lua +++ b/lyra-scripting/scripts/lua/types/init.lua @@ -5,4 +5,6 @@ require "math.quat" require "math.transform" require "ecs.window" -require "ecs.delat_time" \ No newline at end of file +require "ecs.delta_time" + +require "asset.handle" \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/types/math/quat.lua b/lyra-scripting/scripts/lua/types/math/quat.lua index 0ae6a03..d60fd5f 100644 --- a/lyra-scripting/scripts/lua/types/math/quat.lua +++ b/lyra-scripting/scripts/lua/types/math/quat.lua @@ -1,6 +1,6 @@ ---@meta ----@class Quat +---@class Quat: userdata ---This is a Lua export of [`glam::Quat`](https://docs.rs/glam/latest/glam/f32/struct.Quat.html) --- ---@operator add(self): self diff --git a/lyra-scripting/scripts/lua/types/math/transform.lua b/lyra-scripting/scripts/lua/types/math/transform.lua index 0572eb8..6e4c03a 100644 --- a/lyra-scripting/scripts/lua/types/math/transform.lua +++ b/lyra-scripting/scripts/lua/types/math/transform.lua @@ -1,6 +1,6 @@ ---@meta ----@class Transform +---@class Transform: userdata --- ---A Transform represents a transformation of an object. A transform includes the position ---(called translation here), rotation, and scale. Rotation is represented using a Quaternion diff --git a/lyra-scripting/scripts/lua/types/math/vec2.lua b/lyra-scripting/scripts/lua/types/math/vec2.lua index 16fc596..f349b72 100644 --- a/lyra-scripting/scripts/lua/types/math/vec2.lua +++ b/lyra-scripting/scripts/lua/types/math/vec2.lua @@ -1,6 +1,6 @@ ---@meta ----@class Vec2 +---@class Vec2: userdata ---This is a Lua export of [`glam::Vec2`](https://docs.rs/glam/latest/glam/f32/struct.Vec2.html) --- ---@operator add(self|number): self diff --git a/lyra-scripting/scripts/lua/types/math/vec3.lua b/lyra-scripting/scripts/lua/types/math/vec3.lua index 107d4ac..6754d6d 100644 --- a/lyra-scripting/scripts/lua/types/math/vec3.lua +++ b/lyra-scripting/scripts/lua/types/math/vec3.lua @@ -1,6 +1,6 @@ ---@meta ----@class Vec3 +---@class Vec3: userdata ---This is a Lua export of [`glam::Vec3`](https://docs.rs/glam/latest/glam/f32/struct.Vec3.html) --- ---@operator add(self|number): self diff --git a/lyra-scripting/scripts/lua/types/math/vec4.lua b/lyra-scripting/scripts/lua/types/math/vec4.lua index be72e90..4f05cbf 100644 --- a/lyra-scripting/scripts/lua/types/math/vec4.lua +++ b/lyra-scripting/scripts/lua/types/math/vec4.lua @@ -1,6 +1,6 @@ ---@meta ----@class Vec4 +---@class Vec4: userdata ---This is a Lua export of [`glam::Vec4`](https://docs.rs/glam/latest/glam/f32/struct.Vec4.html) --- ---@operator add(self|number): self diff --git a/lyra-scripting/src/lua/providers/ecs.rs b/lyra-scripting/src/lua/providers/ecs.rs index b45d3a5..c136eb3 100644 --- a/lyra-scripting/src/lua/providers/ecs.rs +++ b/lyra-scripting/src/lua/providers/ecs.rs @@ -40,14 +40,18 @@ impl ScriptApiProvider for LyraEcsApiProvider { fn expose_api(&mut self, _: &ScriptData, ctx: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> { let ctx = ctx.lock().unwrap(); - // load window util + // load window enums let bytes = include_str!("../../../scripts/lua/window.lua"); ctx.load(bytes).exec().unwrap(); + // load asset handle enums + let bytes = include_str!("../../../scripts/lua/asset_handle.lua"); + ctx.load(bytes).exec().unwrap(); + let globals = ctx.globals(); globals.set("World", ctx.create_proxy::()?)?; globals.set("DynamicBundle", ctx.create_proxy::()?)?; - globals.set("SceneComponent", ctx.create_proxy::()?)?; + globals.set("SceneHandler", ctx.create_proxy::()?)?; globals.set("ActionHandler", ctx.create_proxy::()?)?; globals.set("Window", ctx.create_proxy::()?)?; diff --git a/lyra-scripting/src/lua/world.rs b/lyra-scripting/src/lua/world.rs index 1414f5a..4f75d8f 100644 --- a/lyra-scripting/src/lua/world.rs +++ b/lyra-scripting/src/lua/world.rs @@ -294,7 +294,7 @@ impl mlua::UserData for ScriptWorldPtr { Ok(()) }); - methods.add_method_mut("request_res", |lua, this, path: String| { + methods.add_method_mut("request_asset", |lua, this, path: String| { let world = this.write(); let man = world.get_resource_mut::().unwrap(); let handle = man.request_raw(&path).unwrap(); diff --git a/lyra-scripting/src/lua/wrappers/asset/mod.rs b/lyra-scripting/src/lua/wrappers/asset/mod.rs index b336c64..c3e2e5f 100644 --- a/lyra-scripting/src/lua/wrappers/asset/mod.rs +++ b/lyra-scripting/src/lua/wrappers/asset/mod.rs @@ -111,9 +111,12 @@ impl mlua::FromLua for LuaResHandle { } } +// TODO: fields wrap_lua_struct!(lyra_resource::gltf::PbrGlossiness, skip(lua_reflect)); // doesn't need internal lua reflection methods +// TODO: fields wrap_lua_struct!(lyra_resource::gltf::Specular, skip(lua_reflect)); // doesn't need internal lua reflection methods +// TODO: fields lua_wrap_handle!(SceneGraph, name=Scene, {}); lua_wrap_handle!(Mesh, diff --git a/lyra-scripting/src/lua/wrappers/input_actions.rs b/lyra-scripting/src/lua/wrappers/input_actions.rs index 7a07675..93f7925 100644 --- a/lyra-scripting/src/lua/wrappers/input_actions.rs +++ b/lyra-scripting/src/lua/wrappers/input_actions.rs @@ -127,13 +127,21 @@ impl mlua::UserData for LuaActionHandler { methods.add_method("get_action_state", |lua, this, action: String| { let state = this.handler.get_action_state(action); + // if the state wasn't found, return Nil. + if state.is_none() { + let mut v = mlua::MultiValue::new(); + v.push_back(mlua::Value::Nil); + return Ok(v); + } + + let state = state.unwrap(); let (name, val) = match state { - ActionState::Idle => ("Idle", None), - ActionState::Pressed(v) => ("Pressed", Some(v)), - ActionState::JustPressed(v) => ("JustPressed", Some(v)), - ActionState::JustReleased => ("JustReleased", None), - ActionState::Axis(v) => ("Axis", Some(v)), - ActionState::Other(v) => ("Other", Some(v)), + ActionState::Idle => ("idle", None), + ActionState::Pressed(v) => ("pressed", Some(v)), + ActionState::JustPressed(v) => ("just_pressed", Some(v)), + ActionState::JustReleased => ("just_released", None), + ActionState::Axis(v) => ("axis", Some(v)), + ActionState::Other(v) => ("other", Some(v)), }; let mut multi = Vec::new(); -- 2.40.1 From 49dfb38da3268991f3a97e01e49e1254c62af137 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sat, 5 Oct 2024 13:46:53 -0400 Subject: [PATCH 04/15] lua: expose fields on some types from lyra_resource --- .../lyra-scripting-derive/src/handle_macro.rs | 23 ++-- .../lyra-scripting-derive/src/lua_macro.rs | 42 ++++++- lyra-scripting/scripts/lua/action_handler.lua | 9 -- lyra-scripting/scripts/lua/asset_handle.lua | 12 -- lyra-scripting/scripts/lua/enums.lua | 63 ++++++++++ lyra-scripting/scripts/lua/window.lua | 27 ----- lyra-scripting/src/lua/providers/ecs.rs | 8 +- lyra-scripting/src/lua/wrappers/asset/mod.rs | 111 ++++++++++++++++-- 8 files changed, 219 insertions(+), 76 deletions(-) delete mode 100644 lyra-scripting/scripts/lua/action_handler.lua delete mode 100644 lyra-scripting/scripts/lua/asset_handle.lua create mode 100644 lyra-scripting/scripts/lua/enums.lua delete mode 100644 lyra-scripting/scripts/lua/window.lua diff --git a/lyra-scripting/lyra-scripting-derive/src/handle_macro.rs b/lyra-scripting/lyra-scripting-derive/src/handle_macro.rs index 9ee8ab2..93f581c 100644 --- a/lyra-scripting/lyra-scripting-derive/src/handle_macro.rs +++ b/lyra-scripting/lyra-scripting-derive/src/handle_macro.rs @@ -1,10 +1,10 @@ use quote::{format_ident, quote}; use syn::{braced, parenthesized, parse_macro_input, token, Ident, Path, Token}; -struct FieldGetter { - field: Ident, - body: Option, - wrapper_type: Option, +pub(crate) struct FieldGetter { + pub field: Ident, + pub body: Option, + pub wrapper_type: Option, } impl FieldGetter { @@ -131,6 +131,7 @@ impl syn::parse::Parse for HandleWrapUsage { pub(crate) fn lua_wrap_handle_impl(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as HandleWrapUsage); + let handle_path = &input.type_path; let handle_name = &input.type_path.segments.last().unwrap().ident; let base_name = input.override_name.unwrap_or_else(|| handle_name.clone()); @@ -145,7 +146,7 @@ pub(crate) fn lua_wrap_handle_impl(input: proc_macro::TokenStream) -> proc_macro let field_creator = match &g.wrapper_type { Some(wrap) => { - quote!(#wrap(data.#field).into_lua(lua)) + quote!(#wrap(data.#field.clone()).into_lua(lua)) }, None => match &g.body { Some(body) => { @@ -170,18 +171,18 @@ pub(crate) fn lua_wrap_handle_impl(input: proc_macro::TokenStream) -> proc_macro quote! { #[derive(Clone, Reflect)] - pub struct #wrapper_name(pub ResHandle<#handle_name>); + pub struct #wrapper_name(pub ResHandle<#handle_path>); impl Deref for #wrapper_name { - type Target = ResHandle<#handle_name>; + type Target = ResHandle<#handle_path>; fn deref(&self) -> &Self::Target { &self.0 } } - impl From> for #wrapper_name { - fn from(value: ResHandle<#handle_name>) -> Self { + impl From> for #wrapper_name { + fn from(value: ResHandle<#handle_path>) -> Self { #wrapper_name(value) } } @@ -224,7 +225,7 @@ pub(crate) fn lua_wrap_handle_impl(input: proc_macro::TokenStream) -> proc_macro }); methods.add_function(FN_NAME_INTERNAL_REFLECT_TYPE, |_, ()| { - Ok(ScriptBorrow::from_component::>(None)) + Ok(ScriptBorrow::from_component::>(None)) }); methods.add_method(FN_NAME_INTERNAL_REFLECT, |_, this, ()| { Ok(ScriptBorrow::from_component(Some(this.0.clone()))) @@ -247,7 +248,7 @@ pub(crate) fn lua_wrap_handle_impl(input: proc_macro::TokenStream) -> proc_macro impl LuaWrapper for #wrapper_name { fn wrapped_type_id() -> std::any::TypeId { - TypeId::of::>() + TypeId::of::>() } } }.into() diff --git a/lyra-scripting/lyra-scripting-derive/src/lua_macro.rs b/lyra-scripting/lyra-scripting-derive/src/lua_macro.rs index 4bfb0b2..0f3d87d 100644 --- a/lyra-scripting/lyra-scripting-derive/src/lua_macro.rs +++ b/lyra-scripting/lyra-scripting-derive/src/lua_macro.rs @@ -1,8 +1,8 @@ use proc_macro2::Span; use quote::quote; -use syn::{parenthesized, parse_macro_input, punctuated::Punctuated, token, Ident, Path, Token}; +use syn::{braced, parenthesized, parse_macro_input, punctuated::Punctuated, token, Ident, Path, Token}; -use crate::{FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE}; +use crate::{handle_macro::FieldGetter, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE}; #[derive(Clone, Copy, Debug, PartialEq)] enum SkipType { @@ -297,6 +297,7 @@ struct WrapUsage { /// The extra derives of the type. override_name: Option, auto_fields: Vec, + manual_field_getters: Vec, auto_derives: Vec, auto_new: bool, meta_methods: Vec, @@ -314,6 +315,7 @@ impl syn::parse::Parse for WrapUsage { override_name: None, auto_fields: vec![], auto_derives: vec![], + manual_field_getters: vec![], extra_fields: None, extra_methods: None, auto_new: false, @@ -384,6 +386,17 @@ impl syn::parse::Parse for WrapUsage { let _eq: Token![=] = input.parse()?; s.extra_methods = Some(input.parse::()?); }, + "field_getters" => { + let _eq: Token![=] = input.parse()?; + + if input.peek(token::Brace) { + let content; + let _braced: token::Brace = braced!(content in input); + + let terminated = content.parse_terminated(FieldGetter::parse, Token![,])?; + s.manual_field_getters = terminated.into_iter().collect(); + } + } _ => { return Err(syn::Error::new_spanned(ident, format!("unknown wrapper command: '{}'", ident_str))); } @@ -469,6 +482,30 @@ pub fn wrap_lua_struct_impl(input: proc_macro::TokenStream) -> proc_macro::Token } }; + let custom_getters = input.manual_field_getters.iter().map(|g| { + let field = &g.field; + + let field_creator = match &g.wrapper_type { + Some(wrap) => { + quote!(#wrap(this.#field).into_lua(lua)) + }, + None => match &g.body { + Some(body) => { + quote!(#body) + }, + None => { + quote!(this.#field.clone().into_lua(lua)) + } + } + }; + + quote! { + fields.add_field_method_get(stringify!($field), |lua, this| { + #field_creator + }); + } + }); + proc_macro::TokenStream::from(quote! { #[derive(Clone, lyra_reflect::Reflect, #(#derive_idents_iter),*)] pub struct #wrapper_typename(#[reflect(skip)] pub(crate) #path); @@ -499,6 +536,7 @@ pub fn wrap_lua_struct_impl(input: proc_macro::TokenStream) -> proc_macro::Token impl mlua::UserData for #wrapper_typename { fn add_fields>(fields: &mut F) { #(#field_get_set_pairs)* + #(#custom_getters)* #extra_fields } diff --git a/lyra-scripting/scripts/lua/action_handler.lua b/lyra-scripting/scripts/lua/action_handler.lua deleted file mode 100644 index c4c0b8f..0000000 --- a/lyra-scripting/scripts/lua/action_handler.lua +++ /dev/null @@ -1,9 +0,0 @@ ----@enum ActionState -ActionState = { - IDLE = "idle", - PRESSED = "pressed", - JUST_PRESSED = "just_pressed", - JUST_RELEASED = "just_released", - AXIS = "axis", - OTHER = "other", -} \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/asset_handle.lua b/lyra-scripting/scripts/lua/asset_handle.lua deleted file mode 100644 index 00d276e..0000000 --- a/lyra-scripting/scripts/lua/asset_handle.lua +++ /dev/null @@ -1,12 +0,0 @@ ----@enum HandleState -HandleState = { - LOADING = "loading", - READY = "ready", - ERROR = "error", -} - ----@enum ActionKind -ActionKind = { - BUTTON = "button", - AXIS = "axis", -} \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/enums.lua b/lyra-scripting/scripts/lua/enums.lua new file mode 100644 index 0000000..f22e262 --- /dev/null +++ b/lyra-scripting/scripts/lua/enums.lua @@ -0,0 +1,63 @@ +---@enum WindowMode +WindowMode = { + WNDOWED = "windowed", + BORDERLESS_FULLSCREEN = "borderless_fullscreen", + SIZED_FULLSCREEN = "sized_fullscreen", + FULLSCREEN = "fullscreen", +} + +---@enum CursorGrabMode +CursorGrabMode = { + NONE = "none", + CONFINED = "confined", + LOCKED = "locked", +} + +---@enum WindowTheme +WindowTheme = { + LIGHT = "light", + DARK = "dark", +} + +---@enum WindowLevel +WindowLevel = { + ALWAYS_ON_BOTTOM = "always_on_bottom", + NORMAL = "normal", + ALWAYS_ON_TOP = "always_on_top", +} + +---@enum HandleState +HandleState = { + LOADING = "loading", + READY = "ready", + ERROR = "error", +} + +---@enum ActionKind +ActionKind = { + BUTTON = "button", + AXIS = "axis", +} + +---@enum ActionState +ActionState = { + IDLE = "idle", + PRESSED = "pressed", + JUST_PRESSED = "just_pressed", + JUST_RELEASED = "just_released", + AXIS = "axis", + OTHER = "other", +} + +---@enum FilterMode +FilterMode = { + NEAREST = "nearest", + LINEAR = "linear", +} + +---@enum WrappingMode +WrappingMode = { + CLAMP_TO_EDGE = "clamp_to_edge", + MIRRORED_REPEAT = "mirrored_repeat", + REPEAT = "repeat", +} \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/window.lua b/lyra-scripting/scripts/lua/window.lua deleted file mode 100644 index ca5092a..0000000 --- a/lyra-scripting/scripts/lua/window.lua +++ /dev/null @@ -1,27 +0,0 @@ ----@enum WindowMode -WindowMode = { - WNDOWED = "windowed", - BORDERLESS_FULLSCREEN = "borderless_fullscreen", - SIZED_FULLSCREEN = "sized_fullscreen", - FULLSCREEN = "fullscreen", -} - ----@enum CursorGrabMode -CursorGrabMode = { - NONE = "none", - CONFINED = "confined", - LOCKED = "locked", -} - ----@enum WindowTheme -WindowTheme = { - LIGHT = "light", - DARK = "dark", -} - ----@enum WindowLevel -WindowLevel = { - ALWAYS_ON_BOTTOM = "always_on_bottom", - NORMAL = "normal", - ALWAYS_ON_TOP = "always_on_top", -} \ No newline at end of file diff --git a/lyra-scripting/src/lua/providers/ecs.rs b/lyra-scripting/src/lua/providers/ecs.rs index c136eb3..1e9e1be 100644 --- a/lyra-scripting/src/lua/providers/ecs.rs +++ b/lyra-scripting/src/lua/providers/ecs.rs @@ -40,12 +40,8 @@ impl ScriptApiProvider for LyraEcsApiProvider { fn expose_api(&mut self, _: &ScriptData, ctx: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> { let ctx = ctx.lock().unwrap(); - // load window enums - let bytes = include_str!("../../../scripts/lua/window.lua"); - ctx.load(bytes).exec().unwrap(); - - // load asset handle enums - let bytes = include_str!("../../../scripts/lua/asset_handle.lua"); + // load enums + let bytes = include_str!("../../../scripts/lua/enums.lua"); ctx.load(bytes).exec().unwrap(); let globals = ctx.globals(); diff --git a/lyra-scripting/src/lua/wrappers/asset/mod.rs b/lyra-scripting/src/lua/wrappers/asset/mod.rs index c3e2e5f..ec0a596 100644 --- a/lyra-scripting/src/lua/wrappers/asset/mod.rs +++ b/lyra-scripting/src/lua/wrappers/asset/mod.rs @@ -1,6 +1,6 @@ use std::{any::TypeId, ops::Deref}; //use mlua::{AnyUserData, IntoLua, FromLua, Lua, Value}; -use lyra_resource::{gltf::{Gltf, Material, Mesh}, Texture, ResHandle, UntypedResHandle}; +use lyra_resource::{gltf::{Gltf, Material, Mesh}, FilterMode, ResHandle, Texture, UntypedResHandle, WrappingMode}; use lyra_game::scene::SceneGraph; use lyra_reflect::{Reflect, TypeData}; use lyra_scripting_derive::{lua_wrap_handle, wrap_lua_struct}; @@ -111,15 +111,87 @@ impl mlua::FromLua for LuaResHandle { } } -// TODO: fields -wrap_lua_struct!(lyra_resource::gltf::PbrGlossiness, skip(lua_reflect)); // doesn't need internal lua reflection methods -// TODO: fields -wrap_lua_struct!(lyra_resource::gltf::Specular, skip(lua_reflect)); // doesn't need internal lua reflection methods +fn filter_mode_to_str(fm: FilterMode) -> &'static str { + match fm { + FilterMode::Nearest => "nearest", + FilterMode::Linear => "linear", + } +} + +fn wrapping_mode_to_str(wm: WrappingMode) -> &'static str { + match wm { + WrappingMode::ClampToEdge => "clamp_to_edge", + WrappingMode::MirroredRepeat => "mirrored_repeat", + WrappingMode::Repeat => "repeat", + } +} + +wrap_lua_struct!(lyra_resource::TextureSampler, + // this can be safely skipped since it wont be a component or resource. + skip(lua_reflect), + field_getters={ + (mag_filter, { + this.mag_filter.map(|f| filter_mode_to_str(f)) + .into_lua(lua) + }), + (min_filter, { + this.min_filter.map(|f| filter_mode_to_str(f)) + .into_lua(lua) + }), + (mipmap_filter, { + this.mipmap_filter.map(|f| filter_mode_to_str(f)) + .into_lua(lua) + }), + (wrap_u, { + wrapping_mode_to_str(this.wrap_u) + .into_lua(lua) + }), + (wrap_v, { + wrapping_mode_to_str(this.wrap_v) + .into_lua(lua) + }), + (wrap_w, { + wrapping_mode_to_str(this.wrap_w) + .into_lua(lua) + }), + } +); + +wrap_lua_struct!(lyra_resource::gltf::PbrGlossiness, + // this can be safely skipped since it wont be a component or resource. + skip(lua_reflect), + field_getters={ + (diffuse_color, wrapper=crate::lua::wrappers::LuaVec4), + (specular, wrapper=crate::lua::wrappers::LuaVec3), + glossiness, + } +); + +wrap_lua_struct!(lyra_resource::gltf::Specular, + // this can be safely skipped since it wont be a component or resource. + skip(lua_reflect), + field_getters={ + factor, + (color_factor, wrapper=crate::lua::wrappers::LuaVec3), + }, + extra_fields={ + fields.add_field_method_get("texture", |lua, this| { + this.texture.clone() + .map(|t| LuaTextureHandle(t)) + .into_lua(lua) + }); + fields.add_field_method_get("color_texture", |lua, this| { + this.color_texture.clone() + .map(|t| LuaTextureHandle(t)) + .into_lua(lua) + }); + } +); // TODO: fields lua_wrap_handle!(SceneGraph, name=Scene, {}); -lua_wrap_handle!(Mesh, +lua_wrap_handle!(Mesh, field_getters={ (material, { data.material.clone() @@ -154,10 +226,32 @@ lua_wrap_handle!(Mesh, } }); - // TODO: attributes + // TODO: mesh attributes } ); -lua_wrap_handle!(Texture, {}); + +lua_wrap_handle!(lyra_resource::Image, + field_getters={ + (width, { + data.width().into_lua(lua) + }), + (height, { + data.height().into_lua(lua) + }) + } +); + +lua_wrap_handle!(Texture, + field_getters={ + (image, wrapper=LuaImageHandle), + (sampler, { + data.sampler.clone() + .map(|s| LuaTextureSampler(s)) + .into_lua(lua) + }) + } + +); lua_wrap_handle!(Material, field_getters={ @@ -185,7 +279,6 @@ lua_wrap_handle!(Material, alpha_cutoff, (alpha_mode, { match data.alpha_mode { - // TODO: Lua enums lyra_resource::gltf::AlphaMode::Opaque => "opaque", lyra_resource::gltf::AlphaMode::Mask => "mask", lyra_resource::gltf::AlphaMode::Blend => "blend", -- 2.40.1 From e9cbb4865323415f2ccf672553d5640f15069c88 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Mon, 7 Oct 2024 15:20:13 -0400 Subject: [PATCH 05/15] lua: expose camera, support ToLua and FromLua structs in World:view --- examples/lua-scripting/scripts/test.lua | 13 +- lyra-game/src/render/camera.rs | 3 +- lyra-game/src/scene/camera.rs | 3 +- lyra-reflect/src/impls/impl_math.rs | 131 +++++++- .../lyra-scripting-derive/src/field.rs | 143 ++++++++ .../lyra-scripting-derive/src/handle_macro.rs | 6 + .../lyra-scripting-derive/src/lib.rs | 8 + .../lyra-scripting-derive/src/lua_macro.rs | 312 ++++++++++------- .../lyra-scripting-derive/src/to_lua_macro.rs | 317 ++++++++++++++++++ lyra-scripting/scripts/lua/enums.lua | 6 + .../scripts/lua/types/ecs/camera.lua | 18 + .../scripts/lua/types/math/angle.lua | 25 ++ lyra-scripting/src/lua/mod.rs | 17 + lyra-scripting/src/lua/providers/ecs.rs | 35 +- lyra-scripting/src/lua/providers/math.rs | 4 +- lyra-scripting/src/lua/proxy.rs | 3 + lyra-scripting/src/lua/wrappers/asset/mod.rs | 39 ++- lyra-scripting/src/lua/wrappers/camera.rs | 46 +++ lyra-scripting/src/lua/wrappers/delta_time.rs | 8 + .../src/lua/wrappers/input_actions.rs | 6 + lyra-scripting/src/lua/wrappers/math.rs | 31 +- lyra-scripting/src/lua/wrappers/mod.rs | 5 +- lyra-scripting/src/lua/wrappers/window.rs | 5 +- 23 files changed, 1021 insertions(+), 163 deletions(-) create mode 100644 lyra-scripting/lyra-scripting-derive/src/field.rs create mode 100644 lyra-scripting/lyra-scripting-derive/src/to_lua_macro.rs create mode 100644 lyra-scripting/scripts/lua/types/ecs/camera.lua create mode 100644 lyra-scripting/scripts/lua/types/math/angle.lua create mode 100644 lyra-scripting/src/lua/wrappers/camera.rs diff --git a/examples/lua-scripting/scripts/test.lua b/examples/lua-scripting/scripts/test.lua index 4b4c9d6..717ea6e 100644 --- a/examples/lua-scripting/scripts/test.lua +++ b/examples/lua-scripting/scripts/test.lua @@ -69,12 +69,23 @@ function on_update() ---@type number local dt = world:resource(DeltaTime) - world:view( + --[[ world:view( ---@param t Transform function (t) t:translate(0, 0.15 * dt, 0) return t end, Transform + ) ]] + + world:view( + ---@param c Camera + function (c) + c.transform:translate(0, 0.15 * dt, 0) + + print("Moving camera to: " .. tostring(c.transform)) + + return c + end, Camera ) end diff --git a/lyra-game/src/render/camera.rs b/lyra-game/src/render/camera.rs index 3c12dd4..67a96ee 100755 --- a/lyra-game/src/render/camera.rs +++ b/lyra-game/src/render/camera.rs @@ -1,8 +1,9 @@ +use lyra_reflect::Reflect; use winit::dpi::PhysicalSize; use crate::{math::{Angle, OPENGL_TO_WGPU_MATRIX}, scene::CameraComponent}; -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Reflect)] pub enum CameraProjectionMode { /// 3d camera projection Perspective, diff --git a/lyra-game/src/scene/camera.rs b/lyra-game/src/scene/camera.rs index 0808699..4ede942 100755 --- a/lyra-game/src/scene/camera.rs +++ b/lyra-game/src/scene/camera.rs @@ -1,8 +1,9 @@ use lyra_ecs::Component; +use lyra_reflect::Reflect; use crate::{math::{Angle, Transform}, render::camera::CameraProjectionMode}; -#[derive(Clone, Component)] +#[derive(Clone, Component, Reflect)] pub struct CameraComponent { pub transform: Transform, pub fov: Angle, diff --git a/lyra-reflect/src/impls/impl_math.rs b/lyra-reflect/src/impls/impl_math.rs index 2381203..b317246 100644 --- a/lyra-reflect/src/impls/impl_math.rs +++ b/lyra-reflect/src/impls/impl_math.rs @@ -1,6 +1,7 @@ +use lyra_math::Angle; use lyra_reflect_derive::{impl_reflect_simple_struct, impl_reflect_trait_value}; -use crate::{lyra_engine, Method, Reflect}; +use crate::{lyra_engine, Enum, Method, Reflect, ReflectMut, ReflectRef}; impl_reflect_simple_struct!(lyra_math::Vec2, fields(x = f32, y = f32)); impl_reflect_simple_struct!(lyra_math::Vec3, fields(x = f32, y = f32, z = f32)); @@ -17,3 +18,131 @@ impl_reflect_simple_struct!( ); impl_reflect_trait_value!(lyra_math::Mat4); + +impl Reflect for Angle { + fn name(&self) -> String { + "Angle".into() + } + + fn type_id(&self) -> std::any::TypeId { + std::any::TypeId::of::() + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + self + } + + fn as_boxed_any(self: Box) -> Box { + self + } + + fn apply(&mut self, val: &dyn Reflect) { + if let ReflectRef::Enum(e) = val.reflect_ref() { + let s = e.as_any().downcast_ref::() + .expect("cannot apply mismatched reflected enum"); + *self = *s; + } else { + panic!("Provided value was not an enum!"); + } + } + + fn clone_inner(&self) -> Box { + Box::new(self.clone()) + } + + fn reflect_ref(&self) -> crate::ReflectRef { + ReflectRef::Enum(self) + } + + fn reflect_mut(&mut self) -> crate::ReflectMut { + ReflectMut::Enum(self) + } + + fn reflect_val(&self) -> &dyn Reflect { + self + } + + fn reflect_val_mut(&mut self) -> &mut dyn Reflect { + self + } +} + +impl Enum for Angle { + fn field(&self, _: &str) -> Option<&dyn Reflect> { + // no struct variants + None + } + + fn field_mut(&mut self, _: &str) -> Option<&mut dyn Reflect> { + // no struct variants + None + } + + fn field_at(&self, idx: usize) -> Option<&dyn Reflect> { + // all variants only have one tuple field + if idx != 0 { + return None; + } + + match self { + Angle::Degrees(v) => Some(v), + Angle::Radians(v) => Some(v), + } + } + + fn field_at_mut(&mut self, idx: usize) -> Option<&mut dyn Reflect> { + // all variants only have one tuple field + if idx != 0 { + return None; + } + + match self { + Angle::Degrees(v) => Some(v), + Angle::Radians(v) => Some(v), + } + } + + fn field_name_at(&self, _: usize) -> Option { + // no struct variants + None + } + + fn has_field(&self, _: &str) -> bool { + // no struct variants + false + } + + fn fields_len(&self) -> usize { + 1 + } + + fn variants_len(&self) -> usize { + 2 + } + + fn variant_name(&self) -> String { + match self { + Angle::Degrees(_) => "degrees".into(), + Angle::Radians(_) => "radians".into(), + } + } + + fn variant_index(&self) -> usize { + match self { + Angle::Degrees(_) => 0, + Angle::Radians(_) => 1, + } + } + + fn is_variant_name(&self, name: &str) -> bool { + self.variant_name() == name + } + + fn variant_type(&self) -> crate::EnumType { + crate::EnumType::Tuple + } +} \ No newline at end of file diff --git a/lyra-scripting/lyra-scripting-derive/src/field.rs b/lyra-scripting/lyra-scripting-derive/src/field.rs new file mode 100644 index 0000000..a80d00f --- /dev/null +++ b/lyra-scripting/lyra-scripting-derive/src/field.rs @@ -0,0 +1,143 @@ +use syn::{parenthesized, token, Token}; + +pub(crate) enum FieldType { + Unknown, + Type(syn::Path), + Wrapped(syn::Path), +} + +impl FieldType { + pub fn is_unknown(&self) -> bool { + matches!(self, FieldType::Unknown) + } + + pub fn is_wrapped(&self) -> bool { + matches!(self, FieldType::Wrapped(_)) + } + + pub fn get_type_path(&self) -> Option<&syn::Path> { + match self { + FieldType::Unknown => None, + FieldType::Type(path) => Some(path), + FieldType::Wrapped(path) => Some(path), + } + } +} + +pub(crate) struct Field { + pub field: syn::Ident, + pub field_ty: FieldType, + pub skip_setter: bool, + pub setter: Option, + pub getter: Option, +} + +impl Field { + fn parse_extended(input: syn::parse::ParseStream) -> syn::Result { + let field_name = input.parse()?; + + let fty = if input.peek(Token![:]) { + let _col: Token![:] = input.parse()?; + let s: syn::Path = input.parse()?; + let mut fty = FieldType::Type(s.clone()); + + if let Some(ident) = s.get_ident() { + if ident.to_string() == "wrap" { + let content; + let _parens: token::Paren = parenthesized!(content in input); + fty = FieldType::Wrapped(content.parse()?); + } + } + + fty + } else { + FieldType::Unknown + }; + + let mut s = Self { + field: field_name, + field_ty: fty, + skip_setter: false, + setter: None, + getter: None, + }; + + while input.peek(Token![,]) { + let _: Token![,] = input.parse()?; + + if input.peek(syn::Ident) { + let ident: syn::Ident = input.parse()?; + let ident_str = ident.to_string(); + let ident_str = ident_str.as_str(); + + match ident_str { + "skip_set" => { + s.skip_setter = true; + } + "set" => { + let _eq: Token![=] = input.parse()?; + + s.setter = Some(input.parse()?); + } + "get" => { + let _eq: Token![=] = input.parse()?; + + s.getter = Some(input.parse()?); + } + _ => { + return Err(syn::Error::new_spanned(ident, "unknown wrapper command")); + } + } + } + } + + if (s.getter.is_some() || s.setter.is_some()) && s.field_ty.is_wrapped() { + return Err(syn::Error::new( + input.span(), + "cannot specify custom getter or setter \ + with wrapped type", + )); + } + + Ok(s) + } +} + +impl syn::parse::Parse for Field { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + if input.peek(token::Paren) { + let content; + let _parens: token::Paren = parenthesized!(content in input); + + Self::parse_extended(&content) + } else { + let field_name = input.parse()?; + + let fty = if input.peek(Token![:]) { + let _col: Token![:] = input.parse()?; + let s: syn::Path = input.parse()?; + let mut fty = FieldType::Type(s.clone()); + + if let Some(ident) = s.get_ident() { + if ident.to_string() == "wrap" { + let content; + let _parens: token::Paren = parenthesized!(content in input); + fty = FieldType::Wrapped(content.parse()?); + } + } + + fty + } else { + FieldType::Unknown + }; + + Ok(Self { + field: field_name, + field_ty: fty, + skip_setter: false, + setter: None, + getter: None, + }) + } + } +} \ No newline at end of file diff --git a/lyra-scripting/lyra-scripting-derive/src/handle_macro.rs b/lyra-scripting/lyra-scripting-derive/src/handle_macro.rs index 93f581c..cb2b4fa 100644 --- a/lyra-scripting/lyra-scripting-derive/src/handle_macro.rs +++ b/lyra-scripting/lyra-scripting-derive/src/handle_macro.rs @@ -247,9 +247,15 @@ pub(crate) fn lua_wrap_handle_impl(input: proc_macro::TokenStream) -> proc_macro } impl LuaWrapper for #wrapper_name { + type Wrap = ResHandle<#handle_path>; + fn wrapped_type_id() -> std::any::TypeId { TypeId::of::>() } + + fn into_wrapped(self) -> Self::Wrap { + self.0 + } } }.into() } \ No newline at end of file diff --git a/lyra-scripting/lyra-scripting-derive/src/lib.rs b/lyra-scripting/lyra-scripting-derive/src/lib.rs index c9a4185..965ccdd 100644 --- a/lyra-scripting/lyra-scripting-derive/src/lib.rs +++ b/lyra-scripting/lyra-scripting-derive/src/lib.rs @@ -5,9 +5,12 @@ use syn::{parse_macro_input, Token}; mod mat_wrapper; mod vec_wrapper; +use to_lua_macro::to_lua_struct_impl; use vec_wrapper::VecWrapper; mod lua_macro; +mod to_lua_macro; +mod field; mod handle_macro; @@ -24,6 +27,11 @@ pub fn lua_wrap_handle(input: proc_macro::TokenStream) -> proc_macro::TokenStrea lua_wrap_handle_impl(input) } +#[proc_macro] +pub fn to_lua_convert(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + to_lua_struct_impl(input) +} + pub(crate) struct VecExtensionInputs { #[allow(dead_code)] pub type_path: syn::Path, diff --git a/lyra-scripting/lyra-scripting-derive/src/lua_macro.rs b/lyra-scripting/lyra-scripting-derive/src/lua_macro.rs index 0f3d87d..e681bd5 100644 --- a/lyra-scripting/lyra-scripting-derive/src/lua_macro.rs +++ b/lyra-scripting/lyra-scripting-derive/src/lua_macro.rs @@ -1,13 +1,15 @@ use proc_macro2::Span; use quote::quote; -use syn::{braced, parenthesized, parse_macro_input, punctuated::Punctuated, token, Ident, Path, Token}; +use syn::{ + braced, parenthesized, parse_macro_input, punctuated::Punctuated, token, Ident, Path, Token, +}; -use crate::{handle_macro::FieldGetter, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE}; +use crate::{field::{Field, FieldType}, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE}; #[derive(Clone, Copy, Debug, PartialEq)] enum SkipType { /// Skips implementing - LuaReflect + LuaReflect, } impl syn::parse::Parse for SkipType { @@ -17,9 +19,7 @@ impl syn::parse::Parse for SkipType { match name_str.as_str() { "lua_reflect" => Ok(Self::LuaReflect), - _ => { - Err(syn::Error::new_spanned(name, "unknown skip type")) - } + _ => Err(syn::Error::new_spanned(name, "unknown skip type")), } } } @@ -43,22 +43,21 @@ impl MetaMethod { let mm_str = mm_str.as_str(); match mm_str { "Add" | "Sub" | "Div" | "Mul" | "Mod" | "Eq" | "Shl" | "Shr" | "BAnd" | "BOr" - | "BXor" => { - true - }, - "Unm" | "BNot" | "ToString" => { - false - }, + | "BXor" => true, + "Unm" | "BNot" | "ToString" => false, _ => todo!(), } } /// returns the tokens of the body of the metamethod - /// + /// /// Parameters /// * `metamethod` - The ident of the metamethod that is being implemented. /// * `other` - The tokens of the argument used in the metamethod. - fn get_method_body(metamethod: &Ident, other: proc_macro2::TokenStream) -> proc_macro2::TokenStream { + fn get_method_body( + metamethod: &Ident, + other: proc_macro2::TokenStream, + ) -> proc_macro2::TokenStream { let mm_str = metamethod.to_string(); let mm_str = mm_str.as_str(); match mm_str { @@ -75,17 +74,17 @@ impl MetaMethod { quote! { Ok(Self(this.0 #symbol #other)) } - }, + } "Unm" => { quote! { Ok(Self(-this.0)) } - }, + } "Eq" => { quote! { Ok(this.0 == #other) } - }, + } "Shl" => { quote! { Ok(Self(this.0 << #other)) @@ -95,41 +94,47 @@ impl MetaMethod { quote! { Ok(Self(this.0 >> #other)) } - }, + } "BAnd" | "BOr" | "BXor" => { let symbol = match mm_str { "BAnd" => { quote!(&) - }, + } "BOr" => { quote!(|) - }, + } "BXor" => { quote!(^) - }, - _ => unreachable!() // the string was just checked to be one of these + } + _ => unreachable!(), // the string was just checked to be one of these }; quote! { Ok(Self(this.0 #symbol #other)) } - }, + } "BNot" => { quote! { Ok(Self(!this.0)) } - }, + } "ToString" => { quote! { Ok(format!("{:?}", this.0)) } - }, - _ => syn::Error::new_spanned(metamethod, - "unsupported auto implementation of metamethod").to_compile_error(), + } + _ => { + syn::Error::new_spanned(metamethod, "unsupported auto implementation of metamethod") + .to_compile_error() + } } } - fn get_body_for_arg(mt_ident: &Ident, arg_ident: &Ident, arg_param: proc_macro2::TokenStream) -> proc_macro2::TokenStream { + fn get_body_for_arg( + mt_ident: &Ident, + arg_ident: &Ident, + arg_param: proc_macro2::TokenStream, + ) -> proc_macro2::TokenStream { let other: proc_macro2::TokenStream = if Self::is_arg_wrapper(arg_ident) { // Lua wrappers must be dereferenced quote! { @@ -167,7 +172,6 @@ impl MetaMethod { }); } } - } else if self.arg.len() == 1 { let first = self.arg.iter().next().unwrap(); let body = Self::get_body_for_arg(&self.name, first, quote!(v)); @@ -184,38 +188,41 @@ impl MetaMethod { let is = i.to_string(); let is = is.as_str(); match is { - "u8" | "u16" | "u32" | "u64" | "u128" - | "i8" | "i16" | "i32" | "i64" | "i128" - | "f32" | "f64" => true, + "u8" | "u16" | "u32" | "u64" | "u128" | "i8" | "i16" | "i32" | "i64" + | "i128" | "f32" | "f64" => true, _ => false, } }); if let Some(num_ident) = num_ident { - let body = Self::get_body_for_arg(&self.name, num_ident, quote!(n as #num_ident)); + let body = + Self::get_body_for_arg(&self.name, num_ident, quote!(n as #num_ident)); quote! { mlua::Value::Number(n) => { #body }, } - } else { quote!() } + } else { + quote!() + } }; let userdata_arm = { - let wrappers: Vec<&Ident> = self.arg.iter() + let wrappers: Vec<&Ident> = self + .arg + .iter() .filter(|i| Self::is_arg_wrapper(i)) .collect(); let if_statements = wrappers.iter().map(|i| { let body = Self::get_method_body(&self.name, quote!(other.0)); - + quote! { if let Ok(other) = ud.borrow::<#i>() { #body } } - }); quote! { @@ -226,7 +233,7 @@ impl MetaMethod { // try to get the name of the userdata for the error message if let Ok(mt) = ud.metatable() { if let Ok(name) = mt.get::("__name") { - return Err(mlua::Error::BadArgument { + return Err(mlua::Error::BadArgument { to: Some(format!("{}.__{}", #wrapped_str, #mt_lua_name)), pos: 2, name: Some("rhs".to_string()), @@ -236,8 +243,8 @@ impl MetaMethod { }); } } - - Err(mlua::Error::BadArgument { + + Err(mlua::Error::BadArgument { to: Some(format!("{}.__{}", #wrapped_str, #mt_lua_name)), pos: 2, name: Some("rhs".to_string()), @@ -249,13 +256,13 @@ impl MetaMethod { }, } }; - + quote! { methods.add_meta_method(mlua::MetaMethod::#mt_ident, |_, this, (v,): (mlua::Value,)| { match v { #number_arm #userdata_arm - _ => Err(mlua::Error::BadArgument { + _ => Err(mlua::Error::BadArgument { to: Some(format!("{}.__{}", #wrapped_str, #mt_lua_name)), pos: 2, name: Some("rhs".to_string()), @@ -274,17 +281,15 @@ impl syn::parse::Parse for MetaMethod { fn parse(input: syn::parse::ParseStream) -> syn::Result { let name: Ident = input.parse()?; - let mut s = Self { - name, - arg: vec![], - }; + let mut s = Self { name, arg: vec![] }; // try to parse args if input.peek(syn::token::Paren) { let content; let _parens: syn::token::Paren = parenthesized!(content in input); - let arg: Punctuated = content.parse_terminated(Ident::parse, Token![,])?; + let arg: Punctuated = + content.parse_terminated(Ident::parse, Token![,])?; s.arg = arg.into_iter().collect(); // convert to Vec } @@ -296,8 +301,7 @@ struct WrapUsage { type_path: syn::Path, /// The extra derives of the type. override_name: Option, - auto_fields: Vec, - manual_field_getters: Vec, + auto_fields: Vec, auto_derives: Vec, auto_new: bool, meta_methods: Vec, @@ -315,7 +319,6 @@ impl syn::parse::Parse for WrapUsage { override_name: None, auto_fields: vec![], auto_derives: vec![], - manual_field_getters: vec![], extra_fields: None, extra_methods: None, auto_new: false, @@ -324,7 +327,7 @@ impl syn::parse::Parse for WrapUsage { }; let mut new_ident = None; - + while input.peek(Token![,]) { let _: Token![,] = input.parse()?; @@ -339,73 +342,72 @@ impl syn::parse::Parse for WrapUsage { let name: Ident = input.parse()?; s.override_name = Some(name); - }, + } "new" => { s.auto_new = true; new_ident = Some(ident.clone()); - }, + } "skip" => { let content; let _parens: token::Paren = parenthesized!(content in input); let terminated = content.parse_terminated(SkipType::parse, Token![,])?; s.skips = terminated.into_iter().collect(); - }, + } "derives" => { if input.peek(token::Paren) { let content; let _parens: token::Paren = parenthesized!(content in input); - - let derives: Punctuated = content.parse_terminated(Ident::parse, Token![,])?; + + let derives: Punctuated = + content.parse_terminated(Ident::parse, Token![,])?; s.auto_derives = derives.into_iter().collect(); } - }, + } "fields" => { - if input.peek(token::Paren) { - let content; - let _parens: token::Paren = parenthesized!(content in input); - - let fields: Punctuated = content.parse_terminated(Ident::parse, Token![,])?; - s.auto_fields = fields.into_iter().collect(); - } - }, - "metamethods" => { - if input.peek(token::Paren) { - let content; - let _bracket: token::Paren = parenthesized!(content in input); - - let meta_methods: Punctuated = content.parse_terminated(MetaMethod::parse, Token![,])?; - s.meta_methods = meta_methods.into_iter().collect(); - } - }, - "extra_fields" => { - let _eq: Token![=] = input.parse()?; - s.extra_fields = Some(input.parse::()?); - }, - "extra_methods" => { - let _eq: Token![=] = input.parse()?; - s.extra_methods = Some(input.parse::()?); - }, - "field_getters" => { let _eq: Token![=] = input.parse()?; if input.peek(token::Brace) { let content; let _braced: token::Brace = braced!(content in input); - - let terminated = content.parse_terminated(FieldGetter::parse, Token![,])?; - s.manual_field_getters = terminated.into_iter().collect(); + + let terminated = content.parse_terminated(Field::parse, Token![,])?; + s.auto_fields.extend(terminated.into_iter()); } } + "metamethods" => { + if input.peek(token::Paren) { + let content; + let _bracket: token::Paren = parenthesized!(content in input); + + let meta_methods: Punctuated = + content.parse_terminated(MetaMethod::parse, Token![,])?; + s.meta_methods = meta_methods.into_iter().collect(); + } + } + "extra_fields" => { + let _eq: Token![=] = input.parse()?; + s.extra_fields = Some(input.parse::()?); + } + "extra_methods" => { + let _eq: Token![=] = input.parse()?; + s.extra_methods = Some(input.parse::()?); + } _ => { - return Err(syn::Error::new_spanned(ident, format!("unknown wrapper command: '{}'", ident_str))); + return Err(syn::Error::new_spanned( + ident, + format!("unknown wrapper command: '{}'", ident_str), + )); } } } } if s.auto_new && s.auto_fields.is_empty() { - return Err(syn::Error::new_spanned(new_ident.unwrap(), "must specify 'fields' when auto creating new function")); + return Err(syn::Error::new_spanned( + new_ident.unwrap(), + "must specify 'fields' when auto creating new function", + )); } Ok(s) @@ -417,51 +419,50 @@ pub fn wrap_lua_struct_impl(input: proc_macro::TokenStream) -> proc_macro::Token let input = parse_macro_input!(input as WrapUsage); let path: Path = input.type_path; - let type_name = &path.segments.last() + let type_name = &path + .segments + .last() .expect("Failure to find typename in macro usage!") .ident; - let wrapper_typename = input.override_name + let wrapper_typename = input + .override_name .unwrap_or_else(|| Ident::new(&format!("Lua{}", type_name), Span::call_site())); let derive_idents_iter = input.auto_derives.iter(); let extra_fields = input.extra_fields; let extra_methods = input.extra_methods; - let field_get_set_pairs = input.auto_fields.iter().map(|i| { - let is = i.to_string(); - quote! { - fields.add_field_method_get(#is, |_, this| { - Ok(this.#i) - }); - fields.add_field_method_set(#is, |_, this, #i| { - this.#i = #i; - Ok(()) - }); - } - }); - // the tokens for the new function let new_func_tokens = if input.auto_new { - let arg_names = input.auto_fields.iter().map(|i| { - Ident::new(&i.to_string().to_lowercase(), Span::call_site()) - }); + let arg_names = input + .auto_fields + .iter() + .map(|i| Ident::new(&i.field.to_string().to_lowercase(), Span::call_site())); + + let arg_types = input + .auto_fields + .iter() + .map(|i| i.field_ty.get_type_path().unwrap()); let arg_names_clone = arg_names.clone(); + let arg_types_clone = arg_types.clone(); quote! { // arguments for function are not specified since they can be implied from the call // to new(...) - methods.add_function("new", |_, ( #(#arg_names_clone),* )| { + methods.add_function("new", |_, ( #(#arg_names_clone),* ): ( #(#arg_types_clone),* ) | { Ok(#wrapper_typename(#path::new( #(#arg_names),* ))) }); } - - } else { quote!() }; + } else { + quote!() + }; let meta_methods_tokens = { - let method_tokens = input.meta_methods.iter().map(|mm| { - mm.to_tokens(&wrapper_typename) - }); + let method_tokens = input + .meta_methods + .iter() + .map(|mm| mm.to_tokens(&wrapper_typename)); quote! { #(#method_tokens)* @@ -475,34 +476,80 @@ pub fn wrap_lua_struct_impl(input: proc_macro::TokenStream) -> proc_macro::Token methods.add_method(#FN_NAME_INTERNAL_REFLECT, |_, this, ()| { Ok(crate::ScriptBorrow::from_component::<#path>(Some(this.0.clone()))) }); - + methods.add_function(#FN_NAME_INTERNAL_REFLECT_TYPE, |_, ()| { Ok(crate::ScriptBorrow::from_component::<#path>(None)) }); } }; - let custom_getters = input.manual_field_getters.iter().map(|g| { + for field in input.auto_fields.iter() { + if field.field_ty.is_unknown() && !field.skip_setter { + return syn::Error::new( + field.field.span(), + "missing type of field, must be specified to generate setters", + ) + .to_compile_error() + .into(); + } + } + + let fields = input.auto_fields.iter().map(|g| { let field = &g.field; - - let field_creator = match &g.wrapper_type { - Some(wrap) => { - quote!(#wrap(this.#field).into_lua(lua)) - }, - None => match &g.body { + + let field_getter = match &g.field_ty { + FieldType::Wrapped(wrap) => { + quote!(#wrap(this.#field.clone()).into_lua(lua)) + } + _ => match &g.getter { Some(body) => { quote!(#body) - }, + } None => { quote!(this.#field.clone().into_lua(lua)) } } }; + let field_setter = if g.skip_setter { + quote! {} + } else { + let fty = g + .field_ty + .get_type_path() + // should be unreachable due to the checks right before this closure + .expect("no field type specified"); + let s = if g.field_ty.is_wrapped() { + quote! { + this.#field = #field.0.clone(); + Ok(()) + } + } else { + match &g.setter { + Some(body) => { + quote!(#body) + } + None => { + quote! { + this.#field = #field.clone(); + Ok(()) + } + } + } + }; + + quote! { + fields.add_field_method_set(stringify!(#field), |_, this, #field: #fty| { + #s + }); + } + }; + quote! { - fields.add_field_method_get(stringify!($field), |lua, this| { - #field_creator + fields.add_field_method_get(stringify!(#field), |lua, this| { + #field_getter }); + #field_setter } }); @@ -512,12 +559,12 @@ pub fn wrap_lua_struct_impl(input: proc_macro::TokenStream) -> proc_macro::Token impl std::ops::Deref for #wrapper_typename { type Target = #path; - + fn deref(&self) -> &Self::Target { &self.0 } } - + impl std::ops::DerefMut for #wrapper_typename { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 @@ -535,13 +582,16 @@ pub fn wrap_lua_struct_impl(input: proc_macro::TokenStream) -> proc_macro::Token impl mlua::UserData for #wrapper_typename { fn add_fields>(fields: &mut F) { - #(#field_get_set_pairs)* - #(#custom_getters)* + use mlua::IntoLua; + + #(#fields)* #extra_fields } fn add_methods>(methods: &mut M) { + use mlua::IntoLua; + #lua_reflects #new_func_tokens @@ -551,9 +601,15 @@ pub fn wrap_lua_struct_impl(input: proc_macro::TokenStream) -> proc_macro::Token } impl lyra_scripting::lua::LuaWrapper for #wrapper_typename { + type Wrap = #path; + fn wrapped_type_id() -> std::any::TypeId { std::any::TypeId::of::<#path>() } + + fn into_wrapped(self) -> Self::Wrap { + self.0 + } } }) -} \ No newline at end of file +} diff --git a/lyra-scripting/lyra-scripting-derive/src/to_lua_macro.rs b/lyra-scripting/lyra-scripting-derive/src/to_lua_macro.rs new file mode 100644 index 0000000..d416887 --- /dev/null +++ b/lyra-scripting/lyra-scripting-derive/src/to_lua_macro.rs @@ -0,0 +1,317 @@ +use syn::{braced, parenthesized, parse_macro_input, punctuated::Punctuated, token, Token}; +use quote::{quote, ToTokens}; +use crate::{field::Field, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE}; + +fn field_table_setter(field: &Field) -> proc_macro2::TokenStream { + let ident = &field.field; + + match &field.setter { + Some(set) => { + quote! { + table.set(stringify!(#ident), #set)?; + } + }, + None => { + let ty = field.field_ty.get_type_path() + .expect("no field type specified"); + + let arg = if field.field_ty.is_wrapped() { + quote!(#ty(self.#ident)) + } else { quote!(self.#ident) }; + + quote! { + table.set(stringify!(#ident), #arg)?; + } + }, + } +} + +fn field_table_getter(field: &Field) -> proc_macro2::TokenStream { + let ident = &field.field; + + match &field.getter { + Some(get) => { + quote! { + let #ident = #get; + } + }, + None => { + let ty = field.field_ty.get_type_path() + .expect("no field type specified"); + + quote! { + let #ident: #ty = table.get(stringify!(#ident))?; + } + }, + } +} + +fn wrapper_creation(wrapper: &syn::Ident, type_path: &syn::Path, create: Option<&syn::Block>, fields: &Vec) -> proc_macro2::TokenStream { + + match create { + Some(b) => quote!(#b), + None => { + /* let field_iter = fields.iter().map(|f| match &f.field_ty { + crate::field::FieldType::Type(path) => quote!(#path), + crate::field::FieldType::Wrapped(path) => quote!(*#path), + _ => todo!() + }); */ + let field_iter = fields.iter().map(|f| { + let ident = &f.field; + if f.field_ty.is_wrapped() { + quote!(#ident: (*#ident).clone()) + } else { + quote!(#ident) + } + }); + + quote! { + #wrapper(#type_path { + #( + #field_iter + ),* + }) + } + } + } + +} + +fn get_reflect_lua_functions(ty: &ReflectType, type_path: &syn::Path, set_data: bool) -> proc_macro2::TokenStream { + let data = if set_data { + quote!(Some(this.into_wrapped())) + } else { quote!(None) }; + + match ty { + ReflectType::Component => { + quote! { + Ok(ScriptBorrow::from_component::<#type_path>(#data)) + } + }, + ReflectType::Resource => { + quote! { + Ok(ScriptBorrow::from_component::<#type_path>(#data)) + } + }, + } +} + +#[derive(Debug, Clone, Copy)] +enum ReflectType { + //Unknown, + Component, + Resource, +} + +struct IntoLuaUsage { + type_path: syn::Path, + override_name: Option, + table_name: String, + derives: Vec, + fields: Vec, + create: Option, + reflection_type: Option, +} + +impl syn::parse::Parse for IntoLuaUsage { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let type_path: syn::Path = input.parse()?; + let mut s = Self { + type_path, + override_name: None, + table_name: String::new(), + derives: vec![], + fields: vec![], + create: None, + reflection_type: None, + }; + + while input.peek(Token![,]) { + let _: Token![,] = input.parse()?; + + if input.peek(syn::Ident) { + let ident: syn::Ident = input.parse()?; + let ident_str = ident.to_string(); + let ident_str = ident_str.as_str(); + + match ident_str { + "name" => { + let _eq: Token![=] = input.parse()?; + + let name: syn::Ident = input.parse()?; + s.override_name = Some(name); + }, + "lua_name" => { + let _eq: Token![=] = input.parse()?; + s.table_name = input.parse::()?.value(); + }, + "derives" => { + if input.peek(token::Paren) { + let content; + let _parens: token::Paren = parenthesized!(content in input); + + let derives: Punctuated = + content.parse_terminated(syn::Ident::parse, Token![,])?; + s.derives = derives.into_iter().collect(); + } + }, + "fields" => { + let _eq: Token![=] = input.parse()?; + + if input.peek(token::Brace) { + let content; + let _braced: token::Brace = braced!(content in input); + + let terminated = content.parse_terminated(Field::parse, Token![,])?; + s.fields.extend(terminated.into_iter()); + } + }, + "create" => { + let _eq: Token![=] = input.parse()?; + s.create = Some(input.parse()?); + }, + "reflect" => { + let _eq: Token![=] = input.parse()?; + let ty: syn::Ident = input.parse()?; + let ty_str = ty.to_string(); + let ty_str = ty_str.as_str(); + + let ty = match ty_str { + "component" => ReflectType::Component, + "resource" => ReflectType::Resource, + _ => return Err(syn::Error::new_spanned( + ident, + format!("unknown wrapper type: '{}', expected 'component' or 'resource'", ty_str), + )), + }; + + s.reflection_type = Some(ty); + }, + _ => { + return Err(syn::Error::new_spanned( + ident, + format!("unknown wrapper command: '{}'", ident_str), + )); + } + } + } + } + + if s.reflection_type.is_none() { + return Err(syn::Error::new( + input.span(), + format!("Wrapper type not specified! Expected 'type=component' or 'type=resource'"), + )); + } + + if s.table_name.is_empty() { + return Err(syn::Error::new( + input.span(), + format!("No lua table specified. Use 'lua_name=\"Camera\"'"), + )) + } + + Ok(s) + } +} + +pub fn to_lua_struct_impl(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as IntoLuaUsage); + + // unwrap is fine since `Some` is ensured in parse impl + let reflect_type = input.reflection_type.as_ref().unwrap(); + let type_path = &input.type_path; + let type_name = &type_path + .segments + .last() + .expect("Failure to find typename in macro usage!") + .ident; + let wrapper = input.override_name.unwrap_or_else(|| syn::Ident::new(format!("Lua{}", type_name.to_string()).as_str(), type_name.span())); + + let derives_iter = input.derives.into_iter(); + + let lua_name = &input.table_name; + let field_getters_iter = input.fields.iter().map(|f| field_table_getter(f)); + let field_setters_iter = input.fields.iter().map(|f| field_table_setter(f)); + let struct_creator = wrapper_creation(&wrapper, type_path, input.create.as_ref(), &input.fields); + let reflect_fn = get_reflect_lua_functions(reflect_type, &input.type_path, true); + let reflect_type_fn = get_reflect_lua_functions(reflect_type, &input.type_path, false); + + quote! { + #[derive(Clone, #(#derives_iter),*)] + pub struct #wrapper(pub(crate) #type_path); + + impl std::ops::Deref for #wrapper { + type Target = #type_path; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl std::ops::DerefMut for #wrapper { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } + } + + impl mlua::FromLua for #wrapper { + fn from_lua(val: mlua::Value, _: &mlua::Lua) -> mlua::Result { + let ty = val.type_name(); + let table = val.as_table().ok_or(mlua::Error::FromLuaConversionError { + from: ty, + to: "Table".into(), + message: Some("expected Table".into()), + })?; + + #( + #field_getters_iter + )* + + Ok(#struct_creator) + } + } + + impl mlua::IntoLua for #wrapper { + fn into_lua(self, lua: &mlua::Lua) -> mlua::Result { + let table = lua.create_table()?; + #( + #field_setters_iter + )* + + table.set( + #FN_NAME_INTERNAL_REFLECT, + lua.create_function(|_, this: Self| { + #reflect_fn + })?, + )?; + + table.set( + #FN_NAME_INTERNAL_REFLECT_TYPE, + lua.create_function(|_, ()| { + #reflect_type_fn + })?, + )?; + + table.set(mlua::MetaMethod::Type.name(), #lua_name)?; + + Ok(mlua::Value::Table(table)) + } + } + + impl LuaWrapper for #wrapper { + type Wrap = #type_path; + + #[inline(always)] + fn wrapped_type_id() -> std::any::TypeId { + std::any::TypeId::of::<#type_path>() + } + + #[inline(always)] + fn into_wrapped(self) -> Self::Wrap { + self.0 + } + } + + }.into_token_stream().into() +} diff --git a/lyra-scripting/scripts/lua/enums.lua b/lyra-scripting/scripts/lua/enums.lua index f22e262..ea62940 100644 --- a/lyra-scripting/scripts/lua/enums.lua +++ b/lyra-scripting/scripts/lua/enums.lua @@ -60,4 +60,10 @@ WrappingMode = { CLAMP_TO_EDGE = "clamp_to_edge", MIRRORED_REPEAT = "mirrored_repeat", REPEAT = "repeat", +} + +---@enum CameraProjectionMode +CameraProjectionMode = { + PERSPECTIVE = "perspective", + ORTHOGRAPHIC = "orthographic", } \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/types/ecs/camera.lua b/lyra-scripting/scripts/lua/types/ecs/camera.lua new file mode 100644 index 0000000..27ac6ef --- /dev/null +++ b/lyra-scripting/scripts/lua/types/ecs/camera.lua @@ -0,0 +1,18 @@ +---@meta + +---@class Camera: userdata +Camera = { + ---The position of the camera + ---@type Transform + transform = nil, + ---The field of view of the camera + ---@type Angle + fov = nil, + ---The projection mode the camera. + ---Can be used to specify if the camera is 2D (orthographic), or 3D (perspective). + ---@type CameraProjectionMode + mode = nil, + ---Flag to enable some debug rendering stuff. + ---@type boolean + debug = nil, +} \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/types/math/angle.lua b/lyra-scripting/scripts/lua/types/math/angle.lua new file mode 100644 index 0000000..1556d91 --- /dev/null +++ b/lyra-scripting/scripts/lua/types/math/angle.lua @@ -0,0 +1,25 @@ +---@meta + +---@class Angle: userdata +Angle = {} + +---Create a new angle in degrees. +---@param deg number +function Angle.new_degrees(deg) end +---Create a new angle in radians. +---@param rad number +function Angle.new_radians(rad) end + +---Get the angle in radians. +--- +---Will convert from degrees automatically. +--- +---@return number radians +function Angle:radians() end + +---Get the angle in degrees. +--- +---Will convert from radians automatically. +--- +---@return number degrees +function Angle:degrees() end \ No newline at end of file diff --git a/lyra-scripting/src/lua/mod.rs b/lyra-scripting/src/lua/mod.rs index c53c3c3..7bf232c 100644 --- a/lyra-scripting/src/lua/mod.rs +++ b/lyra-scripting/src/lua/mod.rs @@ -131,6 +131,11 @@ pub trait RegisterLuaType { fn register_lua_convert(&mut self) where T: Clone + mlua::FromLua + mlua::IntoLua + LuaWrapper + 'static; + + fn register_lua_convert_component(&mut self, name: &str) + where + T: Clone + mlua::FromLua + mlua::IntoLua + LuaWrapper + 'static, + T::Wrap: lyra_ecs::Component + Reflect; } impl RegisterLuaType for World { @@ -165,6 +170,18 @@ impl RegisterLuaType for World { let reg_type = registry.get_type_or_default(T::wrapped_type_id()); reg_type.add_data(ReflectLuaProxy::from_as_and_from_lua::()); } + + fn register_lua_convert_component(&mut self, name: &str) + where + T: Clone + mlua::FromLua + mlua::IntoLua + LuaWrapper + 'static, + T::Wrap: lyra_ecs::Component + Reflect + { + self.register_lua_convert::(); + + let mut lookup = self.get_resource_or_default::(); + lookup.comp_info_from_name.insert(name.into(), lyra_ecs::ComponentInfo::new::()); + lookup.typeid_from_name.insert(name.into(), std::any::TypeId::of::()); + } } impl mlua::FromLua for ScriptBorrow { diff --git a/lyra-scripting/src/lua/providers/ecs.rs b/lyra-scripting/src/lua/providers/ecs.rs index 1e9e1be..69c1907 100644 --- a/lyra-scripting/src/lua/providers/ecs.rs +++ b/lyra-scripting/src/lua/providers/ecs.rs @@ -4,7 +4,9 @@ use lyra_ecs::ResourceObject; use lyra_reflect::{Reflect, TypeRegistry}; use lyra_resource::gltf::Gltf; -use crate::{lua::{wrappers::{LuaActionHandler, LuaDeltaTime, LuaGltfHandle, LuaResHandleToComponent, LuaSceneHandle, LuaWindow}, LuaContext, ReflectLuaProxy, RegisterLuaType, FN_NAME_INTERNAL_REFLECT_TYPE}, ScriptApiProvider, ScriptBorrow, ScriptData, ScriptDynamicBundle, ScriptWorldPtr}; +use crate::{lua::{wrappers::*, LuaContext, LuaTableProxyLookup, LuaWrapper, ReflectLuaProxy, RegisterLuaType, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE}, ScriptApiProvider, ScriptBorrow, ScriptData, ScriptDynamicBundle, ScriptWorldPtr}; + +//fn register_lua_proxy::(); world.register_lua_wrapper::(); world.register_lua_wrapper::(); + world.register_lua_convert::(); + + let mut lookup = world.get_resource_or_default::(); + lookup.comp_info_from_name.insert("Camera".into(), lyra_ecs::ComponentInfo::new::()); + lookup.typeid_from_name.insert("Camera".into(), std::any::TypeId::of::()); + drop(lookup); let mut registry = world.get_resource_mut::().unwrap(); @@ -33,8 +41,6 @@ impl ScriptApiProvider for LyraEcsApiProvider { } ); reg_type.add_data(l); - - //reg_type.add_data(ReflectLuaProxy::from_lua_proxy::()); } fn expose_api(&mut self, _: &ScriptData, ctx: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> { @@ -51,6 +57,9 @@ impl ScriptApiProvider for LyraEcsApiProvider { globals.set("ActionHandler", ctx.create_proxy::()?)?; globals.set("Window", ctx.create_proxy::()?)?; + let cam_table = create_reflect_comp_table::(&ctx, "Camera")?; + globals.set("Camera", cam_table)?; + let dt_table = create_reflect_table::(&ctx)?; globals.set("DeltaTime", dt_table)?; @@ -72,5 +81,25 @@ fn create_reflect_table(lua: &m Ok(ScriptBorrow::from_resource::(None)) })?)?; + Ok(table) +} + +fn create_reflect_comp_table(lua: &mlua::Lua, name: &str) -> mlua::Result +where + T: LuaWrapper + mlua::FromLua, + T::Wrap: lyra_ecs::Component + Reflect +{ + let table = lua.create_table()?; + table.set(FN_NAME_INTERNAL_REFLECT, lua.create_function(|_, this: T| { + Ok(ScriptBorrow::from_component::(Some(this.into_wrapped()))) + })?)?; + + table.set(FN_NAME_INTERNAL_REFLECT_TYPE, lua.create_function(|_, ()| { + Ok(ScriptBorrow::from_component::(None)) + })?)?; + + table.set(mlua::MetaMethod::Type.name(), name)?; + + Ok(table) } \ No newline at end of file diff --git a/lyra-scripting/src/lua/providers/math.rs b/lyra-scripting/src/lua/providers/math.rs index 5897192..c1d4992 100644 --- a/lyra-scripting/src/lua/providers/math.rs +++ b/lyra-scripting/src/lua/providers/math.rs @@ -1,5 +1,5 @@ use lyra_ecs::World; -use crate::lua::wrappers::{LuaQuat, LuaTransform, LuaVec3, LuaVec2}; +use crate::lua::wrappers::{LuaAngle, LuaQuat, LuaTransform, LuaVec2, LuaVec3}; use crate::ScriptData; use crate::lua::RegisterLuaType; @@ -16,6 +16,7 @@ impl ScriptApiProvider for LyraMathApiProvider { world.register_lua_wrapper::(); world.register_lua_wrapper::(); world.register_lua_wrapper::(); + world.register_lua_wrapper::(); } fn expose_api(&mut self, _data: &ScriptData, ctx: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> { @@ -29,6 +30,7 @@ impl ScriptApiProvider for LyraMathApiProvider { globals.set("Vec3", ctx.create_proxy::()?)?; globals.set("Quat", ctx.create_proxy::()?)?; globals.set("Transform", ctx.create_proxy::()?)?; + globals.set("Angle", ctx.create_proxy::()?)?; Ok(()) } diff --git a/lyra-scripting/src/lua/proxy.rs b/lyra-scripting/src/lua/proxy.rs index 7017da1..75af9b6 100644 --- a/lyra-scripting/src/lua/proxy.rs +++ b/lyra-scripting/src/lua/proxy.rs @@ -9,8 +9,11 @@ use crate::{ScriptBorrow, ScriptDynamicBundle}; use super::{Error, FN_NAME_INTERNAL_REFLECT}; pub trait LuaWrapper { + type Wrap: Reflect + 'static; + /// The type id of the wrapped type. fn wrapped_type_id() -> TypeId; + fn into_wrapped(self) -> Self::Wrap; } /// A trait that used to convert something into lua, or to set something to a value from lua. diff --git a/lyra-scripting/src/lua/wrappers/asset/mod.rs b/lyra-scripting/src/lua/wrappers/asset/mod.rs index ec0a596..a1a3798 100644 --- a/lyra-scripting/src/lua/wrappers/asset/mod.rs +++ b/lyra-scripting/src/lua/wrappers/asset/mod.rs @@ -129,28 +129,29 @@ fn wrapping_mode_to_str(wm: WrappingMode) -> &'static str { wrap_lua_struct!(lyra_resource::TextureSampler, // this can be safely skipped since it wont be a component or resource. skip(lua_reflect), - field_getters={ - (mag_filter, { + fields={ + // don't need to specify field types since setters are skipped + (mag_filter, skip_set, get={ this.mag_filter.map(|f| filter_mode_to_str(f)) .into_lua(lua) }), - (min_filter, { + (min_filter, skip_set, get={ this.min_filter.map(|f| filter_mode_to_str(f)) .into_lua(lua) }), - (mipmap_filter, { + (mipmap_filter, skip_set, get={ this.mipmap_filter.map(|f| filter_mode_to_str(f)) .into_lua(lua) }), - (wrap_u, { + (wrap_u, skip_set, get={ wrapping_mode_to_str(this.wrap_u) .into_lua(lua) }), - (wrap_v, { + (wrap_v, skip_set, get={ wrapping_mode_to_str(this.wrap_v) .into_lua(lua) }), - (wrap_w, { + (wrap_w, skip_set, get={ wrapping_mode_to_str(this.wrap_w) .into_lua(lua) }), @@ -160,31 +161,29 @@ wrap_lua_struct!(lyra_resource::TextureSampler, wrap_lua_struct!(lyra_resource::gltf::PbrGlossiness, // this can be safely skipped since it wont be a component or resource. skip(lua_reflect), - field_getters={ - (diffuse_color, wrapper=crate::lua::wrappers::LuaVec4), - (specular, wrapper=crate::lua::wrappers::LuaVec3), - glossiness, + fields={ + (diffuse_color: wrap(crate::lua::wrappers::LuaVec4), skip_set), + (specular: wrap(crate::lua::wrappers::LuaVec3), skip_set), + (glossiness, skip_set), } ); wrap_lua_struct!(lyra_resource::gltf::Specular, // this can be safely skipped since it wont be a component or resource. skip(lua_reflect), - field_getters={ - factor, - (color_factor, wrapper=crate::lua::wrappers::LuaVec3), - }, - extra_fields={ - fields.add_field_method_get("texture", |lua, this| { + fields={ + (factor, skip_set), + (color_factor: wrap(crate::lua::wrappers::LuaVec3), skip_set), + (texture, skip_set, get={ this.texture.clone() .map(|t| LuaTextureHandle(t)) .into_lua(lua) - }); - fields.add_field_method_get("color_texture", |lua, this| { + }), + (color_texture, skip_set, get={ this.color_texture.clone() .map(|t| LuaTextureHandle(t)) .into_lua(lua) - }); + }) } ); diff --git a/lyra-scripting/src/lua/wrappers/camera.rs b/lyra-scripting/src/lua/wrappers/camera.rs new file mode 100644 index 0000000..4a7609f --- /dev/null +++ b/lyra-scripting/src/lua/wrappers/camera.rs @@ -0,0 +1,46 @@ +use crate::{ + lua::{ + wrappers::{LuaAngle, LuaTransform}, + LuaWrapper + }, + ScriptBorrow, +}; +use lyra_game::{render::camera::CameraProjectionMode, scene::CameraComponent}; +use lyra_reflect::Enum; +use lyra_scripting_derive::to_lua_convert; + +fn projection_mode_from_str(s: &str) -> Option { + match s { + "perspective" => Some(CameraProjectionMode::Perspective), + "orthographic" => Some(CameraProjectionMode::Orthographic), + _ => None, + } +} + +to_lua_convert!( + // Struct that is being wrapped[] + CameraComponent, + // Name of wrapping struct + name=LuaCamera, + // The name of the type in Lua + lua_name="Camera", + // Reflection type, can be 'component' or 'resource' + reflect=component, + fields={ + transform: wrap(LuaTransform), + fov: wrap(LuaAngle), + ( + mode, + // Get the table from the value, result must be `CameraProjectionMode` + get={ + let mode: String = table.get("mode")?; + projection_mode_from_str(&mode).unwrap() + }, + // Get the value from self, result must be the type in Lua, here its `String`. + set={ + self.mode.variant_name().to_lowercase() + } + ), + debug: bool + } +); diff --git a/lyra-scripting/src/lua/wrappers/delta_time.rs b/lyra-scripting/src/lua/wrappers/delta_time.rs index 36be0df..d263e40 100644 --- a/lyra-scripting/src/lua/wrappers/delta_time.rs +++ b/lyra-scripting/src/lua/wrappers/delta_time.rs @@ -34,7 +34,15 @@ impl mlua::IntoLua for LuaDeltaTime { } impl LuaWrapper for LuaDeltaTime { + type Wrap = DeltaTime; + + #[inline(always)] fn wrapped_type_id() -> std::any::TypeId { TypeId::of::() } + + #[inline(always)] + fn into_wrapped(self) -> Self::Wrap { + self.0 + } } diff --git a/lyra-scripting/src/lua/wrappers/input_actions.rs b/lyra-scripting/src/lua/wrappers/input_actions.rs index 93f7925..be75765 100644 --- a/lyra-scripting/src/lua/wrappers/input_actions.rs +++ b/lyra-scripting/src/lua/wrappers/input_actions.rs @@ -173,9 +173,15 @@ impl mlua::FromLua for LuaActionHandler { } impl LuaWrapper for LuaActionHandler { + type Wrap = ActionHandler; + fn wrapped_type_id() -> std::any::TypeId { std::any::TypeId::of::() } + + fn into_wrapped(self) -> Self::Wrap { + self.handler + } } fn process_keyboard_string(key_name: &str) -> Option { diff --git a/lyra-scripting/src/lua/wrappers/math.rs b/lyra-scripting/src/lua/wrappers/math.rs index 41967fb..2b2ea06 100644 --- a/lyra-scripting/src/lua/wrappers/math.rs +++ b/lyra-scripting/src/lua/wrappers/math.rs @@ -11,7 +11,7 @@ wrap_lua_struct!( math::Vec2, derives(PartialEq, Copy), new, - fields(x, y), + fields={x: f32, y: f32}, metamethods( Add(LuaVec2, f32), Sub(LuaVec2, f32), @@ -50,7 +50,7 @@ wrap_lua_struct!( math::Vec3, derives(PartialEq, Copy), new, - fields(x, y, z), + fields={x: f32, y: f32, z: f32}, metamethods( Add(LuaVec3, f32), Sub(LuaVec3, f32), @@ -90,7 +90,7 @@ wrap_lua_struct!( math::Vec4, derives(PartialEq, Copy), new, - fields(x, y, z, w), + fields={x: f32, y: f32, z: f32, w: f32}, metamethods( Add(LuaVec4, f32), Sub(LuaVec4, f32), @@ -108,7 +108,7 @@ wrap_lua_struct!( math::Quat, derives(PartialEq, Copy), //new, - fields(x, y, z, w), + fields={x: f32, y: f32, z: f32, w: f32}, metamethods( Eq, Add, @@ -360,4 +360,27 @@ wrap_lua_struct!( Ok(t) }); } +); + +wrap_lua_struct!( + math::Angle, + derives(Copy), + skip(lua_reflect), + extra_methods = { + methods.add_function("new_degrees", |_, deg: f32| { + Ok(Self(math::Angle::Degrees(deg))) + }); + + methods.add_function("new_radians", |_, rad: f32| { + Ok(Self(math::Angle::Radians(rad))) + }); + + methods.add_method("radians", |_, this, ()| { + Ok(this.to_radians()) + }); + + methods.add_method("degrees", |_, this, ()| { + Ok(this.to_degrees()) + }); + }, ); \ No newline at end of file diff --git a/lyra-scripting/src/lua/wrappers/mod.rs b/lyra-scripting/src/lua/wrappers/mod.rs index 742272e..5bfdd70 100644 --- a/lyra-scripting/src/lua/wrappers/mod.rs +++ b/lyra-scripting/src/lua/wrappers/mod.rs @@ -11,4 +11,7 @@ mod delta_time; pub use delta_time::*; mod window; -pub use window::*; \ No newline at end of file +pub use window::*; + +mod camera; +pub use camera::*; \ No newline at end of file diff --git a/lyra-scripting/src/lua/wrappers/window.rs b/lyra-scripting/src/lua/wrappers/window.rs index 9a6ddee..43178e5 100644 --- a/lyra-scripting/src/lua/wrappers/window.rs +++ b/lyra-scripting/src/lua/wrappers/window.rs @@ -1,7 +1,6 @@ use lyra_scripting_derive::wrap_lua_struct; use lyra_game::winit::{WindowOptions, FullscreenMode, Theme, CursorGrabMode, WindowLevel}; -use mlua::IntoLua; use crate::lyra_engine; use crate as lyra_scripting; @@ -15,7 +14,9 @@ wrap_lua_struct!( WindowOptions, //derives(Clone), name=LuaWindow, - fields(focused), + fields={ + (focused, skip_set) + }, extra_fields={ fields.add_field_method_get("window_mode", |lua, this| { let s = match &this.fullscreen_mode { -- 2.40.1 From b90e19161d36bb056ee004ac865c518886caada1 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Mon, 7 Oct 2024 16:28:38 -0400 Subject: [PATCH 06/15] lua: expose FreeFlyCamera --- examples/lua-scripting/scripts/test.lua | 4 +-- lyra-game/src/scene/free_fly_camera.rs | 3 +- .../lyra-scripting-derive/src/to_lua_macro.rs | 13 +++++++-- lyra-scripting/src/lua/providers/ecs.rs | 28 +++++++++++++------ lyra-scripting/src/lua/wrappers/camera.rs | 2 +- .../src/lua/wrappers/free_fly_camera.rs | 17 +++++++++++ lyra-scripting/src/lua/wrappers/mod.rs | 5 +++- 7 files changed, 56 insertions(+), 16 deletions(-) create mode 100644 lyra-scripting/src/lua/wrappers/free_fly_camera.rs diff --git a/examples/lua-scripting/scripts/test.lua b/examples/lua-scripting/scripts/test.lua index 717ea6e..083a991 100644 --- a/examples/lua-scripting/scripts/test.lua +++ b/examples/lua-scripting/scripts/test.lua @@ -77,7 +77,7 @@ function on_update() end, Transform ) ]] - world:view( + --[[ world:view( ---@param c Camera function (c) c.transform:translate(0, 0.15 * dt, 0) @@ -86,7 +86,7 @@ function on_update() return c end, Camera - ) + ) ]] end --[[ function on_post_update() diff --git a/lyra-game/src/scene/free_fly_camera.rs b/lyra-game/src/scene/free_fly_camera.rs index 39510ad..afce5bb 100644 --- a/lyra-game/src/scene/free_fly_camera.rs +++ b/lyra-game/src/scene/free_fly_camera.rs @@ -1,5 +1,6 @@ use glam::{EulerRot, Quat, Vec3}; use lyra_ecs::{query::{Res, View}, Component}; +use lyra_reflect::Reflect; use crate::{game::App, input::ActionHandler, plugin::Plugin, DeltaTime}; @@ -12,7 +13,7 @@ pub const ACTLBL_LOOK_LEFT_RIGHT: &str = "LookLeftRight"; pub const ACTLBL_LOOK_UP_DOWN: &str = "LookUpDown"; pub const ACTLBL_LOOK_ROLL: &str = "LookRoll"; -#[derive(Clone, Component)] +#[derive(Clone, Component, Reflect)] pub struct FreeFlyCamera { pub speed: f32, pub slow_speed_factor: f32, diff --git a/lyra-scripting/lyra-scripting-derive/src/to_lua_macro.rs b/lyra-scripting/lyra-scripting-derive/src/to_lua_macro.rs index d416887..225d14e 100644 --- a/lyra-scripting/lyra-scripting-derive/src/to_lua_macro.rs +++ b/lyra-scripting/lyra-scripting-derive/src/to_lua_macro.rs @@ -1,3 +1,4 @@ +use proc_macro2::Span; use syn::{braced, parenthesized, parse_macro_input, punctuated::Punctuated, token, Token}; use quote::{quote, ToTokens}; use crate::{field::Field, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE}; @@ -116,10 +117,17 @@ struct IntoLuaUsage { impl syn::parse::Parse for IntoLuaUsage { fn parse(input: syn::parse::ParseStream) -> syn::Result { let type_path: syn::Path = input.parse()?; + let type_ident = &type_path + .segments + .last() + .expect("Failure to find typename in macro usage!") + .ident; + let lua_name = type_ident.to_string(); + let mut s = Self { type_path, override_name: None, - table_name: String::new(), + table_name: lua_name, derives: vec![], fields: vec![], create: None, @@ -226,7 +234,8 @@ pub fn to_lua_struct_impl(input: proc_macro::TokenStream) -> proc_macro::TokenSt .last() .expect("Failure to find typename in macro usage!") .ident; - let wrapper = input.override_name.unwrap_or_else(|| syn::Ident::new(format!("Lua{}", type_name.to_string()).as_str(), type_name.span())); + let wrapper = input.override_name + .unwrap_or_else(|| syn::Ident::new(&format!("Lua{}", type_name), Span::call_site())); let derives_iter = input.derives.into_iter(); diff --git a/lyra-scripting/src/lua/providers/ecs.rs b/lyra-scripting/src/lua/providers/ecs.rs index 69c1907..00dd9ee 100644 --- a/lyra-scripting/src/lua/providers/ecs.rs +++ b/lyra-scripting/src/lua/providers/ecs.rs @@ -4,7 +4,7 @@ use lyra_ecs::ResourceObject; use lyra_reflect::{Reflect, TypeRegistry}; use lyra_resource::gltf::Gltf; -use crate::{lua::{wrappers::*, LuaContext, LuaTableProxyLookup, LuaWrapper, ReflectLuaProxy, RegisterLuaType, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE}, ScriptApiProvider, ScriptBorrow, ScriptData, ScriptDynamicBundle, ScriptWorldPtr}; +use crate::{lua::{wrappers::*, LuaContext, LuaWrapper, ReflectLuaProxy, RegisterLuaType, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE}, ScriptApiProvider, ScriptBorrow, ScriptData, ScriptDynamicBundle, ScriptWorldPtr}; //fn register_lua_proxy::(); world.register_lua_wrapper::(); world.register_lua_wrapper::(); - world.register_lua_convert::(); - - let mut lookup = world.get_resource_or_default::(); - lookup.comp_info_from_name.insert("Camera".into(), lyra_ecs::ComponentInfo::new::()); - lookup.typeid_from_name.insert("Camera".into(), std::any::TypeId::of::()); - drop(lookup); + world.register_lua_convert_component::("Camera"); + world.register_lua_convert_component::("FreeFlyCamera"); let mut registry = world.get_resource_mut::().unwrap(); @@ -57,8 +53,8 @@ impl ScriptApiProvider for LyraEcsApiProvider { globals.set("ActionHandler", ctx.create_proxy::()?)?; globals.set("Window", ctx.create_proxy::()?)?; - let cam_table = create_reflect_comp_table::(&ctx, "Camera")?; - globals.set("Camera", cam_table)?; + expose_table_wrapper::(&ctx, &globals, "Camera")?; + expose_table_wrapper::(&ctx, &globals, "FreeFlyCamera")?; let dt_table = create_reflect_table::(&ctx)?; globals.set("DeltaTime", dt_table)?; @@ -102,4 +98,18 @@ where Ok(table) +} + +/// Expose a wrapper that converts to/from a lua type. +/// +/// This creates the reflection functions on a table specified in globals. +/// The table name is set to `name`, which is also how the script will use the table. +fn expose_table_wrapper(lua: &mlua::Lua, globals: &mlua::Table, name: &str) -> mlua::Result<()> +where + T: LuaWrapper + mlua::FromLua, + T::Wrap: lyra_ecs::Component + Reflect +{ + let table = create_reflect_comp_table::(&lua, name)?; + globals.set(name, table)?; + Ok(()) } \ No newline at end of file diff --git a/lyra-scripting/src/lua/wrappers/camera.rs b/lyra-scripting/src/lua/wrappers/camera.rs index 4a7609f..cba35af 100644 --- a/lyra-scripting/src/lua/wrappers/camera.rs +++ b/lyra-scripting/src/lua/wrappers/camera.rs @@ -18,7 +18,7 @@ fn projection_mode_from_str(s: &str) -> Option { } to_lua_convert!( - // Struct that is being wrapped[] + // Struct that is being wrapped CameraComponent, // Name of wrapping struct name=LuaCamera, diff --git a/lyra-scripting/src/lua/wrappers/free_fly_camera.rs b/lyra-scripting/src/lua/wrappers/free_fly_camera.rs new file mode 100644 index 0000000..e08864a --- /dev/null +++ b/lyra-scripting/src/lua/wrappers/free_fly_camera.rs @@ -0,0 +1,17 @@ +use crate::{lua::LuaWrapper, ScriptBorrow}; +use lyra_game::scene::FreeFlyCamera; +use lyra_scripting_derive::to_lua_convert; + +to_lua_convert!( + // Struct that is being wrapped + FreeFlyCamera, + // Reflection type, can be 'component' or 'resource' + reflect=component, + fields={ + speed: f32, + slow_speed_factor: f32, + look_speed: f32, + mouse_sensitivity: f32, + look_with_keys: bool, + } +); diff --git a/lyra-scripting/src/lua/wrappers/mod.rs b/lyra-scripting/src/lua/wrappers/mod.rs index 5bfdd70..24136b8 100644 --- a/lyra-scripting/src/lua/wrappers/mod.rs +++ b/lyra-scripting/src/lua/wrappers/mod.rs @@ -14,4 +14,7 @@ mod window; pub use window::*; mod camera; -pub use camera::*; \ No newline at end of file +pub use camera::*; + +mod free_fly_camera; +pub use free_fly_camera::*; \ No newline at end of file -- 2.40.1 From 6f65e2ce35112a0c1bcd19d1b8b7aa32bd9f5896 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Tue, 8 Oct 2024 20:49:57 -0400 Subject: [PATCH 07/15] lua: add lua type defs for FreeFlyCamera and change name of field --- lyra-game/src/scene/free_fly_camera.rs | 8 +++---- .../scripts/lua/types/ecs/free_fly_camera.lua | 24 +++++++++++++++++++ .../src/lua/wrappers/free_fly_camera.rs | 2 +- 3 files changed, 29 insertions(+), 5 deletions(-) create mode 100644 lyra-scripting/scripts/lua/types/ecs/free_fly_camera.lua diff --git a/lyra-game/src/scene/free_fly_camera.rs b/lyra-game/src/scene/free_fly_camera.rs index afce5bb..1073d03 100644 --- a/lyra-game/src/scene/free_fly_camera.rs +++ b/lyra-game/src/scene/free_fly_camera.rs @@ -16,7 +16,7 @@ pub const ACTLBL_LOOK_ROLL: &str = "LookRoll"; #[derive(Clone, Component, Reflect)] pub struct FreeFlyCamera { pub speed: f32, - pub slow_speed_factor: f32, + pub modifier_speed_factor: f32, pub look_speed: f32, pub mouse_sensitivity: f32, pub look_with_keys: bool, @@ -26,7 +26,7 @@ impl Default for FreeFlyCamera { fn default() -> Self { Self { speed: 4.0, - slow_speed_factor: 0.25, + modifier_speed_factor: 0.25, look_speed: 0.5, mouse_sensitivity: 0.9, look_with_keys: false, @@ -36,10 +36,10 @@ impl Default for FreeFlyCamera { impl FreeFlyCamera { #[allow(dead_code)] - pub fn new(speed: f32, slow_speed_factor: f32, look_speed: f32, mouse_sensitivity: f32, look_with_keys: bool) -> Self { + pub fn new(speed: f32, modifier_speed_factor: f32, look_speed: f32, mouse_sensitivity: f32, look_with_keys: bool) -> Self { Self { speed, - slow_speed_factor, + modifier_speed_factor, look_speed, mouse_sensitivity, look_with_keys, diff --git a/lyra-scripting/scripts/lua/types/ecs/free_fly_camera.lua b/lyra-scripting/scripts/lua/types/ecs/free_fly_camera.lua new file mode 100644 index 0000000..26e929a --- /dev/null +++ b/lyra-scripting/scripts/lua/types/ecs/free_fly_camera.lua @@ -0,0 +1,24 @@ +---@meta + +---@class FreeFlyCamera: userdata +FreeFlyCamera = { + ---Movement speed of the camera. + ---@type number + speed = nil, + ---The modifier speed factor. + --- + ---This isn't currently used, but it would be a factor applied to `speed` + ---when the modifer key is pressed. For example: a slow or sprint mode. + --- + ---@type number + modifier_speed_factor = nil, + ---The speed that the camera rotates, i.e., looks. + ---@type number + look_speed = nil, + ---The sensitivity of the mouse when looking. + ---@type number + mouse_sensitivity = nil, + ---Enable looking with arrow keys + ---@type boolean + look_with_keys = nil, +} \ No newline at end of file diff --git a/lyra-scripting/src/lua/wrappers/free_fly_camera.rs b/lyra-scripting/src/lua/wrappers/free_fly_camera.rs index e08864a..af325a4 100644 --- a/lyra-scripting/src/lua/wrappers/free_fly_camera.rs +++ b/lyra-scripting/src/lua/wrappers/free_fly_camera.rs @@ -9,7 +9,7 @@ to_lua_convert!( reflect=component, fields={ speed: f32, - slow_speed_factor: f32, + modifier_speed_factor: f32, look_speed: f32, mouse_sensitivity: f32, look_with_keys: bool, -- 2.40.1 From 77ec620adb1f30a2e9ec3320526449073308260f Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Wed, 9 Oct 2024 10:30:45 -0400 Subject: [PATCH 08/15] lua: remove unused fields in FreeFlyCamera --- lyra-game/src/scene/free_fly_camera.rs | 9 +-------- .../scripts/lua/types/ecs/free_fly_camera.lua | 15 ++++----------- .../src/lua/wrappers/free_fly_camera.rs | 2 -- 3 files changed, 5 insertions(+), 21 deletions(-) diff --git a/lyra-game/src/scene/free_fly_camera.rs b/lyra-game/src/scene/free_fly_camera.rs index 1073d03..d43a883 100644 --- a/lyra-game/src/scene/free_fly_camera.rs +++ b/lyra-game/src/scene/free_fly_camera.rs @@ -16,33 +16,26 @@ pub const ACTLBL_LOOK_ROLL: &str = "LookRoll"; #[derive(Clone, Component, Reflect)] pub struct FreeFlyCamera { pub speed: f32, - pub modifier_speed_factor: f32, pub look_speed: f32, pub mouse_sensitivity: f32, - pub look_with_keys: bool, } impl Default for FreeFlyCamera { fn default() -> Self { Self { speed: 4.0, - modifier_speed_factor: 0.25, look_speed: 0.5, mouse_sensitivity: 0.9, - look_with_keys: false, } } } impl FreeFlyCamera { - #[allow(dead_code)] - pub fn new(speed: f32, modifier_speed_factor: f32, look_speed: f32, mouse_sensitivity: f32, look_with_keys: bool) -> Self { + pub fn new(speed: f32, look_speed: f32, mouse_sensitivity: f32) -> Self { Self { speed, - modifier_speed_factor, look_speed, mouse_sensitivity, - look_with_keys, } } } diff --git a/lyra-scripting/scripts/lua/types/ecs/free_fly_camera.lua b/lyra-scripting/scripts/lua/types/ecs/free_fly_camera.lua index 26e929a..9c12c1a 100644 --- a/lyra-scripting/scripts/lua/types/ecs/free_fly_camera.lua +++ b/lyra-scripting/scripts/lua/types/ecs/free_fly_camera.lua @@ -5,20 +5,13 @@ FreeFlyCamera = { ---Movement speed of the camera. ---@type number speed = nil, - ---The modifier speed factor. - --- - ---This isn't currently used, but it would be a factor applied to `speed` - ---when the modifer key is pressed. For example: a slow or sprint mode. - --- - ---@type number - modifier_speed_factor = nil, - ---The speed that the camera rotates, i.e., looks. + ---The speed of the camera rotation. ---@type number look_speed = nil, ---The sensitivity of the mouse when looking. + --- + ---This is additional to `look_speed`, but onyl applied to mouse movement. + --- ---@type number mouse_sensitivity = nil, - ---Enable looking with arrow keys - ---@type boolean - look_with_keys = nil, } \ No newline at end of file diff --git a/lyra-scripting/src/lua/wrappers/free_fly_camera.rs b/lyra-scripting/src/lua/wrappers/free_fly_camera.rs index af325a4..75052ec 100644 --- a/lyra-scripting/src/lua/wrappers/free_fly_camera.rs +++ b/lyra-scripting/src/lua/wrappers/free_fly_camera.rs @@ -9,9 +9,7 @@ to_lua_convert!( reflect=component, fields={ speed: f32, - modifier_speed_factor: f32, look_speed: f32, mouse_sensitivity: f32, - look_with_keys: bool, } ); -- 2.40.1 From eff6b221e0df956868bfdd23a504c76a6f2ddda5 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Wed, 9 Oct 2024 10:56:54 -0400 Subject: [PATCH 09/15] remove unused code, cleanup some warnings --- examples/many-lights/src/main.rs | 2 +- examples/simple_scene/src/main.rs | 15 +-- examples/testbed/src/main.rs | 182 +++++++++++++++++++----------- lyra-game/src/scene/mesh.rs | 37 ------ lyra-game/src/scene/mod.rs | 3 - 5 files changed, 117 insertions(+), 122 deletions(-) delete mode 100755 lyra-game/src/scene/mesh.rs diff --git a/examples/many-lights/src/main.rs b/examples/many-lights/src/main.rs index b9248bd..391a365 100644 --- a/examples/many-lights/src/main.rs +++ b/examples/many-lights/src/main.rs @@ -196,7 +196,7 @@ fn setup_scene_plugin(app: &mut App) { fn camera_debug_plugin(app: &mut App) { let sys = |handler: Res, view: View<&mut CameraComponent>| -> anyhow::Result<()> { - if handler.was_action_just_pressed("Debug") { + if let Some(true) = handler.was_action_just_pressed("Debug") { for mut cam in view.into_iter() { cam.debug = !cam.debug; } diff --git a/examples/simple_scene/src/main.rs b/examples/simple_scene/src/main.rs index eee7f82..ff7ae10 100644 --- a/examples/simple_scene/src/main.rs +++ b/examples/simple_scene/src/main.rs @@ -1,12 +1,12 @@ use lyra_engine::{ assets::{gltf::Gltf, ResourceManager}, ecs::query::View, game::App, input::{ Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource, - InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput, - }, math::{self, Transform, Vec3}, render::{light::directional::DirectionalLight, window::WindowOptions}, scene::{ + InputActionPlugin, KeyCode, LayoutId, + }, math::{self, Transform, Vec3}, render::light::directional::DirectionalLight, scene::{ CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform, ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN, ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN, - } + }, winit::WindowOptions }; #[async_std::main] @@ -82,7 +82,6 @@ async fn main() { let mut a = App::new(); a.with_plugin(lyra_engine::DefaultPlugins) - .with_system("mouse_pos_print", mouse_pos_system, &[]) .with_plugin(setup_scene_plugin) .with_plugin(action_handler_plugin) //.with_plugin(camera_debug_plugin) @@ -141,11 +140,3 @@ fn setup_scene_plugin(app: &mut App) { camera.transform.translation += math::Vec3::new(0.0, 0.0, 5.5); world.spawn((camera, FreeFlyCamera::default())); } - -fn mouse_pos_system(view: View<&WindowOptions>) -> anyhow::Result<()> { - for win in view.iter() { - //println!("Mouse pos: {:?}", win.cursor_position()); - } - - Ok(()) -} \ No newline at end of file diff --git a/examples/testbed/src/main.rs b/examples/testbed/src/main.rs index 70e4229..ea9f116 100644 --- a/examples/testbed/src/main.rs +++ b/examples/testbed/src/main.rs @@ -1,7 +1,27 @@ use std::ptr::NonNull; -use lyra_engine::{assets::gltf::Gltf, ecs::{query::{Res, View}, system::{Criteria, CriteriaSchedule, IntoSystem}, Component, World}, game::App, input::{Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource, InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput}, math::{self, Quat, Transform, Vec3}, render::{light::{directional::DirectionalLight, PointLight, SpotLight}, window::{CursorGrabMode, WindowOptions}}, scene::{self, CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform, ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN, ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN}, DeltaTime}; use lyra_engine::assets::ResourceManager; +use lyra_engine::{ + assets::gltf::Gltf, + ecs::{ + query::{Res, View}, + system::{Criteria, CriteriaSchedule, IntoSystem}, + Component, World, + }, + game::App, + input::{ + Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource, + InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput, + }, + math::{self, Quat, Transform, Vec3}, + render::light::{directional::DirectionalLight, PointLight, SpotLight}, + scene::{ + self, CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform, + ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN, + ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN, + }, + DeltaTime, +}; struct FixedTimestep { max_tps: u32, @@ -54,7 +74,7 @@ impl Criteria for FixedTimestep { CriteriaSchedule::No } - + fn modify_world(&mut self, mut world: NonNull) { let world = unsafe { world.as_mut() }; self.old_dt = world.get_resource().map(|r| *r); @@ -71,9 +91,11 @@ impl Criteria for FixedTimestep { } } +#[allow(dead_code)] #[derive(Clone)] struct TpsAccumulator(f32); +#[allow(dead_code)] #[derive(Component)] struct CubeFlag; @@ -90,27 +112,29 @@ async fn main() { //let diffuse_texture = resman.request::("assets/happy-tree.png").unwrap(); //let antique_camera_model = resman.request::("assets/AntiqueCamera.glb").unwrap(); //let cube_model = resman.request::("assets/cube-texture-bin.glb").unwrap(); - let cube_gltf = resman.request::("../assets/texture-sep/texture-sep.gltf").unwrap(); + let cube_gltf = resman + .request::("../assets/texture-sep/texture-sep.gltf") + .unwrap(); /*let crate_gltf = resman.request::("assets/crate/crate.gltf").unwrap(); let separate_gltf = resman.request::("assets/pos-testing/child-node-cubes.glb").unwrap(); */ //drop(resman); cube_gltf.wait_recurse_dependencies_load(); - let cube_mesh = &cube_gltf.data_ref() - .unwrap().meshes[0]; + let cube_mesh = &cube_gltf.data_ref().unwrap().meshes[0]; /* let crate_mesh = &crate_gltf.data_ref() .unwrap().meshes[0]; let separate_scene = &separate_gltf.data_ref() .unwrap().scenes[0]; */ - let sponza_model = resman.request::("../assets/sponza/Sponza.gltf").unwrap(); + let sponza_model = resman + .request::("../assets/sponza/Sponza.gltf") + .unwrap(); drop(resman); sponza_model.wait_recurse_dependencies_load(); - let sponza_scene = &sponza_model.data_ref() - .unwrap().scenes[0]; + let sponza_scene = &sponza_model.data_ref().unwrap().scenes[0]; world.spawn(( sponza_scene.clone(), @@ -127,8 +151,7 @@ async fn main() { DirectionalLight { enabled: true, color: Vec3::ONE, - intensity: 0.35 - //..Default::default() + intensity: 0.35, //..Default::default() }, light_tran, )); @@ -136,10 +159,9 @@ async fn main() { { let t = Transform::new( - //Vec3::new(-5.0, 1.0, -1.28), + //Vec3::new(-5.0, 1.0, -1.28), Vec3::new(-5.0, 1.0, -0.0), - //Vec3::new(-10.0, 0.94, -0.28), - + //Vec3::new(-10.0, 0.94, -0.28), Quat::IDENTITY, Vec3::new(0.25, 0.25, 0.25), ); @@ -160,8 +182,7 @@ async fn main() { let t = Transform::new( Vec3::new(-3.0, 0.2, -1.5), //Vec3::new(-5.0, 1.0, -0.28), - //Vec3::new(-10.0, 0.94, -0.28), - + //Vec3::new(-10.0, 0.94, -0.28), Quat::IDENTITY, Vec3::new(0.15, 0.15, 0.15), ); @@ -182,8 +203,7 @@ async fn main() { let t = Transform::new( Vec3::new(0.0, 0.2, -1.5), //Vec3::new(-5.0, 1.0, -0.28), - //Vec3::new(-10.0, 0.94, -0.28), - + //Vec3::new(-10.0, 0.94, -0.28), Quat::IDENTITY, Vec3::new(0.15, 0.15, 0.15), ); @@ -215,7 +235,7 @@ async fn main() { constant: 1.0, linear: 0.09, quadratic: 0.032, - + ambient: 0.2, diffuse: 1.0, specular: 1.3, @@ -224,7 +244,7 @@ async fn main() { cube_mesh.clone(), )); } */ - + /* { let mut light_tran = Transform::from_xyz(-5.0, 2.5, -9.5); light_tran.scale = Vec3::new(0.5, 0.5, 0.5); @@ -237,7 +257,7 @@ async fn main() { constant: 1.0, linear: 0.045, quadratic: 0.0075, - + ambient: 0.1, diffuse: 1.0, specular: 1.3, @@ -250,35 +270,39 @@ async fn main() { let mut camera = CameraComponent::new_3d(); // these values were taken by manually positioning the camera in the scene. camera.transform = Transform::new( - Vec3::new(-10.0, 0.94, -0.28), - Quat::from_xyzw(0.03375484, -0.7116095, 0.0342693, 0.70092666), - Vec3::ONE + Vec3::new(-10.0, 0.94, -0.28), + Quat::from_xyzw(0.03375484, -0.7116095, 0.0342693, 0.70092666), + Vec3::ONE, ); //camera.transform.translation += math::Vec3::new(0.0, 0.0, 5.5); - world.spawn(( camera, FreeFlyCamera::default() )); + world.spawn((camera, FreeFlyCamera::default())); Ok(()) }; let camera_debug_plugin = move |app: &mut App| { - let sys = |handler: Res, view: View<&mut CameraComponent>| -> anyhow::Result<()> { - if handler.was_action_just_pressed("Debug") { - for mut cam in view.into_iter() { - cam.debug = !cam.debug; + let sys = + |handler: Res, view: View<&mut CameraComponent>| -> anyhow::Result<()> { + if let Some(true) = handler.was_action_just_pressed("Debug") { + for mut cam in view.into_iter() { + cam.debug = !cam.debug; + } } - } - Ok(()) - }; + Ok(()) + }; app.with_system("camera_debug_trigger", sys, &[]); - app.with_system("update_world_transforms", scene::system_update_world_transforms, &[]); + app.with_system( + "update_world_transforms", + scene::system_update_world_transforms, + &[], + ); }; let action_handler_plugin = |app: &mut App| { let action_handler = ActionHandler::builder() .add_layout(LayoutId::from(0)) - .add_action(ACTLBL_MOVE_FORWARD_BACKWARD, Action::new(ActionKind::Axis)) .add_action(ACTLBL_MOVE_LEFT_RIGHT, Action::new(ActionKind::Axis)) .add_action(ACTLBL_MOVE_UP_DOWN, Action::new(ActionKind::Axis)) @@ -286,41 +310,61 @@ async fn main() { .add_action(ACTLBL_LOOK_UP_DOWN, Action::new(ActionKind::Axis)) .add_action(ACTLBL_LOOK_ROLL, Action::new(ActionKind::Axis)) .add_action("Debug", Action::new(ActionKind::Button)) - - .add_mapping(ActionMapping::builder(LayoutId::from(0), ActionMappingId::from(0)) - .bind(ACTLBL_MOVE_FORWARD_BACKWARD, &[ - ActionSource::Keyboard(KeyCode::KeyW).into_binding_modifier(1.0), - ActionSource::Keyboard(KeyCode::KeyS).into_binding_modifier(-1.0) - ]) - .bind(ACTLBL_MOVE_LEFT_RIGHT, &[ - ActionSource::Keyboard(KeyCode::KeyA).into_binding_modifier(-1.0), - ActionSource::Keyboard(KeyCode::KeyD).into_binding_modifier(1.0) - ]) - .bind(ACTLBL_MOVE_UP_DOWN, &[ - ActionSource::Keyboard(KeyCode::KeyC).into_binding_modifier(1.0), - ActionSource::Keyboard(KeyCode::KeyZ).into_binding_modifier(-1.0) - ]) - .bind(ACTLBL_LOOK_LEFT_RIGHT, &[ - ActionSource::Mouse(MouseInput::Axis(MouseAxis::X)).into_binding(), - ActionSource::Keyboard(KeyCode::ArrowLeft).into_binding_modifier(-1.0), - ActionSource::Keyboard(KeyCode::ArrowRight).into_binding_modifier(1.0), - //ActionSource::Gamepad(GamepadFormat::DualAxis, GamepadInput::Axis(GamepadAxis::RThumbstickX)).into_binding(), - ]) - .bind(ACTLBL_LOOK_UP_DOWN, &[ - ActionSource::Mouse(MouseInput::Axis(MouseAxis::Y)).into_binding(), - ActionSource::Keyboard(KeyCode::ArrowUp).into_binding_modifier(-1.0), - ActionSource::Keyboard(KeyCode::ArrowDown).into_binding_modifier(1.0), - //ActionSource::Gamepad(GamepadFormat::DualAxis, GamepadInput::Axis(GamepadAxis::RThumbstickY)).into_binding(), - ]) - .bind(ACTLBL_LOOK_ROLL, &[ - ActionSource::Keyboard(KeyCode::KeyE).into_binding_modifier(-1.0), - ActionSource::Keyboard(KeyCode::KeyQ).into_binding_modifier(1.0), - ]) - .bind("Debug", &[ - ActionSource::Keyboard(KeyCode::KeyB).into_binding(), - ]) - .finish() - ).finish(); + .add_mapping( + ActionMapping::builder(LayoutId::from(0), ActionMappingId::from(0)) + .bind( + ACTLBL_MOVE_FORWARD_BACKWARD, + &[ + ActionSource::Keyboard(KeyCode::KeyW).into_binding_modifier(1.0), + ActionSource::Keyboard(KeyCode::KeyS).into_binding_modifier(-1.0), + ], + ) + .bind( + ACTLBL_MOVE_LEFT_RIGHT, + &[ + ActionSource::Keyboard(KeyCode::KeyA).into_binding_modifier(-1.0), + ActionSource::Keyboard(KeyCode::KeyD).into_binding_modifier(1.0), + ], + ) + .bind( + ACTLBL_MOVE_UP_DOWN, + &[ + ActionSource::Keyboard(KeyCode::KeyC).into_binding_modifier(1.0), + ActionSource::Keyboard(KeyCode::KeyZ).into_binding_modifier(-1.0), + ], + ) + .bind( + ACTLBL_LOOK_LEFT_RIGHT, + &[ + ActionSource::Mouse(MouseInput::Axis(MouseAxis::X)).into_binding(), + ActionSource::Keyboard(KeyCode::ArrowLeft).into_binding_modifier(-1.0), + ActionSource::Keyboard(KeyCode::ArrowRight).into_binding_modifier(1.0), + //ActionSource::Gamepad(GamepadFormat::DualAxis, GamepadInput::Axis(GamepadAxis::RThumbstickX)).into_binding(), + ], + ) + .bind( + ACTLBL_LOOK_UP_DOWN, + &[ + ActionSource::Mouse(MouseInput::Axis(MouseAxis::Y)).into_binding(), + ActionSource::Keyboard(KeyCode::ArrowUp).into_binding_modifier(-1.0), + ActionSource::Keyboard(KeyCode::ArrowDown).into_binding_modifier(1.0), + //ActionSource::Gamepad(GamepadFormat::DualAxis, GamepadInput::Axis(GamepadAxis::RThumbstickY)).into_binding(), + ], + ) + .bind( + ACTLBL_LOOK_ROLL, + &[ + ActionSource::Keyboard(KeyCode::KeyE).into_binding_modifier(-1.0), + ActionSource::Keyboard(KeyCode::KeyQ).into_binding_modifier(1.0), + ], + ) + .bind( + "Debug", + &[ActionSource::Keyboard(KeyCode::KeyB).into_binding()], + ) + .finish(), + ) + .finish(); let world = &mut app.world; world.add_resource(action_handler); @@ -341,7 +385,7 @@ async fn main() { world.spawn((scripts,)); }; */ - + let mut app = App::new(); app.with_plugin(lyra_engine::DefaultPlugins); app.with_startup_system(setup_sys.into_system()); diff --git a/lyra-game/src/scene/mesh.rs b/lyra-game/src/scene/mesh.rs deleted file mode 100755 index f7c7255..0000000 --- a/lyra-game/src/scene/mesh.rs +++ /dev/null @@ -1,37 +0,0 @@ -use lyra_ecs::Component; -use lyra_reflect::Reflect; -use lyra_resource::{gltf::Mesh, ResHandle}; - -#[derive(Clone, Component, Reflect)] -pub struct MeshComponent { - #[reflect(skip)] - pub mesh: ResHandle, -} - -impl From> for MeshComponent { - fn from(value: ResHandle) -> Self { - Self { - mesh: value - } - } -} - -impl std::ops::Deref for MeshComponent { - type Target = ResHandle; - - fn deref(&self) -> &Self::Target { - &self.mesh - } -} - -impl std::ops::DerefMut for MeshComponent { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.mesh - } -} - -impl MeshComponent { - pub fn new(mesh: ResHandle) -> Self { - Self::from(mesh) - } -} \ No newline at end of file diff --git a/lyra-game/src/scene/mod.rs b/lyra-game/src/scene/mod.rs index 3437fba..9677295 100644 --- a/lyra-game/src/scene/mod.rs +++ b/lyra-game/src/scene/mod.rs @@ -1,6 +1,3 @@ -pub mod mesh; -pub use mesh::*; - pub mod camera; pub use camera::*; -- 2.40.1 From 624cd5362ff0501d14e7fc42e49b0549431eaae9 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Wed, 9 Oct 2024 11:08:21 -0400 Subject: [PATCH 10/15] lua: change lyra-scripting path in lyra-engine crate --- examples/lua-scripting/src/main.rs | 3 +- lyra-game/Cargo.toml | 2 +- lyra-game/src/change_tracker.rs | 119 ----------------------------- lyra-game/src/lib.rs | 4 - src/lib.rs | 2 +- 5 files changed, 3 insertions(+), 127 deletions(-) delete mode 100644 lyra-game/src/change_tracker.rs diff --git a/examples/lua-scripting/src/main.rs b/examples/lua-scripting/src/main.rs index bf23e64..e0436f9 100644 --- a/examples/lua-scripting/src/main.rs +++ b/examples/lua-scripting/src/main.rs @@ -5,7 +5,7 @@ use lyra_engine::{ Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource, InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput, }, - lua::{LuaScript, LuaScriptingPlugin}, + script::{lua::{LuaScript, LuaScriptingPlugin}, Script, ScriptList}, math::{self, Transform, Vec3}, render::light::directional::DirectionalLight, scene::{ @@ -13,7 +13,6 @@ use lyra_engine::{ ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN, ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN, }, - Script, ScriptList, }; #[async_std::main] diff --git a/lyra-game/Cargo.toml b/lyra-game/Cargo.toml index af9e840..52695bd 100644 --- a/lyra-game/Cargo.toml +++ b/lyra-game/Cargo.toml @@ -43,4 +43,4 @@ fast_poisson = { version = "1.0.0", features = ["single_precision"] } atomic_refcell = "0.1.13" [features] -tracy = ["dep:tracing-tracy"] +tracy = ["dep:tracing-tracy"] \ No newline at end of file diff --git a/lyra-game/src/change_tracker.rs b/lyra-game/src/change_tracker.rs deleted file mode 100644 index ac8215c..0000000 --- a/lyra-game/src/change_tracker.rs +++ /dev/null @@ -1,119 +0,0 @@ -use std::ops::{DerefMut, Deref}; - -/// A change tracker. This tracks changes of the data it owns. -/// Tracking changes can cause false positives since it actually tracks calls to `deref_mut()` -/// If you mutably dereference this, make sure its only when you change the data. -pub struct Ct { - data: T, - changed: bool, -} - -impl Ct { - pub fn new(data: T) -> Self { - Self { - data, - changed: false, - } - } - - /// Create a new change tracker with data - pub fn new_true(data: T) -> Self { - Self { - data, - changed: true, - } - } - - /// Returns true if there was a change. Will reset back to false after this is called. - pub fn changed(&mut self) -> bool { - let before = self.changed; - self.reset(); - before - } - - /// Returns true if there was a change, will NOT reset back to false. - pub fn peek_changed(&self) -> bool { - self.changed - } - - /// Resets the changed tracker to be false - pub fn reset(&mut self) { - self.changed = false; - } - - /// Triggers the change tracker to be true - pub fn trigger(&mut self) { - self.changed = true; - } - - /// Silently mutate the inner data, this wont be tracked - pub fn silently_mutate(&mut self) -> &mut T { - &mut self.data - } - - /// Consumes self and returns the data stored inside. - pub fn take(self) -> T { - self.data - } -} - -impl Deref for Ct { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.data - } -} - -impl DerefMut for Ct { - fn deref_mut(&mut self) -> &mut Self::Target { - self.changed = true; - &mut self.data - } -} - -#[cfg(test)] -mod tests { - use super::Ct; - - #[test] - fn new() { - let mut c = Ct::new(100); - assert!(!c.changed()); - - let mut c = Ct::new_true(100); - assert!(c.changed()); - assert!(!c.changed()); // it should've reset itself - } - - #[test] - fn change_tracking() { - let mut c = Ct::new(100); - assert!(!c.changed()); - - *c = 10; - assert!(c.changed()); - assert!(!c.changed()); // should now be not changed - } - - #[test] - fn silent_mutate() { - let mut c = Ct::new(100); - let a = c.silently_mutate(); - *a = 10; - assert!(!c.changed()); - } - - #[test] - fn reset_trigger() { - let mut c = Ct::new(100); - *c = 10; - c.reset(); - assert!(!c.changed()); // should not be changed because of reset - - let mut c = Ct::new(100); - c.trigger(); - assert!(c.changed()); // should changed because of trigger - assert!(!c.changed()); // should've reset itself - } -} \ No newline at end of file diff --git a/lyra-game/src/lib.rs b/lyra-game/src/lib.rs index 6be44b4..9961435 100644 --- a/lyra-game/src/lib.rs +++ b/lyra-game/src/lib.rs @@ -11,7 +11,6 @@ pub mod input; pub mod winit; pub mod as_any; pub mod plugin; -pub mod change_tracker; mod event; pub use event::*; @@ -29,7 +28,4 @@ pub use lyra_ecs as ecs; pub use lyra_math as math; pub use lyra_reflect as reflect; -#[cfg(feature = "scripting")] -pub use lyra_scripting as script; - pub use plugin::DefaultPlugins; \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 1f81609..4c67128 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ pub use lyra_game::*; #[cfg(feature = "scripting")] -pub use lyra_scripting::*; \ No newline at end of file +pub use lyra_scripting as script; \ No newline at end of file -- 2.40.1 From 9e9478966bd5e697105c1c9a0b8bf27e8577a596 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Wed, 9 Oct 2024 12:06:08 -0400 Subject: [PATCH 11/15] lua: cleanup --- lyra-game/src/winit/plugin.rs | 5 ++++- lyra-scripting/src/lua/mod.rs | 20 +++++++++++++++++--- lyra-scripting/src/lua/proxy.rs | 6 ++++-- lyra-scripting/src/lua/world.rs | 6 +++--- 4 files changed, 28 insertions(+), 9 deletions(-) diff --git a/lyra-game/src/winit/plugin.rs b/lyra-game/src/winit/plugin.rs index fef3f02..ce3c3b5 100644 --- a/lyra-game/src/winit/plugin.rs +++ b/lyra-game/src/winit/plugin.rs @@ -3,6 +3,7 @@ use std::{collections::VecDeque, sync::Arc}; use async_std::task::block_on; use glam::{DVec2, IVec2, UVec2}; use lyra_ecs::Entity; +use lyra_reflect::Reflect; use rustc_hash::FxHashMap; use tracing::{debug, error, warn}; use winit::{ @@ -22,9 +23,11 @@ use super::WindowOptions; /// A struct that contains a [`DeviceEvent`](winit::event::DeviceEvent) with its source /// [`DeviceId`](winit::event::DeviceId). -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Reflect)] pub struct DeviceEventPair { + #[reflect(skip)] pub device_src: winit::event::DeviceId, + #[reflect(skip)] pub event: winit::event::DeviceEvent, } diff --git a/lyra-scripting/src/lua/mod.rs b/lyra-scripting/src/lua/mod.rs index 7bf232c..787c850 100644 --- a/lyra-scripting/src/lua/mod.rs +++ b/lyra-scripting/src/lua/mod.rs @@ -132,10 +132,18 @@ pub trait RegisterLuaType { where T: Clone + mlua::FromLua + mlua::IntoLua + LuaWrapper + 'static; + /// Registers a type that can be converted to and from lua and adds a lookup entry. + /// + /// This is a shortcut for `register_lua_convert` and `add_component_lookup_entry`. fn register_lua_convert_component(&mut self, name: &str) where T: Clone + mlua::FromLua + mlua::IntoLua + LuaWrapper + 'static, T::Wrap: lyra_ecs::Component + Reflect; + + /// Add an entry for a component in the [`TypeLookup`] table. + fn add_component_lookup_entry(&mut self, name: &str) + where + T: lyra_ecs::Component; } impl RegisterLuaType for World { @@ -177,10 +185,16 @@ impl RegisterLuaType for World { T::Wrap: lyra_ecs::Component + Reflect { self.register_lua_convert::(); + self.add_component_lookup_entry::(name); + } - let mut lookup = self.get_resource_or_default::(); - lookup.comp_info_from_name.insert(name.into(), lyra_ecs::ComponentInfo::new::()); - lookup.typeid_from_name.insert(name.into(), std::any::TypeId::of::()); + fn add_component_lookup_entry(&mut self, name: &str) + where + T: lyra_ecs::Component + { + let mut lookup = self.get_resource_or_default::(); + lookup.comp_info_from_name.insert(name.into(), lyra_ecs::ComponentInfo::new::()); + lookup.typeid_from_name.insert(name.into(), std::any::TypeId::of::()); } } diff --git a/lyra-scripting/src/lua/proxy.rs b/lyra-scripting/src/lua/proxy.rs index 75af9b6..d738bea 100644 --- a/lyra-scripting/src/lua/proxy.rs +++ b/lyra-scripting/src/lua/proxy.rs @@ -58,9 +58,11 @@ where } } -/// A struct that is used for retrieving rust type ids of types that implement `TableProxy`. +/// ECS resource that can be used to lookup types via name. +/// +/// You can get the [`TypeId`] of the type via name, or the [`ComponentInfo`]. #[derive(Default)] -pub struct LuaTableProxyLookup { +pub struct TypeLookup { pub(crate) typeid_from_name: HashMap, pub(crate) comp_info_from_name: HashMap, } diff --git a/lyra-scripting/src/lua/world.rs b/lyra-scripting/src/lua/world.rs index 4f75d8f..ddaf43d 100644 --- a/lyra-scripting/src/lua/world.rs +++ b/lyra-scripting/src/lua/world.rs @@ -10,7 +10,7 @@ use lyra_resource::ResourceManager; use mlua::{IntoLua, ObjectLike}; use super::{ - reflect_user_data, wrappers::LuaResHandleToComponent, Error, LuaTableProxyLookup, ReflectLuaProxy, ReflectedIterator, FN_NAME_INTERNAL_AS_COMPONENT, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE + reflect_user_data, wrappers::LuaResHandleToComponent, Error, TypeLookup, ReflectLuaProxy, ReflectedIterator, FN_NAME_INTERNAL_AS_COMPONENT, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE }; impl mlua::FromLua for ScriptEntity { @@ -115,7 +115,7 @@ impl mlua::UserData for ScriptWorldPtr { mlua::Value::Table(t) => { let name: String = t.get(mlua::MetaMethod::Type.name())?; - let lookup = world.get_resource::().ok_or( + let lookup = world.get_resource::().ok_or( mlua::Error::runtime( "Unable to lookup table proxy, none were ever registered!", ), @@ -195,7 +195,7 @@ impl mlua::UserData for ScriptWorldPtr { mlua::Value::Table(tbl) => { let name: String = tbl.get(mlua::MetaMethod::Type.name())?; - let lookup = world.get_resource::().unwrap(); + let lookup = world.get_resource::().unwrap(); *lookup.typeid_from_name.get(&name).unwrap() } _ => { -- 2.40.1 From 8e56ee1f0fe597816a67cae51202c3aa91db8121 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Fri, 11 Oct 2024 20:49:00 -0400 Subject: [PATCH 12/15] lua: start exposing events --- lyra-game/src/input/mod.rs | 1493 +++++++++++++++-- lyra-game/src/winit/mod.rs | 2 + lyra-game/src/winit/plugin.rs | 7 +- .../lyra-scripting-derive/src/lua_macro.rs | 34 +- lyra-scripting/src/lua/proxy.rs | 6 +- lyra-scripting/src/lua/wrappers/events.rs | 318 ++++ lyra-scripting/src/lua/wrappers/mod.rs | 5 +- 7 files changed, 1693 insertions(+), 172 deletions(-) create mode 100644 lyra-scripting/src/lua/wrappers/events.rs diff --git a/lyra-game/src/input/mod.rs b/lyra-game/src/input/mod.rs index 274c34a..adab4cb 100644 --- a/lyra-game/src/input/mod.rs +++ b/lyra-game/src/input/mod.rs @@ -10,164 +10,1343 @@ pub use buttons::*; mod action; pub use action::*; -pub type KeyCode = winit::keyboard::KeyCode; +/// Contains the platform-native physical key identifier +/// +/// The exact values vary from platform to platform (which is part of why this is a per-platform +/// enum), but the values are primarily tied to the key's physical location on the keyboard. +/// +/// This enum is primarily used to store raw keycodes when Winit doesn't map a given native +/// physical key identifier to a meaningful [`KeyCode`] variant. In the presence of identifiers we +/// haven't mapped for you yet, this lets you use use [`KeyCode`] to: +/// +/// - Correctly match key press and release events. +/// - On non-web platforms, support assigning keybinds to virtually any key through a UI. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum NativeKeyCode { + Unidentified, + /// An Android "scancode". + Android(u32), + /// A macOS "scancode". + MacOS(u16), + /// A Windows "scancode". + Windows(u16), + /// An XKB "keycode". + Xkb(u32), +} + +impl From for NativeKeyCode { + fn from(value: winit::keyboard::NativeKeyCode) -> Self { + match value { + winit::keyboard::NativeKeyCode::Unidentified => Self::Unidentified, + winit::keyboard::NativeKeyCode::Android(v) => Self::Android(v), + winit::keyboard::NativeKeyCode::MacOS(v) => Self::MacOS(v), + winit::keyboard::NativeKeyCode::Windows(v) => Self::Windows(v), + winit::keyboard::NativeKeyCode::Xkb(v) => Self::Xkb(v), + } + } +} + +impl Into for NativeKeyCode { + fn into(self) -> winit::keyboard::NativeKeyCode { + match self { + NativeKeyCode::Unidentified => winit::keyboard::NativeKeyCode::Unidentified, + NativeKeyCode::Android(v) => winit::keyboard::NativeKeyCode::Android(v), + NativeKeyCode::MacOS(v) => winit::keyboard::NativeKeyCode::MacOS(v), + NativeKeyCode::Windows(v) => winit::keyboard::NativeKeyCode::Windows(v), + NativeKeyCode::Xkb(v) => winit::keyboard::NativeKeyCode::Xkb(v), + } + } +} -/// 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::Digit1), - "2" => Some(KeyCode::Digit2), - "3" => Some(KeyCode::Digit3), - "4" => Some(KeyCode::Digit4), - "5" => Some(KeyCode::Digit5), - "6" => Some(KeyCode::Digit6), - "7" => Some(KeyCode::Digit7), - "8" => Some(KeyCode::Digit8), - "9" => Some(KeyCode::Digit9), - "0" => Some(KeyCode::Digit0), - "a" => Some(KeyCode::KeyA), - "b" => Some(KeyCode::KeyB), - "c" => Some(KeyCode::KeyC), - "d" => Some(KeyCode::KeyD), - "e" => Some(KeyCode::KeyE), - "f" => Some(KeyCode::KeyF), - "g" => Some(KeyCode::KeyG), - "h" => Some(KeyCode::KeyH), - "i" => Some(KeyCode::KeyI), - "j" => Some(KeyCode::KeyJ), - "k" => Some(KeyCode::KeyK), - "l" => Some(KeyCode::KeyL), - "m" => Some(KeyCode::KeyM), - "n" => Some(KeyCode::KeyN), - "o" => Some(KeyCode::KeyO), - "p" => Some(KeyCode::KeyP), - "q" => Some(KeyCode::KeyQ), - "r" => Some(KeyCode::KeyR), - "s" => Some(KeyCode::KeyS), - "t" => Some(KeyCode::KeyT), - "u" => Some(KeyCode::KeyU), - "v" => Some(KeyCode::KeyV), - "w" => Some(KeyCode::KeyW), - "x" => Some(KeyCode::KeyX), - "y" => Some(KeyCode::KeyY), - "z" => Some(KeyCode::KeyZ), - "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), - "print_screen" => Some(KeyCode::PrintScreen), - "scroll_lock" => Some(KeyCode::ScrollLock), - "pause" => Some(KeyCode::Pause), - "insert" => Some(KeyCode::Insert), - "home" => Some(KeyCode::Home), - "delete" => Some(KeyCode::Delete), - "end" => Some(KeyCode::End), - "page_down" => Some(KeyCode::PageDown), - "page_up" => Some(KeyCode::PageUp), - "left" => Some(KeyCode::ArrowLeft), - "up" => Some(KeyCode::ArrowUp), - "right" => Some(KeyCode::ArrowRight), - "down" => Some(KeyCode::ArrowDown), - "backspace" => Some(KeyCode::Backspace), - "enter" => Some(KeyCode::Enter), - "space" => Some(KeyCode::Space), - "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), - "numpad_add" => Some(KeyCode::NumpadAdd), - "numpad_divide" => Some(KeyCode::NumpadDivide), - "numpad_decimal" => Some(KeyCode::NumpadDecimal), - "numpad_comma" => Some(KeyCode::NumpadComma), - "numpad_enter" => Some(KeyCode::NumpadEnter), - "numpad_multiply" => Some(KeyCode::NumpadMultiply), - "numpad_subtract" => Some(KeyCode::NumpadSubtract), - "numpad_star" => Some(KeyCode::NumpadStar), - "quote" => Some(KeyCode::Quote), - "launch_app1" => Some(KeyCode::LaunchApp1), - "launch_app2" => Some(KeyCode::LaunchApp2), - "backslash" => Some(KeyCode::Backslash), - "caps_lock" => Some(KeyCode::CapsLock), - "comma" => Some(KeyCode::Comma), - "convert" => Some(KeyCode::Convert), - "equal" => Some(KeyCode::Equal), - "grave" | "backquote" => Some(KeyCode::Backquote), - "kana_mode" => Some(KeyCode::KanaMode), - "katakana" => Some(KeyCode::Katakana), - "alt_left" => Some(KeyCode::AltLeft), - "alt_right" => Some(KeyCode::AltRight), - "bracket_left" => Some(KeyCode::BracketLeft), - "bracket_right" => Some(KeyCode::BracketRight), - "control_left" => Some(KeyCode::ControlLeft), - "control-right" => Some(KeyCode::ControlRight), - "shift_left" => Some(KeyCode::ShiftLeft), - "shift_right" => Some(KeyCode::ShiftRight), - "meta" => Some(KeyCode::Meta), - "mail" => Some(KeyCode::LaunchMail), - "media_select" => Some(KeyCode::MediaSelect), - "media_stop" => Some(KeyCode::MediaStop), - "stop" => Some(KeyCode::MediaStop), - "track_next" => Some(KeyCode::MediaTrackNext), - "track_prev" => Some(KeyCode::MediaTrackPrevious), - "minus" => Some(KeyCode::Minus), - "mute" => Some(KeyCode::AudioVolumeMute), - "browser_forward" => Some(KeyCode::BrowserForward), - "browser_back" => Some(KeyCode::BrowserBack), - "webfavorites" => Some(KeyCode::BrowserFavorites), - "webhome" => Some(KeyCode::BrowserHome), - "webrefresh" => Some(KeyCode::BrowserRefresh), - "websearch" => Some(KeyCode::BrowserSearch), - "webstop" => Some(KeyCode::BrowserStop), - "non_convert" => Some(KeyCode::NonConvert), - "period" => Some(KeyCode::Period), - "play_pause" => Some(KeyCode::MediaPlayPause), - "plus" => Some(KeyCode::NumpadAdd), - "power" => Some(KeyCode::Power), - "semicolon" => Some(KeyCode::Semicolon), - "slash" => Some(KeyCode::Slash), - "sleep" => Some(KeyCode::Sleep), - "tab" => Some(KeyCode::Tab), - "volume_down" => Some(KeyCode::AudioVolumeDown), - "volume_up" => Some(KeyCode::AudioVolumeUp), - "wake_up" => Some(KeyCode::WakeUp), - "yen" => Some(KeyCode::IntlYen), - "copy" => Some(KeyCode::Copy), - "paste" => Some(KeyCode::Paste), - "cut" => Some(KeyCode::Cut), - _ => None +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum KeyCode { + Unknown(NativeKeyCode), + /// ` on a US keyboard. This is also called a backtick or grave. + /// This is the 半角/全角/漢字 + /// (hankaku/zenkaku/kanji) key on Japanese keyboards + Backquote, + /// Used for both the US \\ (on the 101-key layout) and also for the key + /// located between the " and Enter keys on row C of the 102-, + /// 104- and 106-key layouts. + /// Labeled # on a UK (102) keyboard. + Backslash, + /// [ on a US keyboard. + BracketLeft, + /// ] on a US keyboard. + BracketRight, + /// , on a US keyboard. + Comma, + /// 0 on a US keyboard. + Digit0, + /// 1 on a US keyboard. + Digit1, + /// 2 on a US keyboard. + Digit2, + /// 3 on a US keyboard. + Digit3, + /// 4 on a US keyboard. + Digit4, + /// 5 on a US keyboard. + Digit5, + /// 6 on a US keyboard. + Digit6, + /// 7 on a US keyboard. + Digit7, + /// 8 on a US keyboard. + Digit8, + /// 9 on a US keyboard. + Digit9, + /// = on a US keyboard. + Equal, + /// Located between the left Shift and Z keys. + /// Labeled \\ on a UK keyboard. + IntlBackslash, + /// Located between the / and right Shift keys. + /// Labeled \\ (ro) on a Japanese keyboard. + IntlRo, + /// Located between the = and Backspace keys. + /// Labeled ¥ (yen) on a Japanese keyboard. \\ on a + /// Russian keyboard. + IntlYen, + /// a on a US keyboard. + /// Labeled q on an AZERTY (e.g., French) keyboard. + KeyA, + /// b on a US keyboard. + KeyB, + /// c on a US keyboard. + KeyC, + /// d on a US keyboard. + KeyD, + /// e on a US keyboard. + KeyE, + /// f on a US keyboard. + KeyF, + /// g on a US keyboard. + KeyG, + /// h on a US keyboard. + KeyH, + /// i on a US keyboard. + KeyI, + /// j on a US keyboard. + KeyJ, + /// k on a US keyboard. + KeyK, + /// l on a US keyboard. + KeyL, + /// m on a US keyboard. + KeyM, + /// n on a US keyboard. + KeyN, + /// o on a US keyboard. + KeyO, + /// p on a US keyboard. + KeyP, + /// q on a US keyboard. + /// Labeled a on an AZERTY (e.g., French) keyboard. + KeyQ, + /// r on a US keyboard. + KeyR, + /// s on a US keyboard. + KeyS, + /// t on a US keyboard. + KeyT, + /// u on a US keyboard. + KeyU, + /// v on a US keyboard. + KeyV, + /// w on a US keyboard. + /// Labeled z on an AZERTY (e.g., French) keyboard. + KeyW, + /// x on a US keyboard. + KeyX, + /// y on a US keyboard. + /// Labeled z on a QWERTZ (e.g., German) keyboard. + KeyY, + /// z on a US keyboard. + /// Labeled w on an AZERTY (e.g., French) keyboard, and y on a + /// QWERTZ (e.g., German) keyboard. + KeyZ, + /// - on a US keyboard. + Minus, + /// . on a US keyboard. + Period, + /// ' on a US keyboard. + Quote, + /// ; on a US keyboard. + Semicolon, + /// / on a US keyboard. + Slash, + /// Alt, Option, or . + AltLeft, + /// Alt, Option, or . + /// This is labeled AltGr on many keyboard layouts. + AltRight, + /// Backspace or . + /// Labeled Delete on Apple keyboards. + Backspace, + /// CapsLock or + CapsLock, + /// The application context menu key, which is typically found between the right + /// Super key and the right Control key. + ContextMenu, + /// Control or + ControlLeft, + /// Control or + ControlRight, + /// Enter or . Labeled Return on Apple keyboards. + Enter, + /// The Windows, , Command, or other OS symbol key. + SuperLeft, + /// The Windows, , Command, or other OS symbol key. + SuperRight, + /// Shift or + ShiftLeft, + /// Shift or + ShiftRight, + ///   (space) + Space, + /// Tab or + Tab, + /// Japanese: (henkan) + Convert, + /// Japanese: カタカナ/ひらがな/ローマ字 + /// (katakana/hiragana/romaji) + KanaMode, + /// Korean: HangulMode 한/영 (han/yeong) + /// + /// Japanese (Mac keyboard): (kana) + Lang1, + /// Korean: Hanja (hanja) + /// + /// Japanese (Mac keyboard): (eisu) + Lang2, + /// Japanese (word-processing keyboard): Katakana + Lang3, + /// Japanese (word-processing keyboard): Hiragana + Lang4, + /// Japanese (word-processing keyboard): Zenkaku/Hankaku + Lang5, + /// Japanese: 無変換 (muhenkan) + NonConvert, + /// . The forward delete key. + /// Note that on Apple keyboards, the key labelled Delete on the main part of + /// the keyboard is encoded as [`Backspace`]. + /// + /// [`Backspace`]: Self::Backspace + Delete, + /// Page Down, End, or + End, + /// Help. Not present on standard PC keyboards. + Help, + /// Home or + Home, + /// Insert or Ins. Not present on Apple keyboards. + Insert, + /// Page Down, PgDn, or + PageDown, + /// Page Up, PgUp, or + PageUp, + /// + ArrowDown, + /// + ArrowLeft, + /// + ArrowRight, + /// + ArrowUp, + /// On the Mac, this is used for the numpad Clear key. + NumLock, + /// 0 Ins on a keyboard. 0 on a phone or remote control + Numpad0, + /// 1 End on a keyboard. 1 or 1 QZ on a phone or remote + /// control + Numpad1, + /// 2 ↓ on a keyboard. 2 ABC on a phone or remote control + Numpad2, + /// 3 PgDn on a keyboard. 3 DEF on a phone or remote control + Numpad3, + /// 4 ← on a keyboard. 4 GHI on a phone or remote control + Numpad4, + /// 5 on a keyboard. 5 JKL on a phone or remote control + Numpad5, + /// 6 → on a keyboard. 6 MNO on a phone or remote control + Numpad6, + /// 7 Home on a keyboard. 7 PQRS or 7 PRS on a phone + /// or remote control + Numpad7, + /// 8 ↑ on a keyboard. 8 TUV on a phone or remote control + Numpad8, + /// 9 PgUp on a keyboard. 9 WXYZ or 9 WXY on a phone + /// or remote control + Numpad9, + /// + + NumpadAdd, + /// Found on the Microsoft Natural Keyboard. + NumpadBackspace, + /// C or A (All Clear). Also for use with numpads that have a + /// Clear key that is separate from the NumLock key. On the Mac, the + /// numpad Clear key is encoded as [`NumLock`]. + /// + /// [`NumLock`]: Self::NumLock + NumpadClear, + /// C (Clear Entry) + NumpadClearEntry, + /// , (thousands separator). For locales where the thousands separator + /// is a "." (e.g., Brazil), this key may generate a .. + NumpadComma, + /// . Del. For locales where the decimal separator is "," (e.g., + /// Brazil), this key may generate a ,. + NumpadDecimal, + /// / + NumpadDivide, + NumpadEnter, + /// = + NumpadEqual, + /// # on a phone or remote control device. This key is typically found + /// below the 9 key and to the right of the 0 key. + NumpadHash, + /// M Add current entry to the value stored in memory. + NumpadMemoryAdd, + /// M Clear the value stored in memory. + NumpadMemoryClear, + /// M Replace the current entry with the value stored in memory. + NumpadMemoryRecall, + /// M Replace the value stored in memory with the current entry. + NumpadMemoryStore, + /// M Subtract current entry from the value stored in memory. + NumpadMemorySubtract, + /// * on a keyboard. For use with numpads that provide mathematical + /// operations (+, - * and /). + /// + /// Use `NumpadStar` for the * key on phones and remote controls. + NumpadMultiply, + /// ( Found on the Microsoft Natural Keyboard. + NumpadParenLeft, + /// ) Found on the Microsoft Natural Keyboard. + NumpadParenRight, + /// * on a phone or remote control device. + /// + /// This key is typically found below the 7 key and to the left of + /// the 0 key. + /// + /// Use "NumpadMultiply" for the * key on + /// numeric keypads. + NumpadStar, + /// - + NumpadSubtract, + /// Esc or + Escape, + /// Fn This is typically a hardware key that does not generate a separate code. + Fn, + /// FLock or FnLock. Function Lock key. Found on the Microsoft + /// Natural Keyboard. + FnLock, + /// PrtScr SysRq or Print Screen + PrintScreen, + /// Scroll Lock + ScrollLock, + /// Pause Break + Pause, + /// Some laptops place this key to the left of the key. + /// + /// This also the "back" button (triangle) on Android. + BrowserBack, + BrowserFavorites, + /// Some laptops place this key to the right of the key. + BrowserForward, + /// The "home" button on Android. + BrowserHome, + BrowserRefresh, + BrowserSearch, + BrowserStop, + /// Eject or . This key is placed in the function section on some Apple + /// keyboards. + Eject, + /// Sometimes labelled My Computer on the keyboard + LaunchApp1, + /// Sometimes labelled Calculator on the keyboard + LaunchApp2, + LaunchMail, + MediaPlayPause, + MediaSelect, + MediaStop, + MediaTrackNext, + MediaTrackPrevious, + /// This key is placed in the function section on some Apple keyboards, replacing the + /// Eject key. + Power, + Sleep, + AudioVolumeDown, + AudioVolumeMute, + AudioVolumeUp, + WakeUp, + // Legacy modifier key. Also called "Super" in certain places. + Meta, + // Legacy modifier key. + Hyper, + Turbo, + Abort, + Resume, + Suspend, + /// Found on Sun’s USB keyboard. + Again, + /// Found on Sun’s USB keyboard. + Copy, + /// Found on Sun’s USB keyboard. + Cut, + /// Found on Sun’s USB keyboard. + Find, + /// Found on Sun’s USB keyboard. + Open, + /// Found on Sun’s USB keyboard. + Paste, + /// Found on Sun’s USB keyboard. + Props, + /// Found on Sun’s USB keyboard. + Select, + /// Found on Sun’s USB keyboard. + Undo, + /// Use for dedicated ひらがな key found on some Japanese word processing keyboards. + Hiragana, + /// Use for dedicated カタカナ key found on some Japanese word processing keyboards. + Katakana, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F1, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F2, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F3, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F4, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F5, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F6, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F7, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F8, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F9, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F10, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F11, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F12, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F13, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F14, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F15, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F16, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F17, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F18, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F19, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F20, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F21, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F22, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F23, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F24, + /// General-purpose function key. + F25, + /// General-purpose function key. + F26, + /// General-purpose function key. + F27, + /// General-purpose function key. + F28, + /// General-purpose function key. + F29, + /// General-purpose function key. + F30, + /// General-purpose function key. + F31, + /// General-purpose function key. + F32, + /// General-purpose function key. + F33, + /// General-purpose function key. + F34, + /// General-purpose function key. + F35, +} + +impl KeyCode { + /// Create [`KeyCode`] from [`winit::event::RawKeyEvent`]. + pub fn from_raw_event(raw_ev: &winit::event::RawKeyEvent) -> Self { + match raw_ev.physical_key { + winit::keyboard::PhysicalKey::Code(c) => Self::from(c), + winit::keyboard::PhysicalKey::Unidentified(n) => Self::Unknown(NativeKeyCode::from(n)), + } + } + + pub fn as_physical_key(&self) -> winit::keyboard::PhysicalKey { + match self { + KeyCode::Unknown(v) => winit::keyboard::PhysicalKey::Unidentified((*v).into()), + _ => winit::keyboard::PhysicalKey::Code(self.into()) + } + } + + /// If the key code is unknown, return the [`NativeKeyCode`]. + pub fn get_unknown(&self) -> Option<&NativeKeyCode> { + match self { + KeyCode::Unknown(u) => Some(u), + _ => None, + } + } + + /// Parses a [`KeyCode`] from a [`&str`]. + /// + /// The input is expected to be lowercased and snake_case. So `KeyCode::BracketLeft` would be + /// `bracket_left`. + /// + /// There are some changes to a few keycodes. All the number keys (`Digit1`, `Digit2`, etc.), have + /// the `Digit` prefix removed; so they are be `1`, `2`, etc. The same thing was changed with + /// the letter keys (`KeyA` is `a`, `KeyB` is `b`, etc.). + pub fn from_str(input: &str) -> Option { + match input { + "backquote" => Some(KeyCode::Backquote), + "backslash" => Some(KeyCode::Backslash), + "bracket_left" => Some(KeyCode::BracketLeft), + "bracket_right" => Some(KeyCode::BracketRight), + "comma" => Some(KeyCode::Comma), + "0" => Some(KeyCode::Digit0), + "1" => Some(KeyCode::Digit1), + "2" => Some(KeyCode::Digit2), + "3" => Some(KeyCode::Digit3), + "4" => Some(KeyCode::Digit4), + "5" => Some(KeyCode::Digit5), + "6" => Some(KeyCode::Digit6), + "7" => Some(KeyCode::Digit7), + "8" => Some(KeyCode::Digit8), + "9" => Some(KeyCode::Digit9), + "equal" => Some(KeyCode::Equal), + "intl_backslash" => Some(KeyCode::IntlBackslash), + "intl_ro" => Some(KeyCode::IntlRo), + "intl_yen" => Some(KeyCode::IntlYen), + "a" => Some(KeyCode::KeyA), + "b" => Some(KeyCode::KeyB), + "c" => Some(KeyCode::KeyC), + "d" => Some(KeyCode::KeyD), + "e" => Some(KeyCode::KeyE), + "f" => Some(KeyCode::KeyF), + "g" => Some(KeyCode::KeyG), + "h" => Some(KeyCode::KeyH), + "i" => Some(KeyCode::KeyI), + "j" => Some(KeyCode::KeyJ), + "k" => Some(KeyCode::KeyK), + "l" => Some(KeyCode::KeyL), + "m" => Some(KeyCode::KeyM), + "n" => Some(KeyCode::KeyN), + "o" => Some(KeyCode::KeyO), + "p" => Some(KeyCode::KeyP), + "q" => Some(KeyCode::KeyQ), + "r" => Some(KeyCode::KeyR), + "s" => Some(KeyCode::KeyS), + "t" => Some(KeyCode::KeyT), + "u" => Some(KeyCode::KeyU), + "v" => Some(KeyCode::KeyV), + "w" => Some(KeyCode::KeyW), + "x" => Some(KeyCode::KeyX), + "y" => Some(KeyCode::KeyY), + "z" => Some(KeyCode::KeyZ), + "minus" => Some(KeyCode::Minus), + "period" => Some(KeyCode::Period), + "quote" => Some(KeyCode::Quote), + "semicolon" => Some(KeyCode::Semicolon), + "slash" => Some(KeyCode::Slash), + "alt_left" => Some(KeyCode::AltLeft), + "alt_right" => Some(KeyCode::AltRight), + "backspace" => Some(KeyCode::Backspace), + "caps_lock" => Some(KeyCode::CapsLock), + "context_menu" => Some(KeyCode::ContextMenu), + "control_left" => Some(KeyCode::ControlLeft), + "control_right" => Some(KeyCode::ControlRight), + "enter" => Some(KeyCode::Enter), + "super_left" => Some(KeyCode::SuperLeft), + "super_right" => Some(KeyCode::SuperRight), + "shift_left" => Some(KeyCode::ShiftLeft), + "shift_right" => Some(KeyCode::ShiftRight), + "space" => Some(KeyCode::Space), + "tab" => Some(KeyCode::Tab), + "convert" => Some(KeyCode::Convert), + "kana_mode" => Some(KeyCode::KanaMode), + "lang_1" => Some(KeyCode::Lang1), + "lang_2" => Some(KeyCode::Lang2), + "lang_3" => Some(KeyCode::Lang3), + "lang_4" => Some(KeyCode::Lang4), + "lang_5" => Some(KeyCode::Lang5), + "non_convert" => Some(KeyCode::NonConvert), + "delete" => Some(KeyCode::Delete), + "end" => Some(KeyCode::End), + "help" => Some(KeyCode::Help), + "home" => Some(KeyCode::Home), + "insert" => Some(KeyCode::Insert), + "page_down" => Some(KeyCode::PageDown), + "page_up" => Some(KeyCode::PageUp), + "arrow_down" => Some(KeyCode::ArrowDown), + "arrow_left" => Some(KeyCode::ArrowLeft), + "arrow_right" => Some(KeyCode::ArrowRight), + "arrow_up" => Some(KeyCode::ArrowUp), + "num_lock" => Some(KeyCode::NumLock), + "numpad_0" => Some(KeyCode::Numpad0), + "numpad_1" => Some(KeyCode::Numpad1), + "numpad_2" => Some(KeyCode::Numpad2), + "numpad_3" => Some(KeyCode::Numpad3), + "numpad_4" => Some(KeyCode::Numpad4), + "numpad_5" => Some(KeyCode::Numpad5), + "numpad_6" => Some(KeyCode::Numpad6), + "numpad_7" => Some(KeyCode::Numpad7), + "numpad_8" => Some(KeyCode::Numpad8), + "numpad_9" => Some(KeyCode::Numpad9), + "numpad_add" => Some(KeyCode::NumpadAdd), + "numpad_backspace" => Some(KeyCode::NumpadBackspace), + "numpad_clear" => Some(KeyCode::NumpadClear), + "numpad_clear_entry" => Some(KeyCode::NumpadClearEntry), + "numpad_comma" => Some(KeyCode::NumpadComma), + "numpad_decimal" => Some(KeyCode::NumpadDecimal), + "numpad_divide" => Some(KeyCode::NumpadDivide), + "numpad_enter" => Some(KeyCode::NumpadEnter), + "numpad_equal" => Some(KeyCode::NumpadEqual), + "numpad_hash" => Some(KeyCode::NumpadHash), + "numpad_memory_add" => Some(KeyCode::NumpadMemoryAdd), + "numpad_memory_clear" => Some(KeyCode::NumpadMemoryClear), + "numpad_memory_recall" => Some(KeyCode::NumpadMemoryRecall), + "numpad_memory_store" => Some(KeyCode::NumpadMemoryStore), + "numpad_memory_subtract" => Some(KeyCode::NumpadMemorySubtract), + "numpad_multiply" => Some(KeyCode::NumpadMultiply), + "numpad_paren_left" => Some(KeyCode::NumpadParenLeft), + "numpad_paren_right" => Some(KeyCode::NumpadParenRight), + "numpad_star" => Some(KeyCode::NumpadStar), + "numpad_subtract" => Some(KeyCode::NumpadSubtract), + "escape" => Some(KeyCode::Escape), + "fn" => Some(KeyCode::Fn), + "fn_lock" => Some(KeyCode::FnLock), + "print_screen" => Some(KeyCode::PrintScreen), + "scroll_lock" => Some(KeyCode::ScrollLock), + "pause" => Some(KeyCode::Pause), + "browser_back" => Some(KeyCode::BrowserBack), + "browser_favorites" => Some(KeyCode::BrowserFavorites), + "browser_forward" => Some(KeyCode::BrowserForward), + "browser_home" => Some(KeyCode::BrowserHome), + "browser_refresh" => Some(KeyCode::BrowserRefresh), + "browser_search" => Some(KeyCode::BrowserSearch), + "browser_stop" => Some(KeyCode::BrowserStop), + "eject" => Some(KeyCode::Eject), + "launch_app_1" => Some(KeyCode::LaunchApp1), + "launch_app_2" => Some(KeyCode::LaunchApp2), + "launch_mail" => Some(KeyCode::LaunchMail), + "media_play_pause" => Some(KeyCode::MediaPlayPause), + "media_select" => Some(KeyCode::MediaSelect), + "media_stop" => Some(KeyCode::MediaStop), + "media_track_next" => Some(KeyCode::MediaTrackNext), + "media_track_previous" => Some(KeyCode::MediaTrackPrevious), + "power" => Some(KeyCode::Power), + "sleep" => Some(KeyCode::Sleep), + "audio_volume_down" => Some(KeyCode::AudioVolumeDown), + "audio_volume_mute" => Some(KeyCode::AudioVolumeMute), + "audio_volume_up" => Some(KeyCode::AudioVolumeUp), + "wake_up" => Some(KeyCode::WakeUp), + "meta" => Some(KeyCode::Meta), + "hyper" => Some(KeyCode::Hyper), + "turbo" => Some(KeyCode::Turbo), + "abort" => Some(KeyCode::Abort), + "resume" => Some(KeyCode::Resume), + "suspend" => Some(KeyCode::Suspend), + "again" => Some(KeyCode::Again), + "copy" => Some(KeyCode::Copy), + "cut" => Some(KeyCode::Cut), + "find" => Some(KeyCode::Find), + "open" => Some(KeyCode::Open), + "paste" => Some(KeyCode::Paste), + "props" => Some(KeyCode::Props), + "select" => Some(KeyCode::Select), + "undo" => Some(KeyCode::Undo), + "hiragana" => Some(KeyCode::Hiragana), + "katakana" => Some(KeyCode::Katakana), + "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), + "f25" => Some(KeyCode::F25), + "f26" => Some(KeyCode::F26), + "f27" => Some(KeyCode::F27), + "f28" => Some(KeyCode::F28), + "f29" => Some(KeyCode::F29), + "f30" => Some(KeyCode::F30), + "f31" => Some(KeyCode::F31), + "f32" => Some(KeyCode::F32), + "f33" => Some(KeyCode::F33), + "f34" => Some(KeyCode::F34), + "f35" => Some(KeyCode::F35), + _ => None, + } + } + + /// Converts `self` to a `str`.` + /// + /// The output will be lower cased snake_case. So `KeyCode::BracketLeft` would be + /// `bracket_left`. Returns `None` if self is `KeyCode::Unknown`. + /// + /// The returned str does not match one-to-one to the enum. The digit keys + /// (`Digit1`, `Digit2`, etc.) have `digit` removed, so those variants just return + /// `1`, `2`, etc. The same was done for the letter keys (`KeyA`, `KeyB`, etc.). + pub fn as_str(&self) -> Option<&'static str> { + match self { + KeyCode::Backquote => Some("backquote"), + KeyCode::Backslash => Some("backslash"), + KeyCode::BracketLeft => Some("bracket_left"), + KeyCode::BracketRight => Some("bracket_right"), + KeyCode::Comma => Some("comma"), + KeyCode::Digit0 => Some("0"), + KeyCode::Digit1 => Some("1"), + KeyCode::Digit2 => Some("2"), + KeyCode::Digit3 => Some("3"), + KeyCode::Digit4 => Some("4"), + KeyCode::Digit5 => Some("5"), + KeyCode::Digit6 => Some("6"), + KeyCode::Digit7 => Some("7"), + KeyCode::Digit8 => Some("8"), + KeyCode::Digit9 => Some("9"), + KeyCode::Equal => Some("equal"), + KeyCode::IntlBackslash => Some("intl_backslash"), + KeyCode::IntlRo => Some("intl_ro"), + KeyCode::IntlYen => Some("intl_yen"), + KeyCode::KeyA => Some("a"), + KeyCode::KeyB => Some("b"), + KeyCode::KeyC => Some("c"), + KeyCode::KeyD => Some("d"), + KeyCode::KeyE => Some("e"), + KeyCode::KeyF => Some("f"), + KeyCode::KeyG => Some("g"), + KeyCode::KeyH => Some("h"), + KeyCode::KeyI => Some("i"), + KeyCode::KeyJ => Some("j"), + KeyCode::KeyK => Some("k"), + KeyCode::KeyL => Some("l"), + KeyCode::KeyM => Some("m"), + KeyCode::KeyN => Some("n"), + KeyCode::KeyO => Some("o"), + KeyCode::KeyP => Some("p"), + KeyCode::KeyQ => Some("q"), + KeyCode::KeyR => Some("r"), + KeyCode::KeyS => Some("s"), + KeyCode::KeyT => Some("t"), + KeyCode::KeyU => Some("u"), + KeyCode::KeyV => Some("v"), + KeyCode::KeyW => Some("w"), + KeyCode::KeyX => Some("x"), + KeyCode::KeyY => Some("y"), + KeyCode::KeyZ => Some("z"), + KeyCode::Minus => Some("minus"), + KeyCode::Period => Some("period"), + KeyCode::Quote => Some("quote"), + KeyCode::Semicolon => Some("semicolon"), + KeyCode::Slash => Some("slash"), + KeyCode::AltLeft => Some("alt_left"), + KeyCode::AltRight => Some("alt_right"), + KeyCode::Backspace => Some("backspace"), + KeyCode::CapsLock => Some("caps_lock"), + KeyCode::ContextMenu => Some("context_menu"), + KeyCode::ControlLeft => Some("control_left"), + KeyCode::ControlRight => Some("control_right"), + KeyCode::Enter => Some("enter"), + KeyCode::SuperLeft => Some("super_left"), + KeyCode::SuperRight => Some("super_right"), + KeyCode::ShiftLeft => Some("shift_left"), + KeyCode::ShiftRight => Some("shift_right"), + KeyCode::Space => Some("space"), + KeyCode::Tab => Some("tab"), + KeyCode::Convert => Some("convert"), + KeyCode::KanaMode => Some("kana_mode"), + KeyCode::Lang1 => Some("lang_1"), + KeyCode::Lang2 => Some("lang_2"), + KeyCode::Lang3 => Some("lang_3"), + KeyCode::Lang4 => Some("lang_4"), + KeyCode::Lang5 => Some("lang_5"), + KeyCode::NonConvert => Some("non_convert"), + KeyCode::Delete => Some("delete"), + KeyCode::End => Some("end"), + KeyCode::Help => Some("help"), + KeyCode::Home => Some("home"), + KeyCode::Insert => Some("insert"), + KeyCode::PageDown => Some("page_down"), + KeyCode::PageUp => Some("page_up"), + KeyCode::ArrowDown => Some("arrow_down"), + KeyCode::ArrowLeft => Some("arrow_left"), + KeyCode::ArrowRight => Some("arrow_right"), + KeyCode::ArrowUp => Some("arrow_up"), + KeyCode::NumLock => Some("num_lock"), + KeyCode::Numpad0 => Some("numpad_0"), + KeyCode::Numpad1 => Some("numpad_1"), + KeyCode::Numpad2 => Some("numpad_2"), + KeyCode::Numpad3 => Some("numpad_3"), + KeyCode::Numpad4 => Some("numpad_4"), + KeyCode::Numpad5 => Some("numpad_5"), + KeyCode::Numpad6 => Some("numpad_6"), + KeyCode::Numpad7 => Some("numpad_7"), + KeyCode::Numpad8 => Some("numpad_8"), + KeyCode::Numpad9 => Some("numpad_9"), + KeyCode::NumpadAdd => Some("numpad_add"), + KeyCode::NumpadBackspace => Some("numpad_backspace"), + KeyCode::NumpadClear => Some("numpad_clear"), + KeyCode::NumpadClearEntry => Some("numpad_clear_entry"), + KeyCode::NumpadComma => Some("numpad_comma"), + KeyCode::NumpadDecimal => Some("numpad_decimal"), + KeyCode::NumpadDivide => Some("numpad_divide"), + KeyCode::NumpadEnter => Some("numpad_enter"), + KeyCode::NumpadEqual => Some("numpad_equal"), + KeyCode::NumpadHash => Some("numpad_hash"), + KeyCode::NumpadMemoryAdd => Some("numpad_memory_add"), + KeyCode::NumpadMemoryClear => Some("numpad_memory_clear"), + KeyCode::NumpadMemoryRecall => Some("numpad_memory_recall"), + KeyCode::NumpadMemoryStore => Some("numpad_memory_store"), + KeyCode::NumpadMemorySubtract => Some("numpad_memory_subtract"), + KeyCode::NumpadMultiply => Some("numpad_multiply"), + KeyCode::NumpadParenLeft => Some("numpad_paren_left"), + KeyCode::NumpadParenRight => Some("numpad_paren_right"), + KeyCode::NumpadStar => Some("numpad_star"), + KeyCode::NumpadSubtract => Some("numpad_subtract"), + KeyCode::Escape => Some("escape"), + KeyCode::Fn => Some("fn"), + KeyCode::FnLock => Some("fn_lock"), + KeyCode::PrintScreen => Some("print_screen"), + KeyCode::ScrollLock => Some("scroll_lock"), + KeyCode::Pause => Some("pause"), + KeyCode::BrowserBack => Some("browser_back"), + KeyCode::BrowserFavorites => Some("browser_favorites"), + KeyCode::BrowserForward => Some("browser_forward"), + KeyCode::BrowserHome => Some("browser_home"), + KeyCode::BrowserRefresh => Some("browser_refresh"), + KeyCode::BrowserSearch => Some("browser_search"), + KeyCode::BrowserStop => Some("browser_stop"), + KeyCode::Eject => Some("eject"), + KeyCode::LaunchApp1 => Some("launch_app_1"), + KeyCode::LaunchApp2 => Some("launch_app_2"), + KeyCode::LaunchMail => Some("launch_mail"), + KeyCode::MediaPlayPause => Some("media_play_pause"), + KeyCode::MediaSelect => Some("media_select"), + KeyCode::MediaStop => Some("media_stop"), + KeyCode::MediaTrackNext => Some("media_track_next"), + KeyCode::MediaTrackPrevious => Some("media_track_previous"), + KeyCode::Power => Some("power"), + KeyCode::Sleep => Some("sleep"), + KeyCode::AudioVolumeDown => Some("audio_volume_down"), + KeyCode::AudioVolumeMute => Some("audio_volume_mute"), + KeyCode::AudioVolumeUp => Some("audio_volume_up"), + KeyCode::WakeUp => Some("wake_up"), + KeyCode::Meta => Some("meta"), + KeyCode::Hyper => Some("hyper"), + KeyCode::Turbo => Some("turbo"), + KeyCode::Abort => Some("abort"), + KeyCode::Resume => Some("resume"), + KeyCode::Suspend => Some("suspend"), + KeyCode::Again => Some("again"), + KeyCode::Copy => Some("copy"), + KeyCode::Cut => Some("cut"), + KeyCode::Find => Some("find"), + KeyCode::Open => Some("open"), + KeyCode::Paste => Some("paste"), + KeyCode::Props => Some("props"), + KeyCode::Select => Some("select"), + KeyCode::Undo => Some("undo"), + KeyCode::Hiragana => Some("hiragana"), + KeyCode::Katakana => Some("katakana"), + KeyCode::F1 => Some("f1"), + KeyCode::F2 => Some("f2"), + KeyCode::F3 => Some("f3"), + KeyCode::F4 => Some("f4"), + KeyCode::F5 => Some("f5"), + KeyCode::F6 => Some("f6"), + KeyCode::F7 => Some("f7"), + KeyCode::F8 => Some("f8"), + KeyCode::F9 => Some("f9"), + KeyCode::F10 => Some("f10"), + KeyCode::F11 => Some("f11"), + KeyCode::F12 => Some("f12"), + KeyCode::F13 => Some("f13"), + KeyCode::F14 => Some("f14"), + KeyCode::F15 => Some("f15"), + KeyCode::F16 => Some("f16"), + KeyCode::F17 => Some("f17"), + KeyCode::F18 => Some("f18"), + KeyCode::F19 => Some("f19"), + KeyCode::F20 => Some("f20"), + KeyCode::F21 => Some("f21"), + KeyCode::F22 => Some("f22"), + KeyCode::F23 => Some("f23"), + KeyCode::F24 => Some("f24"), + KeyCode::F25 => Some("f25"), + KeyCode::F26 => Some("f26"), + KeyCode::F27 => Some("f27"), + KeyCode::F28 => Some("f28"), + KeyCode::F29 => Some("f29"), + KeyCode::F30 => Some("f30"), + KeyCode::F31 => Some("f31"), + KeyCode::F32 => Some("f32"), + KeyCode::F33 => Some("f33"), + KeyCode::F34 => Some("f34"), + KeyCode::F35 => Some("f35"), + KeyCode::Unknown(_) => None + } + } + + pub fn as_winit_keycode(&self) -> Option { + match self { + Self::Unknown(_) => None, + Self::Backquote => Some(winit::keyboard::KeyCode::Backquote), + Self::Backslash => Some(winit::keyboard::KeyCode::Backslash), + Self::BracketLeft => Some(winit::keyboard::KeyCode::BracketLeft), + Self::BracketRight => Some(winit::keyboard::KeyCode::BracketRight), + Self::Comma => Some(winit::keyboard::KeyCode::Comma), + Self::Digit0 => Some(winit::keyboard::KeyCode::Digit0), + Self::Digit1 => Some(winit::keyboard::KeyCode::Digit1), + Self::Digit2 => Some(winit::keyboard::KeyCode::Digit2), + Self::Digit3 => Some(winit::keyboard::KeyCode::Digit3), + Self::Digit4 => Some(winit::keyboard::KeyCode::Digit4), + Self::Digit5 => Some(winit::keyboard::KeyCode::Digit5), + Self::Digit6 => Some(winit::keyboard::KeyCode::Digit6), + Self::Digit7 => Some(winit::keyboard::KeyCode::Digit7), + Self::Digit8 => Some(winit::keyboard::KeyCode::Digit8), + Self::Digit9 => Some(winit::keyboard::KeyCode::Digit9), + Self::Equal => Some(winit::keyboard::KeyCode::Equal), + Self::IntlBackslash => Some(winit::keyboard::KeyCode::IntlBackslash), + Self::IntlRo => Some(winit::keyboard::KeyCode::IntlRo), + Self::IntlYen => Some(winit::keyboard::KeyCode::IntlYen), + Self::KeyA => Some(winit::keyboard::KeyCode::KeyA), + Self::KeyB => Some(winit::keyboard::KeyCode::KeyB), + Self::KeyC => Some(winit::keyboard::KeyCode::KeyC), + Self::KeyD => Some(winit::keyboard::KeyCode::KeyD), + Self::KeyE => Some(winit::keyboard::KeyCode::KeyE), + Self::KeyF => Some(winit::keyboard::KeyCode::KeyF), + Self::KeyG => Some(winit::keyboard::KeyCode::KeyG), + Self::KeyH => Some(winit::keyboard::KeyCode::KeyH), + Self::KeyI => Some(winit::keyboard::KeyCode::KeyI), + Self::KeyJ => Some(winit::keyboard::KeyCode::KeyJ), + Self::KeyK => Some(winit::keyboard::KeyCode::KeyK), + Self::KeyL => Some(winit::keyboard::KeyCode::KeyL), + Self::KeyM => Some(winit::keyboard::KeyCode::KeyM), + Self::KeyN => Some(winit::keyboard::KeyCode::KeyN), + Self::KeyO => Some(winit::keyboard::KeyCode::KeyO), + Self::KeyP => Some(winit::keyboard::KeyCode::KeyP), + Self::KeyQ => Some(winit::keyboard::KeyCode::KeyQ), + Self::KeyR => Some(winit::keyboard::KeyCode::KeyR), + Self::KeyS => Some(winit::keyboard::KeyCode::KeyS), + Self::KeyT => Some(winit::keyboard::KeyCode::KeyT), + Self::KeyU => Some(winit::keyboard::KeyCode::KeyU), + Self::KeyV => Some(winit::keyboard::KeyCode::KeyV), + Self::KeyW => Some(winit::keyboard::KeyCode::KeyW), + Self::KeyX => Some(winit::keyboard::KeyCode::KeyX), + Self::KeyY => Some(winit::keyboard::KeyCode::KeyY), + Self::KeyZ => Some(winit::keyboard::KeyCode::KeyZ), + Self::Minus => Some(winit::keyboard::KeyCode::Minus), + Self::Period => Some(winit::keyboard::KeyCode::Period), + Self::Quote => Some(winit::keyboard::KeyCode::Quote), + Self::Semicolon => Some(winit::keyboard::KeyCode::Semicolon), + Self::Slash => Some(winit::keyboard::KeyCode::Slash), + Self::AltLeft => Some(winit::keyboard::KeyCode::AltLeft), + Self::AltRight => Some(winit::keyboard::KeyCode::AltRight), + Self::Backspace => Some(winit::keyboard::KeyCode::Backspace), + Self::CapsLock => Some(winit::keyboard::KeyCode::CapsLock), + Self::ContextMenu => Some(winit::keyboard::KeyCode::ContextMenu), + Self::ControlLeft => Some(winit::keyboard::KeyCode::ControlLeft), + Self::ControlRight => Some(winit::keyboard::KeyCode::ControlRight), + Self::Enter => Some(winit::keyboard::KeyCode::Enter), + Self::SuperLeft => Some(winit::keyboard::KeyCode::SuperLeft), + Self::SuperRight => Some(winit::keyboard::KeyCode::SuperRight), + Self::ShiftLeft => Some(winit::keyboard::KeyCode::ShiftLeft), + Self::ShiftRight => Some(winit::keyboard::KeyCode::ShiftRight), + Self::Space => Some(winit::keyboard::KeyCode::Space), + Self::Tab => Some(winit::keyboard::KeyCode::Tab), + Self::Convert => Some(winit::keyboard::KeyCode::Convert), + Self::KanaMode => Some(winit::keyboard::KeyCode::KanaMode), + Self::Lang1 => Some(winit::keyboard::KeyCode::Lang1), + Self::Lang2 => Some(winit::keyboard::KeyCode::Lang2), + Self::Lang3 => Some(winit::keyboard::KeyCode::Lang3), + Self::Lang4 => Some(winit::keyboard::KeyCode::Lang4), + Self::Lang5 => Some(winit::keyboard::KeyCode::Lang5), + Self::NonConvert => Some(winit::keyboard::KeyCode::NonConvert), + Self::Delete => Some(winit::keyboard::KeyCode::Delete), + Self::End => Some(winit::keyboard::KeyCode::End), + Self::Help => Some(winit::keyboard::KeyCode::Help), + Self::Home => Some(winit::keyboard::KeyCode::Home), + Self::Insert => Some(winit::keyboard::KeyCode::Insert), + Self::PageDown => Some(winit::keyboard::KeyCode::PageDown), + Self::PageUp => Some(winit::keyboard::KeyCode::PageUp), + Self::ArrowDown => Some(winit::keyboard::KeyCode::ArrowDown), + Self::ArrowLeft => Some(winit::keyboard::KeyCode::ArrowLeft), + Self::ArrowRight => Some(winit::keyboard::KeyCode::ArrowRight), + Self::ArrowUp => Some(winit::keyboard::KeyCode::ArrowUp), + Self::NumLock => Some(winit::keyboard::KeyCode::NumLock), + Self::Numpad0 => Some(winit::keyboard::KeyCode::Numpad0), + Self::Numpad1 => Some(winit::keyboard::KeyCode::Numpad1), + Self::Numpad2 => Some(winit::keyboard::KeyCode::Numpad2), + Self::Numpad3 => Some(winit::keyboard::KeyCode::Numpad3), + Self::Numpad4 => Some(winit::keyboard::KeyCode::Numpad4), + Self::Numpad5 => Some(winit::keyboard::KeyCode::Numpad5), + Self::Numpad6 => Some(winit::keyboard::KeyCode::Numpad6), + Self::Numpad7 => Some(winit::keyboard::KeyCode::Numpad7), + Self::Numpad8 => Some(winit::keyboard::KeyCode::Numpad8), + Self::Numpad9 => Some(winit::keyboard::KeyCode::Numpad9), + Self::NumpadAdd => Some(winit::keyboard::KeyCode::NumpadAdd), + Self::NumpadBackspace => Some(winit::keyboard::KeyCode::NumpadBackspace), + Self::NumpadClear => Some(winit::keyboard::KeyCode::NumpadClear), + Self::NumpadClearEntry => Some(winit::keyboard::KeyCode::NumpadClearEntry), + Self::NumpadComma => Some(winit::keyboard::KeyCode::NumpadComma), + Self::NumpadDecimal => Some(winit::keyboard::KeyCode::NumpadDecimal), + Self::NumpadDivide => Some(winit::keyboard::KeyCode::NumpadDivide), + Self::NumpadEnter => Some(winit::keyboard::KeyCode::NumpadEnter), + Self::NumpadEqual => Some(winit::keyboard::KeyCode::NumpadEqual), + Self::NumpadHash => Some(winit::keyboard::KeyCode::NumpadHash), + Self::NumpadMemoryAdd => Some(winit::keyboard::KeyCode::NumpadMemoryAdd), + Self::NumpadMemoryClear => Some(winit::keyboard::KeyCode::NumpadMemoryClear), + Self::NumpadMemoryRecall => Some(winit::keyboard::KeyCode::NumpadMemoryRecall), + Self::NumpadMemoryStore => Some(winit::keyboard::KeyCode::NumpadMemoryStore), + Self::NumpadMemorySubtract => Some(winit::keyboard::KeyCode::NumpadMemorySubtract), + Self::NumpadMultiply => Some(winit::keyboard::KeyCode::NumpadMultiply), + Self::NumpadParenLeft => Some(winit::keyboard::KeyCode::NumpadParenLeft), + Self::NumpadParenRight => Some(winit::keyboard::KeyCode::NumpadParenRight), + Self::NumpadStar => Some(winit::keyboard::KeyCode::NumpadStar), + Self::NumpadSubtract => Some(winit::keyboard::KeyCode::NumpadSubtract), + Self::Escape => Some(winit::keyboard::KeyCode::Escape), + Self::Fn => Some(winit::keyboard::KeyCode::Fn), + Self::FnLock => Some(winit::keyboard::KeyCode::FnLock), + Self::PrintScreen => Some(winit::keyboard::KeyCode::PrintScreen), + Self::ScrollLock => Some(winit::keyboard::KeyCode::ScrollLock), + Self::Pause => Some(winit::keyboard::KeyCode::Pause), + Self::BrowserBack => Some(winit::keyboard::KeyCode::BrowserBack), + Self::BrowserFavorites => Some(winit::keyboard::KeyCode::BrowserFavorites), + Self::BrowserForward => Some(winit::keyboard::KeyCode::BrowserForward), + Self::BrowserHome => Some(winit::keyboard::KeyCode::BrowserHome), + Self::BrowserRefresh => Some(winit::keyboard::KeyCode::BrowserRefresh), + Self::BrowserSearch => Some(winit::keyboard::KeyCode::BrowserSearch), + Self::BrowserStop => Some(winit::keyboard::KeyCode::BrowserStop), + Self::Eject => Some(winit::keyboard::KeyCode::Eject), + Self::LaunchApp1 => Some(winit::keyboard::KeyCode::LaunchApp1), + Self::LaunchApp2 => Some(winit::keyboard::KeyCode::LaunchApp2), + Self::LaunchMail => Some(winit::keyboard::KeyCode::LaunchMail), + Self::MediaPlayPause => Some(winit::keyboard::KeyCode::MediaPlayPause), + Self::MediaSelect => Some(winit::keyboard::KeyCode::MediaSelect), + Self::MediaStop => Some(winit::keyboard::KeyCode::MediaStop), + Self::MediaTrackNext => Some(winit::keyboard::KeyCode::MediaTrackNext), + Self::MediaTrackPrevious => Some(winit::keyboard::KeyCode::MediaTrackPrevious), + Self::Power => Some(winit::keyboard::KeyCode::Power), + Self::Sleep => Some(winit::keyboard::KeyCode::Sleep), + Self::AudioVolumeDown => Some(winit::keyboard::KeyCode::AudioVolumeDown), + Self::AudioVolumeMute => Some(winit::keyboard::KeyCode::AudioVolumeMute), + Self::AudioVolumeUp => Some(winit::keyboard::KeyCode::AudioVolumeUp), + Self::WakeUp => Some(winit::keyboard::KeyCode::WakeUp), + Self::Meta => Some(winit::keyboard::KeyCode::Meta), + Self::Hyper => Some(winit::keyboard::KeyCode::Hyper), + Self::Turbo => Some(winit::keyboard::KeyCode::Turbo), + Self::Abort => Some(winit::keyboard::KeyCode::Abort), + Self::Resume => Some(winit::keyboard::KeyCode::Resume), + Self::Suspend => Some(winit::keyboard::KeyCode::Suspend), + Self::Again => Some(winit::keyboard::KeyCode::Again), + Self::Copy => Some(winit::keyboard::KeyCode::Copy), + Self::Cut => Some(winit::keyboard::KeyCode::Cut), + Self::Find => Some(winit::keyboard::KeyCode::Find), + Self::Open => Some(winit::keyboard::KeyCode::Open), + Self::Paste => Some(winit::keyboard::KeyCode::Paste), + Self::Props => Some(winit::keyboard::KeyCode::Props), + Self::Select => Some(winit::keyboard::KeyCode::Select), + Self::Undo => Some(winit::keyboard::KeyCode::Undo), + Self::Hiragana => Some(winit::keyboard::KeyCode::Hiragana), + Self::Katakana => Some(winit::keyboard::KeyCode::Katakana), + Self::F1 => Some(winit::keyboard::KeyCode::F1), + Self::F2 => Some(winit::keyboard::KeyCode::F2), + Self::F3 => Some(winit::keyboard::KeyCode::F3), + Self::F4 => Some(winit::keyboard::KeyCode::F4), + Self::F5 => Some(winit::keyboard::KeyCode::F5), + Self::F6 => Some(winit::keyboard::KeyCode::F6), + Self::F7 => Some(winit::keyboard::KeyCode::F7), + Self::F8 => Some(winit::keyboard::KeyCode::F8), + Self::F9 => Some(winit::keyboard::KeyCode::F9), + Self::F10 => Some(winit::keyboard::KeyCode::F10), + Self::F11 => Some(winit::keyboard::KeyCode::F11), + Self::F12 => Some(winit::keyboard::KeyCode::F12), + Self::F13 => Some(winit::keyboard::KeyCode::F13), + Self::F14 => Some(winit::keyboard::KeyCode::F14), + Self::F15 => Some(winit::keyboard::KeyCode::F15), + Self::F16 => Some(winit::keyboard::KeyCode::F16), + Self::F17 => Some(winit::keyboard::KeyCode::F17), + Self::F18 => Some(winit::keyboard::KeyCode::F18), + Self::F19 => Some(winit::keyboard::KeyCode::F19), + Self::F20 => Some(winit::keyboard::KeyCode::F20), + Self::F21 => Some(winit::keyboard::KeyCode::F21), + Self::F22 => Some(winit::keyboard::KeyCode::F22), + Self::F23 => Some(winit::keyboard::KeyCode::F23), + Self::F24 => Some(winit::keyboard::KeyCode::F24), + Self::F25 => Some(winit::keyboard::KeyCode::F25), + Self::F26 => Some(winit::keyboard::KeyCode::F26), + Self::F27 => Some(winit::keyboard::KeyCode::F27), + Self::F28 => Some(winit::keyboard::KeyCode::F28), + Self::F29 => Some(winit::keyboard::KeyCode::F29), + Self::F30 => Some(winit::keyboard::KeyCode::F30), + Self::F31 => Some(winit::keyboard::KeyCode::F31), + Self::F32 => Some(winit::keyboard::KeyCode::F32), + Self::F33 => Some(winit::keyboard::KeyCode::F33), + Self::F34 => Some(winit::keyboard::KeyCode::F34), + Self::F35 => Some(winit::keyboard::KeyCode::F35), + } + } +} + +impl From for KeyCode { + fn from(value: winit::keyboard::KeyCode) -> Self { + match value { + winit::keyboard::KeyCode::Backquote => Self::Backquote, + winit::keyboard::KeyCode::Backslash => Self::Backslash, + winit::keyboard::KeyCode::BracketLeft => Self::BracketLeft, + winit::keyboard::KeyCode::BracketRight => Self::BracketRight, + winit::keyboard::KeyCode::Comma => Self::Comma, + winit::keyboard::KeyCode::Digit0 => Self::Digit0, + winit::keyboard::KeyCode::Digit1 => Self::Digit1, + winit::keyboard::KeyCode::Digit2 => Self::Digit2, + winit::keyboard::KeyCode::Digit3 => Self::Digit3, + winit::keyboard::KeyCode::Digit4 => Self::Digit4, + winit::keyboard::KeyCode::Digit5 => Self::Digit5, + winit::keyboard::KeyCode::Digit6 => Self::Digit6, + winit::keyboard::KeyCode::Digit7 => Self::Digit7, + winit::keyboard::KeyCode::Digit8 => Self::Digit8, + winit::keyboard::KeyCode::Digit9 => Self::Digit9, + winit::keyboard::KeyCode::Equal => Self::Equal, + winit::keyboard::KeyCode::IntlBackslash => Self::IntlBackslash, + winit::keyboard::KeyCode::IntlRo => Self::IntlRo, + winit::keyboard::KeyCode::IntlYen => Self::IntlYen, + winit::keyboard::KeyCode::KeyA => Self::KeyA, + winit::keyboard::KeyCode::KeyB => Self::KeyB, + winit::keyboard::KeyCode::KeyC => Self::KeyC, + winit::keyboard::KeyCode::KeyD => Self::KeyD, + winit::keyboard::KeyCode::KeyE => Self::KeyE, + winit::keyboard::KeyCode::KeyF => Self::KeyF, + winit::keyboard::KeyCode::KeyG => Self::KeyG, + winit::keyboard::KeyCode::KeyH => Self::KeyH, + winit::keyboard::KeyCode::KeyI => Self::KeyI, + winit::keyboard::KeyCode::KeyJ => Self::KeyJ, + winit::keyboard::KeyCode::KeyK => Self::KeyK, + winit::keyboard::KeyCode::KeyL => Self::KeyL, + winit::keyboard::KeyCode::KeyM => Self::KeyM, + winit::keyboard::KeyCode::KeyN => Self::KeyN, + winit::keyboard::KeyCode::KeyO => Self::KeyO, + winit::keyboard::KeyCode::KeyP => Self::KeyP, + winit::keyboard::KeyCode::KeyQ => Self::KeyQ, + winit::keyboard::KeyCode::KeyR => Self::KeyR, + winit::keyboard::KeyCode::KeyS => Self::KeyS, + winit::keyboard::KeyCode::KeyT => Self::KeyT, + winit::keyboard::KeyCode::KeyU => Self::KeyU, + winit::keyboard::KeyCode::KeyV => Self::KeyV, + winit::keyboard::KeyCode::KeyW => Self::KeyW, + winit::keyboard::KeyCode::KeyX => Self::KeyX, + winit::keyboard::KeyCode::KeyY => Self::KeyY, + winit::keyboard::KeyCode::KeyZ => Self::KeyZ, + winit::keyboard::KeyCode::Minus => Self::Minus, + winit::keyboard::KeyCode::Period => Self::Period, + winit::keyboard::KeyCode::Quote => Self::Quote, + winit::keyboard::KeyCode::Semicolon => Self::Semicolon, + winit::keyboard::KeyCode::Slash => Self::Slash, + winit::keyboard::KeyCode::AltLeft => Self::AltLeft, + winit::keyboard::KeyCode::AltRight => Self::AltRight, + winit::keyboard::KeyCode::Backspace => Self::Backslash, + winit::keyboard::KeyCode::CapsLock => Self::CapsLock, + winit::keyboard::KeyCode::ContextMenu => Self::ContextMenu, + winit::keyboard::KeyCode::ControlLeft => Self::ControlLeft, + winit::keyboard::KeyCode::ControlRight => Self::ControlRight, + winit::keyboard::KeyCode::Enter => Self::Enter, + winit::keyboard::KeyCode::SuperLeft => Self::SuperLeft, + winit::keyboard::KeyCode::SuperRight => Self::SuperRight, + winit::keyboard::KeyCode::ShiftLeft => Self::ShiftLeft, + winit::keyboard::KeyCode::ShiftRight => Self::ShiftRight, + winit::keyboard::KeyCode::Space => Self::Space, + winit::keyboard::KeyCode::Tab => Self::Tab, + winit::keyboard::KeyCode::Convert => Self::Convert, + winit::keyboard::KeyCode::KanaMode => Self::KanaMode, + winit::keyboard::KeyCode::Lang1 => Self::Lang1, + winit::keyboard::KeyCode::Lang2 => Self::Lang2, + winit::keyboard::KeyCode::Lang3 => Self::Lang3, + winit::keyboard::KeyCode::Lang4 => Self::Lang4, + winit::keyboard::KeyCode::Lang5 => Self::Lang5, + winit::keyboard::KeyCode::NonConvert => Self::NonConvert, + winit::keyboard::KeyCode::Delete => Self::Delete, + winit::keyboard::KeyCode::End => Self::End, + winit::keyboard::KeyCode::Help => Self::Help, + winit::keyboard::KeyCode::Home => Self::Home, + winit::keyboard::KeyCode::Insert => Self::Insert, + winit::keyboard::KeyCode::PageDown => Self::PageDown, + winit::keyboard::KeyCode::PageUp => Self::PageUp, + winit::keyboard::KeyCode::ArrowDown => Self::ArrowDown, + winit::keyboard::KeyCode::ArrowLeft => Self::ArrowLeft, + winit::keyboard::KeyCode::ArrowRight => Self::ArrowRight, + winit::keyboard::KeyCode::ArrowUp => Self::ArrowUp, + winit::keyboard::KeyCode::NumLock => Self::NumLock, + winit::keyboard::KeyCode::Numpad0 => Self::Numpad0, + winit::keyboard::KeyCode::Numpad1 => Self::Numpad1, + winit::keyboard::KeyCode::Numpad2 => Self::Numpad2, + winit::keyboard::KeyCode::Numpad3 => Self::Numpad3, + winit::keyboard::KeyCode::Numpad4 => Self::Numpad4, + winit::keyboard::KeyCode::Numpad5 => Self::Numpad5, + winit::keyboard::KeyCode::Numpad6 => Self::Numpad6, + winit::keyboard::KeyCode::Numpad7 => Self::Numpad7, + winit::keyboard::KeyCode::Numpad8 => Self::Numpad8, + winit::keyboard::KeyCode::Numpad9 => Self::Numpad9, + winit::keyboard::KeyCode::NumpadAdd => Self::NumpadAdd, + winit::keyboard::KeyCode::NumpadBackspace => Self::NumpadBackspace, + winit::keyboard::KeyCode::NumpadClear => Self::NumpadClear, + winit::keyboard::KeyCode::NumpadClearEntry => Self::NumpadClearEntry, + winit::keyboard::KeyCode::NumpadComma => Self::NumpadComma, + winit::keyboard::KeyCode::NumpadDecimal => Self::NumpadDecimal, + winit::keyboard::KeyCode::NumpadDivide => Self::NumpadDivide, + winit::keyboard::KeyCode::NumpadEnter => Self::NumpadEnter, + winit::keyboard::KeyCode::NumpadEqual => Self::NumpadEqual, + winit::keyboard::KeyCode::NumpadHash => Self::NumpadHash, + winit::keyboard::KeyCode::NumpadMemoryAdd => Self::NumpadMemoryAdd, + winit::keyboard::KeyCode::NumpadMemoryClear => Self::NumpadMemoryClear, + winit::keyboard::KeyCode::NumpadMemoryRecall => Self::NumpadMemoryRecall, + winit::keyboard::KeyCode::NumpadMemoryStore => Self::NumpadMemoryStore, + winit::keyboard::KeyCode::NumpadMemorySubtract => Self::NumpadMemorySubtract, + winit::keyboard::KeyCode::NumpadMultiply => Self::NumpadMultiply, + winit::keyboard::KeyCode::NumpadParenLeft => Self::NumpadParenLeft, + winit::keyboard::KeyCode::NumpadParenRight => Self::NumpadParenRight, + winit::keyboard::KeyCode::NumpadStar => Self::NumpadStar, + winit::keyboard::KeyCode::NumpadSubtract => Self::NumpadSubtract, + winit::keyboard::KeyCode::Escape => Self::Escape, + winit::keyboard::KeyCode::Fn => Self::Fn, + winit::keyboard::KeyCode::FnLock => Self::FnLock, + winit::keyboard::KeyCode::PrintScreen => Self::PrintScreen, + winit::keyboard::KeyCode::ScrollLock => Self::ScrollLock, + winit::keyboard::KeyCode::Pause => Self::Pause, + winit::keyboard::KeyCode::BrowserBack => Self::BrowserBack, + winit::keyboard::KeyCode::BrowserFavorites => Self::BrowserFavorites, + winit::keyboard::KeyCode::BrowserForward => Self::BrowserForward, + winit::keyboard::KeyCode::BrowserHome => Self::BrowserHome, + winit::keyboard::KeyCode::BrowserRefresh => Self::BrowserRefresh, + winit::keyboard::KeyCode::BrowserSearch => Self::BrowserSearch, + winit::keyboard::KeyCode::BrowserStop => Self::BrowserStop, + winit::keyboard::KeyCode::Eject => Self::Eject, + winit::keyboard::KeyCode::LaunchApp1 => Self::LaunchApp1, + winit::keyboard::KeyCode::LaunchApp2 => Self::LaunchApp2, + winit::keyboard::KeyCode::LaunchMail => Self::LaunchMail, + winit::keyboard::KeyCode::MediaPlayPause => Self::MediaPlayPause, + winit::keyboard::KeyCode::MediaSelect => Self::MediaSelect, + winit::keyboard::KeyCode::MediaStop => Self::MediaStop, + winit::keyboard::KeyCode::MediaTrackNext => Self::MediaTrackNext, + winit::keyboard::KeyCode::MediaTrackPrevious => Self::MediaTrackPrevious, + winit::keyboard::KeyCode::Power => Self::Power, + winit::keyboard::KeyCode::Sleep => Self::Sleep, + winit::keyboard::KeyCode::AudioVolumeDown => Self::AudioVolumeDown, + winit::keyboard::KeyCode::AudioVolumeMute => Self::AudioVolumeMute, + winit::keyboard::KeyCode::AudioVolumeUp => Self::AudioVolumeUp, + winit::keyboard::KeyCode::WakeUp => Self::WakeUp, + winit::keyboard::KeyCode::Meta => Self::Meta, + winit::keyboard::KeyCode::Hyper => Self::Hyper, + winit::keyboard::KeyCode::Turbo => Self::Turbo, + winit::keyboard::KeyCode::Abort => Self::Abort, + winit::keyboard::KeyCode::Resume => Self::Resume, + winit::keyboard::KeyCode::Suspend => Self::Suspend, + winit::keyboard::KeyCode::Again => Self::Again, + winit::keyboard::KeyCode::Copy => Self::Copy, + winit::keyboard::KeyCode::Cut => Self::Cut, + winit::keyboard::KeyCode::Find => Self::Find, + winit::keyboard::KeyCode::Open => Self::Open, + winit::keyboard::KeyCode::Paste => Self::Paste, + winit::keyboard::KeyCode::Props => Self::Props, + winit::keyboard::KeyCode::Select => Self::Select, + winit::keyboard::KeyCode::Undo => Self::Undo, + winit::keyboard::KeyCode::Hiragana => Self::Hiragana, + winit::keyboard::KeyCode::Katakana => Self::Katakana, + winit::keyboard::KeyCode::F1 => Self::F1, + winit::keyboard::KeyCode::F2 => Self::F2, + winit::keyboard::KeyCode::F3 => Self::F3, + winit::keyboard::KeyCode::F4 => Self::F4, + winit::keyboard::KeyCode::F5 => Self::F5, + winit::keyboard::KeyCode::F6 => Self::F6, + winit::keyboard::KeyCode::F7 => Self::F7, + winit::keyboard::KeyCode::F8 => Self::F8, + winit::keyboard::KeyCode::F9 => Self::F9, + winit::keyboard::KeyCode::F10 => Self::F10, + winit::keyboard::KeyCode::F11 => Self::F11, + winit::keyboard::KeyCode::F12 => Self::F12, + winit::keyboard::KeyCode::F13 => Self::F13, + winit::keyboard::KeyCode::F14 => Self::F14, + winit::keyboard::KeyCode::F15 => Self::F15, + winit::keyboard::KeyCode::F16 => Self::F16, + winit::keyboard::KeyCode::F17 => Self::F17, + winit::keyboard::KeyCode::F18 => Self::F18, + winit::keyboard::KeyCode::F19 => Self::F19, + winit::keyboard::KeyCode::F20 => Self::F20, + winit::keyboard::KeyCode::F21 => Self::F21, + winit::keyboard::KeyCode::F22 => Self::F22, + winit::keyboard::KeyCode::F23 => Self::F23, + winit::keyboard::KeyCode::F24 => Self::F24, + winit::keyboard::KeyCode::F25 => Self::F25, + winit::keyboard::KeyCode::F26 => Self::F26, + winit::keyboard::KeyCode::F27 => Self::F27, + winit::keyboard::KeyCode::F28 => Self::F28, + winit::keyboard::KeyCode::F29 => Self::F29, + winit::keyboard::KeyCode::F30 => Self::F30, + winit::keyboard::KeyCode::F31 => Self::F31, + winit::keyboard::KeyCode::F32 => Self::F32, + winit::keyboard::KeyCode::F33 => Self::F33, + winit::keyboard::KeyCode::F34 => Self::F34, + winit::keyboard::KeyCode::F35 => Self::F35, + _ => panic!("unknown KeyCode"), + } } } \ No newline at end of file diff --git a/lyra-game/src/winit/mod.rs b/lyra-game/src/winit/mod.rs index 51adcbf..342d548 100644 --- a/lyra-game/src/winit/mod.rs +++ b/lyra-game/src/winit/mod.rs @@ -4,3 +4,5 @@ pub use plugin::*; mod window; pub use window::*; + +pub use winit::dpi as dpi; \ No newline at end of file diff --git a/lyra-game/src/winit/plugin.rs b/lyra-game/src/winit/plugin.rs index ce3c3b5..b2457d5 100644 --- a/lyra-game/src/winit/plugin.rs +++ b/lyra-game/src/winit/plugin.rs @@ -13,6 +13,9 @@ use winit::{ window::{Window, WindowAttributes, WindowId}, }; +pub use winit::event::{DeviceId, DeviceEvent, MouseScrollDelta, ElementState}; +pub use winit::keyboard::PhysicalKey; + use crate::{ game::{App, WindowState}, plugin::Plugin, @@ -26,9 +29,9 @@ use super::WindowOptions; #[derive(Debug, Clone, Reflect)] pub struct DeviceEventPair { #[reflect(skip)] - pub device_src: winit::event::DeviceId, + pub device_src: DeviceId, #[reflect(skip)] - pub event: winit::event::DeviceEvent, + pub event: DeviceEvent, } pub struct WinitPlugin { diff --git a/lyra-scripting/lyra-scripting-derive/src/lua_macro.rs b/lyra-scripting/lyra-scripting-derive/src/lua_macro.rs index e681bd5..502b487 100644 --- a/lyra-scripting/lyra-scripting-derive/src/lua_macro.rs +++ b/lyra-scripting/lyra-scripting-derive/src/lua_macro.rs @@ -10,6 +10,7 @@ use crate::{field::{Field, FieldType}, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNA enum SkipType { /// Skips implementing LuaReflect, + LuaWrapper, } impl syn::parse::Parse for SkipType { @@ -19,6 +20,7 @@ impl syn::parse::Parse for SkipType { match name_str.as_str() { "lua_reflect" => Ok(Self::LuaReflect), + "lua_wrapper" => Ok(Self::LuaWrapper), _ => Err(syn::Error::new_spanned(name, "unknown skip type")), } } @@ -483,6 +485,24 @@ pub fn wrap_lua_struct_impl(input: proc_macro::TokenStream) -> proc_macro::Token } }; + let lua_wrapper = if input.skips.contains(&SkipType::LuaWrapper) { + quote!() + } else { + quote! { + impl lyra_scripting::lua::LuaWrapper for #wrapper_typename { + type Wrap = #path; + + fn wrapped_type_id() -> std::any::TypeId { + std::any::TypeId::of::<#path>() + } + + fn into_wrapped(self) -> Self::Wrap { + self.0 + } + } + } + }; + for field in input.auto_fields.iter() { if field.field_ty.is_unknown() && !field.skip_setter { return syn::Error::new( @@ -583,6 +603,7 @@ pub fn wrap_lua_struct_impl(input: proc_macro::TokenStream) -> proc_macro::Token impl mlua::UserData for #wrapper_typename { fn add_fields>(fields: &mut F) { use mlua::IntoLua; + use mlua::FromLua; #(#fields)* @@ -591,6 +612,7 @@ pub fn wrap_lua_struct_impl(input: proc_macro::TokenStream) -> proc_macro::Token fn add_methods>(methods: &mut M) { use mlua::IntoLua; + use mlua::FromLua; #lua_reflects @@ -600,16 +622,6 @@ pub fn wrap_lua_struct_impl(input: proc_macro::TokenStream) -> proc_macro::Token } } - impl lyra_scripting::lua::LuaWrapper for #wrapper_typename { - type Wrap = #path; - - fn wrapped_type_id() -> std::any::TypeId { - std::any::TypeId::of::<#path>() - } - - fn into_wrapped(self) -> Self::Wrap { - self.0 - } - } + #lua_wrapper }) } diff --git a/lyra-scripting/src/lua/proxy.rs b/lyra-scripting/src/lua/proxy.rs index d738bea..a7a16a5 100644 --- a/lyra-scripting/src/lua/proxy.rs +++ b/lyra-scripting/src/lua/proxy.rs @@ -12,7 +12,11 @@ pub trait LuaWrapper { type Wrap: Reflect + 'static; /// The type id of the wrapped type. - fn wrapped_type_id() -> TypeId; + #[inline(always)] + fn wrapped_type_id() -> TypeId { + TypeId::of::() + } + fn into_wrapped(self) -> Self::Wrap; } diff --git a/lyra-scripting/src/lua/wrappers/events.rs b/lyra-scripting/src/lua/wrappers/events.rs new file mode 100644 index 0000000..22af8b7 --- /dev/null +++ b/lyra-scripting/src/lua/wrappers/events.rs @@ -0,0 +1,318 @@ +use crate::lua::LuaWrapper; +use lyra_game::{ + input::{KeyCode, NativeKeyCode}, + math::Vec2, + winit::{ + dpi::PhysicalPosition, DeviceEvent, DeviceEventPair, DeviceId, MouseScrollDelta, + PhysicalKey, + }, +}; +use lyra_scripting_derive::wrap_lua_struct; + +use crate::lyra_engine; + +use super::LuaVec2; + +wrap_lua_struct!(DeviceId, skip(lua_reflect, lua_wrapper)); + +/// Wraps [`DeviceEvent`] and implements [`IntoLua`] +#[derive(Clone)] +pub struct LuaDeviceEventRaw(pub(crate) DeviceEvent); + +impl std::ops::Deref for LuaDeviceEventRaw { + type Target = DeviceEvent; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::DerefMut for LuaDeviceEventRaw { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl mlua::FromLua for LuaDeviceEventRaw { + fn from_lua(v: mlua::Value, _: &mlua::Lua) -> mlua::Result { + let ty = v.type_name(); + let table = v.as_table().ok_or(mlua::Error::FromLuaConversionError { + from: ty, + to: "LuaDeviceEventRaw".into(), + message: Some("expected Lua Table".into()), + })?; + + let type_str: String = table.get("type")?; + let type_str = type_str.as_str(); + + let ret = match type_str { + "added" => Self(DeviceEvent::Added), + "removed" => Self(DeviceEvent::Removed), + "mouse_motion" => { + let delta: LuaVec2 = table.get("delta")?; + Self(DeviceEvent::MouseMotion { + delta: (delta.x as _, delta.y as _), + }) + } + "mouse_wheel" => { + let delta = if table.contains_key("line_delta")? { + let ld: LuaVec2 = table.get("line_delta")?; + MouseScrollDelta::LineDelta(ld.x, ld.y) + } else if table.contains_key("pixel_delta")? { + let pd: LuaVec2 = table.get("pixel_delta")?; + let pos = PhysicalPosition::new(pd.x as f64, pd.y as f64); + MouseScrollDelta::PixelDelta(pos) + } else { + return Err(mlua::Error::FromLuaConversionError { + from: ty, + to: "LuaDeviceEventRaw".into(), + message: Some( + "could not find line_delta or pixel_delta in 'mouse_wheel' \ + device event" + .into(), + ), + }); + }; + + Self(DeviceEvent::MouseWheel { delta }) + } + "motion" => { + let axis: u32 = table.get("axis")?; + let value: f64 = table.get("value")?; + Self(DeviceEvent::Motion { axis, value }) + } + "button" => { + let button: u32 = table.get("button")?; + + let state_str: String = table.get("state")?; + let state_str = state_str.as_str(); + let state = match state_str { + "pressed" => lyra_game::winit::ElementState::Pressed, + "released" => lyra_game::winit::ElementState::Released, + _ => { + return Err(mlua::Error::FromLuaConversionError { + from: ty, + to: "LuaDeviceEventRaw".into(), + message: Some(format!("unknown button state: '{}'", state_str)), + }); + } + }; + + Self(DeviceEvent::Button { button, state }) + } + "key" => { + let state_str: String = table.get("state")?; + let state_str = state_str.as_str(); + let state = match state_str { + "pressed" => lyra_game::winit::ElementState::Pressed, + "released" => lyra_game::winit::ElementState::Released, + _ => { + return Err(mlua::Error::FromLuaConversionError { + from: ty, + to: "LuaDeviceEventRaw".into(), + message: Some(format!("unknown key state: '{}'", state_str)), + }); + } + }; + + if table.contains_key("code")? { + let code_str: String = table.get("code")?; + let code = KeyCode::from_str(&code_str); + if code.is_none() { + return Err(mlua::Error::FromLuaConversionError { + from: ty, + to: "LuaDeviceEventRaw".into(), + message: Some(format!("unknown key code: '{}'", code_str)), + }); + } + + let phy = PhysicalKey::Code(code); + Self(DeviceEvent::Key(RawKeyEvent { + physical_key: phy, + state, + })) + } else if table.contains_key("native")? { + let native_str: String = table.get("native")?; + let native_str = native_str.as_str(); + let native_code: u32 = table.get("native_code")?; + + let native_code = match native_str { + "unknown" => NativeKeyCode::Unidentified, + "android" => NativeKeyCode::Android(native_code), + "macos" => NativeKeyCode::MacOS(native_code as u16), + "windows" => NativeKeyCode::Windows(native_code), + "xkb" => NativeKeyCode::Xkb(native_code), + }; + + Self(DeviceEvent::Key(RawKeyEvent { + physical_key: PhysicalKey::Unidentified(native_code), + state, + })) + } else { + return Err(mlua::Error::FromLuaConversionError { + from: ty, + to: "LuaDeviceEventRaw".into(), + message: Some( + "malformed Table for LuaDeviceEventRaw, missing key \ + code" + .into(), + ), + }); + } + } + _ => { + return Err(mlua::Error::FromLuaConversionError { + from: ty, + to: "LuaDeviceEventRaw".into(), + message: Some(format!("unknown device event type '{}'", type_str)), + }); + } + }; + + Ok(ret) + } +} + +impl mlua::IntoLua for LuaDeviceEventRaw { + fn into_lua(self, lua: &mlua::Lua) -> mlua::Result { + let table = lua.create_table()?; + + match self.0 { + DeviceEvent::Added => { + table.set("type", "added")?; + } + DeviceEvent::Removed => { + table.set("type", "removed")?; + } + DeviceEvent::MouseMotion { delta } => { + table.set("type", "mouse_motion")?; + table.set("delta", LuaVec2(Vec2::new(delta.0 as _, delta.1 as _)))?; + } + DeviceEvent::MouseWheel { delta } => { + table.set("type", "mouse_wheel")?; + + match delta { + MouseScrollDelta::LineDelta(x, y) => { + let d = LuaVec2(Vec2::new(x, y)); + table.set("line_delta", d)?; + } + MouseScrollDelta::PixelDelta(delta) => { + let d = delta.cast::(); + let d = LuaVec2(Vec2::new(d.x, d.y)); + table.set("pixel_delta", d)?; + } + } + } + DeviceEvent::Motion { axis, value } => { + table.set("type", "motion")?; + table.set("axis", axis)?; + table.set("value", value)?; + } + DeviceEvent::Button { button, state } => { + table.set("type", "button")?; + table.set("button", button)?; + + let state = match state { + lyra_game::winit::ElementState::Pressed => "pressed", + lyra_game::winit::ElementState::Released => "released", + }; + table.set("state", state)?; + } + DeviceEvent::Key(raw_key_event) => { + table.set("type", "key")?; + + let state = match raw_key_event.state { + lyra_game::winit::ElementState::Pressed => "pressed", + lyra_game::winit::ElementState::Released => "released", + }; + table.set("state", state)?; + + let e = KeyCode::from_raw_event(&raw_key_event); + let s = e.as_str(); + + if let Some(s) = s { + table.set("code", s)?; + } else if let Some(un) = e.get_unknown() { + match un { + lyra_game::input::NativeKeyCode::Unidentified => { + table.set("native", "unknown")?; + } + lyra_game::input::NativeKeyCode::Android(v) => { + table.set("native", "android")?; + table.set("native_code", *v)?; + } + lyra_game::input::NativeKeyCode::MacOS(v) => { + table.set("native", "macos")?; + table.set("native_code", *v)?; + } + lyra_game::input::NativeKeyCode::Windows(v) => { + table.set("native", "windows")?; + table.set("native_code", *v)?; + } + lyra_game::input::NativeKeyCode::Xkb(v) => { + table.set("native", "xkb")?; + table.set("native_code", *v)?; + } + } + } + } + } + + table.into_lua(lua) + } +} + +#[derive(Clone)] +pub struct LuaDeviceEvent(pub(crate) DeviceEventPair); + +impl std::ops::Deref for LuaDeviceEvent { + type Target = DeviceEventPair; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::DerefMut for LuaDeviceEvent { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl mlua::FromLua for LuaDeviceEvent { + fn from_lua(v: mlua::Value, _: &mlua::Lua) -> mlua::Result { + let ty = v.type_name(); + let table = v.as_table().ok_or(mlua::Error::FromLuaConversionError { + from: ty, + to: "LuaDeviceEvent".into(), + message: Some("expected Lua Table".into()), + })?; + + let id: LuaDeviceId = table.get("device")?; + let ev: LuaDeviceEventRaw = table.get("event")?; + + Ok(Self(DeviceEventPair { + device_src: id, + event: ev, + })) + } +} + +impl mlua::IntoLua for LuaDeviceEvent { + fn into_lua(self, lua: &mlua::Lua) -> mlua::Result { + let table = lua.create_table()?; + table.set("device", LuaDeviceId(self.device_src))?; + table.set("event", LuaDeviceEventRaw(self.event.clone()))?; + + table.into_lua(lua) + } +} + +impl LuaWrapper for LuaDeviceEvent { + type Wrap = DeviceEventPair; + + #[inline(always)] + fn into_wrapped(self) -> Self::Wrap { + self.0 + } +} diff --git a/lyra-scripting/src/lua/wrappers/mod.rs b/lyra-scripting/src/lua/wrappers/mod.rs index 24136b8..c942ffc 100644 --- a/lyra-scripting/src/lua/wrappers/mod.rs +++ b/lyra-scripting/src/lua/wrappers/mod.rs @@ -17,4 +17,7 @@ mod camera; pub use camera::*; mod free_fly_camera; -pub use free_fly_camera::*; \ No newline at end of file +pub use free_fly_camera::*; + +mod events; +pub use events::*; \ No newline at end of file -- 2.40.1 From 6a47cd26716d83b6cf91baf76bb31c977704f1de Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sun, 13 Oct 2024 11:43:49 -0400 Subject: [PATCH 13/15] lua: expose DeviceEvent --- examples/lua-scripting/scripts/test.lua | 24 +++ lyra-game/src/event.rs | 14 +- lyra-game/src/input/mod.rs | 3 +- lyra-game/src/input/system.rs | 24 +-- lyra-game/src/winit/plugin.rs | 2 +- lyra-scripting/scripts/lua/enums.lua | 25 +++ .../scripts/lua/types/events/device.lua | 140 ++++++++++++++++ .../scripts/lua/types/events/init.lua | 18 +++ lyra-scripting/src/lua/providers/ecs.rs | 38 ++++- lyra-scripting/src/lua/proxy.rs | 20 ++- lyra-scripting/src/lua/world.rs | 75 +++++++-- .../{events.rs => events/device_event.rs} | 59 ++++--- lyra-scripting/src/lua/wrappers/events/mod.rs | 151 ++++++++++++++++++ .../src/lua/wrappers/input_actions.rs | 4 +- 14 files changed, 526 insertions(+), 71 deletions(-) create mode 100644 lyra-scripting/scripts/lua/types/events/device.lua create mode 100644 lyra-scripting/scripts/lua/types/events/init.lua rename lyra-scripting/src/lua/wrappers/{events.rs => events/device_event.rs} (88%) create mode 100644 lyra-scripting/src/lua/wrappers/events/mod.rs diff --git a/examples/lua-scripting/scripts/test.lua b/examples/lua-scripting/scripts/test.lua index 083a991..63a273c 100644 --- a/examples/lua-scripting/scripts/test.lua +++ b/examples/lua-scripting/scripts/test.lua @@ -14,6 +14,19 @@ function udname(val) return tbl.__name end + +function dump(o) + if type(o) == 'table' then + local s = '{ ' + for k,v in pairs(o) do + if type(k) ~= 'number' then k = '"'..k..'"' end + s = s .. '['..k..'] = ' .. dump(v) .. ',' + end + return s .. '} ' + else + return tostring(o) + end + end function on_init() @@ -46,6 +59,17 @@ function on_first() end, Window ) end + + ---@type EventReader + local reader = world:read_event(DeviceEvent) + + ---@param ev DeviceEvent + for ev in reader:read() do + if ev.event.kind == DeviceEventKind.MOTION then + local motion_ev = ev.event --[[@as DeviceEventMotion]] + print("axis: " .. tostring(motion_ev.axis) .. " = " .. tostring(motion_ev.value)) + end + end end --[[ function on_pre_update() diff --git a/lyra-game/src/event.rs b/lyra-game/src/event.rs index 75d48f8..10a3e42 100644 --- a/lyra-game/src/event.rs +++ b/lyra-game/src/event.rs @@ -1,4 +1,4 @@ -use std::{cell::RefCell, rc::Rc, sync::Arc}; +use std::sync::Arc; use atomic_refcell::AtomicRefCell; use lyra_ecs::{query::{ResMut, WorldTick}, system::FnArgFetcher, Tick}; @@ -99,7 +99,7 @@ impl Events { pub fn reader(&self) -> EventReader { EventReader { events: self.events.clone(), - cursor: Rc::new(RefCell::new(0)), + cursor: Arc::new(AtomicRefCell::new(0)), } } @@ -112,11 +112,11 @@ impl Events { pub struct EventReader { events: Arc>>, - cursor: Rc>, + cursor: Arc>, } impl EventReader { - pub fn read(&mut self) -> Option> { + pub fn read(&self) -> Option> { let events = self.events.borrow(); let mut cursor = self.cursor.borrow_mut(); @@ -136,7 +136,7 @@ pub struct EventWriter { } impl EventWriter { - pub fn write(&mut self, event: T) { + pub fn write(&self, event: T) { let mut events = self.events.borrow_mut(); events.push(event); } @@ -167,12 +167,12 @@ where } impl FnArgFetcher for EventReader { - type State = Rc>; + type State = Arc>; type Arg<'a, 'state> = EventReader; fn create_state(_: std::ptr::NonNull) -> Self::State { - Rc::new(RefCell::new(0)) + Arc::new(AtomicRefCell::new(0)) } unsafe fn get<'a, 'state>(state: &'state mut Self::State, world: std::ptr::NonNull) -> Self::Arg<'a, 'state> { diff --git a/lyra-game/src/input/mod.rs b/lyra-game/src/input/mod.rs index adab4cb..d43df84 100644 --- a/lyra-game/src/input/mod.rs +++ b/lyra-game/src/input/mod.rs @@ -520,7 +520,8 @@ impl KeyCode { pub fn as_physical_key(&self) -> winit::keyboard::PhysicalKey { match self { KeyCode::Unknown(v) => winit::keyboard::PhysicalKey::Unidentified((*v).into()), - _ => winit::keyboard::PhysicalKey::Code(self.into()) + // unwrap is safe here since we know self is not an `Unknown` key. + _ => winit::keyboard::PhysicalKey::Code(self.as_winit_keycode().unwrap()) } } diff --git a/lyra-game/src/input/system.rs b/lyra-game/src/input/system.rs index a30d73a..de54581 100755 --- a/lyra-game/src/input/system.rs +++ b/lyra-game/src/input/system.rs @@ -6,7 +6,7 @@ use winit::{event::{MouseScrollDelta, WindowEvent}, keyboard::PhysicalKey}; use crate::{game::GameStages, plugin::Plugin, winit::DeviceEventPair, EventReader, EventWriter}; -use super::{events::*, InputButtons}; +use super::{events::*, InputButtons, KeyCode}; fn write_scroll_delta(mouse_scroll_ev: &mut EventWriter, delta: &MouseScrollDelta) { let event = match delta { @@ -21,24 +21,24 @@ fn write_scroll_delta(mouse_scroll_ev: &mut EventWriter, delta: &Mo mouse_scroll_ev.write(event); } -fn write_key_event(key_buttons: &mut ResMut>, physical_key: winit::keyboard::PhysicalKey, state: winit::event::ElementState) { +fn write_key_event(key_buttons: &mut ResMut>, physical_key: PhysicalKey, state: winit::event::ElementState) { if let PhysicalKey::Code(code) = physical_key { - key_buttons.add_input_from_winit(code, state); + key_buttons.add_input_from_winit(KeyCode::from(code), state); } } pub fn input_system( - mut key_code_res: ResMut>, + mut key_code_res: ResMut>, mut mouse_btn_res: ResMut>, mut touches_res: ResMut, - mut window_ev: EventReader, - mut device_ev: EventReader, + window_ev: EventReader, + device_ev: EventReader, mut mouse_scroll_ev: EventWriter, - mut mouse_btn_ev: EventWriter, - mut mouse_exact_ev: EventWriter, - mut mouse_entered_ev: EventWriter, - mut mouse_left_ev: EventWriter, - mut mouse_motion_ev: EventWriter, + mouse_btn_ev: EventWriter, + mouse_exact_ev: EventWriter, + mouse_entered_ev: EventWriter, + mouse_left_ev: EventWriter, + mouse_motion_ev: EventWriter, ) -> anyhow::Result<()> { while let Some(event) = window_ev.read() { match event.deref() { @@ -122,7 +122,7 @@ pub struct InputPlugin; impl Plugin for InputPlugin { fn setup(&mut self, app: &mut crate::game::App) { - app.add_resource(InputButtons::::default()); + app.add_resource(InputButtons::::default()); app.add_resource(InputButtons::::default()); app.add_resource(Touches::default()); diff --git a/lyra-game/src/winit/plugin.rs b/lyra-game/src/winit/plugin.rs index b2457d5..ced2134 100644 --- a/lyra-game/src/winit/plugin.rs +++ b/lyra-game/src/winit/plugin.rs @@ -13,7 +13,7 @@ use winit::{ window::{Window, WindowAttributes, WindowId}, }; -pub use winit::event::{DeviceId, DeviceEvent, MouseScrollDelta, ElementState}; +pub use winit::event::{DeviceId, DeviceEvent, MouseScrollDelta, ElementState, RawKeyEvent}; pub use winit::keyboard::PhysicalKey; use crate::{ diff --git a/lyra-scripting/scripts/lua/enums.lua b/lyra-scripting/scripts/lua/enums.lua index ea62940..f891139 100644 --- a/lyra-scripting/scripts/lua/enums.lua +++ b/lyra-scripting/scripts/lua/enums.lua @@ -66,4 +66,29 @@ WrappingMode = { CameraProjectionMode = { PERSPECTIVE = "perspective", ORTHOGRAPHIC = "orthographic", +} + +---@enum DeviceEventKind +DeviceEventKind = { + ADDED = "added", + REMOVED = "removed", + MOUSE_MOTION = "mouse_motion", + MOUSE_WHEEL = "mouse_wheel", + MOTION = "motion", + BUTTON = "button", + KEY = "key", +} + +---@enum NativeKeyCodeKind +NativeKeyCodeKind = { + ANDROID = "android", + MACOS = "macos", + WINDOWS = "windows", + XKB = "xkb", +} + +---@enum ElementState +ElementState = { + PRESSED = "pressed", + RELEASED = "released", } \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/types/events/device.lua b/lyra-scripting/scripts/lua/types/events/device.lua new file mode 100644 index 0000000..6600d82 --- /dev/null +++ b/lyra-scripting/scripts/lua/types/events/device.lua @@ -0,0 +1,140 @@ +---@meta + +---@class DeviceEventAdded: table +DeviceEventAdded = { + ---@type DeviceEventKind + kind = DeviceEventKind.ADDED, +} + +---@class DeviceEventRemoved: table +DeviceEventRemoved = { + ---@type DeviceEventKind + kind = DeviceEventKind.REMOVED, +} + +---@class DeviceEventMouseMotion: table +DeviceEventMouseMotion = { + ---@type DeviceEventKind + kind = DeviceEventKind.MOUSE_MOTION, + + ---The change in physical position of a pointing device. + --- + ---This represents raw, unfiltered physical motion. + --- + ---@type Vec2 + delta = nil, +} + +---A physical scroll event from a device. +--- +---`line_delta` and `pixel_delta` are exclusive, only one is non-nil at a time. +--- +---@class DeviceEventMouseWheel: table +DeviceEventMouseWheel = { + ---@type DeviceEventKind + kind = DeviceEventKind.MOUSE_WHEEL, + + ---Amount in lines or rows to scroll. + --- + ---Positive values indicate that the content that is being scrolled should move right + ---and down (revealing more content left and up). + --- + ---@type Vec2? + line_delta = nil, + + ---Amount in pixels to scroll in the horizontal and vertical direction. + --- + ---Positive values indicate that the content being scrolled should move right/down. + --- + ---@type Vec2? + pixel_delta = nil, +} + +---Device event that was triggered by motion on an analog axis. +---@class DeviceEventMotion: table +DeviceEventMotion = { + ---@type DeviceEventKind + kind = DeviceEventKind.MOTION, + + ---The analog axis that motion was triggered from. + ---@type number + axis = nil, + + ---The amount of motion. + ---@type number + value = nil, +} + +---@class DeviceEventButton: table +DeviceEventButton = { + ---@type DeviceEventKind + kind = DeviceEventKind.BUTTON, + + ---The button id that the event is from. + ---@type number + button = nil, + + ---The state of the button. + ---@type ElementState + state = nil, +} + +---A device event triggered from a key press. +--- +---The field `code` will be nil if the key code identifier is unknown. +---When the identifier is unknown, it can be retrieved with `native_code`. The field +---`native` specifies the kind of platform the code is from. +--- +---@class DeviceEventKey: table +DeviceEventKey = { + ---@type DeviceEventKind + kind = DeviceEventKind.KEY, + + ---@type ElementState + state = nil, + + ---The known key name. + --- + ---This is `nil` if `native` or `native_code` is specified. + --- + ---@type string + code = nil, + + ---The kind of native platform this code is for. + --- + ---This is `nil` if `code` is specified. + --- + ---@type NativeKeyCodeKind + native = nil, + + ---The platform-native physical key identifier. + --- + ---This is `nil` if `code` is specified. + --- + ---@type number + native_code = nil, +} + +---@alias DeviceEventRaw +---| DeviceEventAdded +---| DeviceEventRemoved +---| DeviceEventMotion +---| DeviceEventMouseMotion +---| DeviceEventMouseWheel +---| DeviceEventButton +---| DeviceEventKey +DeviceEventRaw = { } + +---@class DeviceId: userdata +DeviceId = {} + +---@class DeviceEvent: table +DeviceEvent = { + ---The device id that the event came from + ---@type DeviceId + source = nil, + ---The actual device event + ---@type DeviceEventRaw + event = nil, +} + diff --git a/lyra-scripting/scripts/lua/types/events/init.lua b/lyra-scripting/scripts/lua/types/events/init.lua new file mode 100644 index 0000000..4d0bf1b --- /dev/null +++ b/lyra-scripting/scripts/lua/types/events/init.lua @@ -0,0 +1,18 @@ +---@meta + +---@class EventReader: userdata +EventReader = {} + +---Returns an iterator over the events in the reader. +---@return fun(): any +function EventReader:read() end + +---@class EventWriter: userdata +EventWriter = {} + +---Writes an event. +--- +---The event must be the same type that this writer wraps. +--- +---@param event any +function EventWriter:write(event) end diff --git a/lyra-scripting/src/lua/providers/ecs.rs b/lyra-scripting/src/lua/providers/ecs.rs index 00dd9ee..896550a 100644 --- a/lyra-scripting/src/lua/providers/ecs.rs +++ b/lyra-scripting/src/lua/providers/ecs.rs @@ -1,10 +1,11 @@ use std::any::TypeId; use lyra_ecs::ResourceObject; -use lyra_reflect::{Reflect, TypeRegistry}; +use lyra_game::winit::DeviceEventPair; +use lyra_reflect::{FromType, Reflect, TypeRegistry}; use lyra_resource::gltf::Gltf; -use crate::{lua::{wrappers::*, LuaContext, LuaWrapper, ReflectLuaProxy, RegisterLuaType, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE}, ScriptApiProvider, ScriptBorrow, ScriptData, ScriptDynamicBundle, ScriptWorldPtr}; +use crate::{lua::{wrappers::*, LuaContext, LuaWrapper, ReflectLuaProxy, RegisterLuaType, TypeLookup, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE}, ScriptApiProvider, ScriptBorrow, ScriptData, ScriptDynamicBundle, ScriptWorldPtr}; //fn register_lua_proxy::(); world.register_lua_convert_component::("Camera"); world.register_lua_convert_component::("FreeFlyCamera"); + world.register_lua_wrapper::(); + world.register_lua_convert::(); + world.register_lua_convert::(); + + // Add typeid of 'DeviceEvent' + let mut lookup = world.get_resource_or_default::(); + lookup.typeid_from_name.insert("DeviceEvent".into(), std::any::TypeId::of::()); + drop(lookup); let mut registry = world.get_resource_mut::().unwrap(); + // add LuaWrappedEventProxy + let reg_type = registry.get_type_or_default(TypeId::of::()); + let l: LuaWrappedEventProxy = FromType::::from_type(); + reg_type.add_data(l); + + // add Gltf handle let reg_type = registry.get_type_or_default(TypeId::of::()); reg_type.add_data(ReflectLuaProxy::from_lua_proxy::()); - let l = LuaResHandleToComponent::new( |lua, res| { if let Some(gltf) = res.as_typed::() { @@ -53,8 +67,9 @@ impl ScriptApiProvider for LyraEcsApiProvider { globals.set("ActionHandler", ctx.create_proxy::()?)?; globals.set("Window", ctx.create_proxy::()?)?; - expose_table_wrapper::(&ctx, &globals, "Camera")?; - expose_table_wrapper::(&ctx, &globals, "FreeFlyCamera")?; + expose_comp_table_wrapper::(&ctx, &globals, "Camera")?; + expose_comp_table_wrapper::(&ctx, &globals, "FreeFlyCamera")?; + expose_table_wrapper::(&ctx, &globals, "DeviceEvent")?; let dt_table = create_reflect_table::(&ctx)?; globals.set("DeltaTime", dt_table)?; @@ -104,12 +119,23 @@ where /// /// This creates the reflection functions on a table specified in globals. /// The table name is set to `name`, which is also how the script will use the table. -fn expose_table_wrapper(lua: &mlua::Lua, globals: &mlua::Table, name: &str) -> mlua::Result<()> +fn expose_comp_table_wrapper(lua: &mlua::Lua, globals: &mlua::Table, name: &str) -> mlua::Result<()> where T: LuaWrapper + mlua::FromLua, T::Wrap: lyra_ecs::Component + Reflect { let table = create_reflect_comp_table::(&lua, name)?; globals.set(name, table)?; + Ok(()) +} + +fn expose_table_wrapper(lua: &mlua::Lua, globals: &mlua::Table, name: &str) -> mlua::Result<()> +where + T: LuaWrapper, +{ + let table = lua.create_table()?; + table.set(mlua::MetaMethod::Type.name(), name.to_string())?; + globals.set(name, table)?; + Ok(()) } \ No newline at end of file diff --git a/lyra-scripting/src/lua/proxy.rs b/lyra-scripting/src/lua/proxy.rs index a7a16a5..d330a43 100644 --- a/lyra-scripting/src/lua/proxy.rs +++ b/lyra-scripting/src/lua/proxy.rs @@ -8,8 +8,8 @@ use crate::{ScriptBorrow, ScriptDynamicBundle}; use super::{Error, FN_NAME_INTERNAL_REFLECT}; -pub trait LuaWrapper { - type Wrap: Reflect + 'static; +pub trait LuaWrapper: Sized { + type Wrap: 'static; /// The type id of the wrapped type. #[inline(always)] @@ -18,6 +18,12 @@ pub trait LuaWrapper { } fn into_wrapped(self) -> Self::Wrap; + + #[inline(always)] + fn from_wrapped(wrap: Self::Wrap) -> Option { + let _ = wrap; + None + } } /// A trait that used to convert something into lua, or to set something to a value from lua. @@ -36,7 +42,7 @@ pub trait LuaProxy { impl<'a, T> LuaProxy for T where - T: Reflect + Clone + mlua::FromLua + mlua::UserData + T: Reflect + Clone + mlua::FromLua + mlua::IntoLua { fn as_lua_value( lua: &mlua::Lua, @@ -47,16 +53,14 @@ where } fn apply( - _: &mlua::Lua, + lua: &mlua::Lua, this: &mut dyn Reflect, apply: &mlua::Value, ) -> mlua::Result<()> { let this = this.as_any_mut().downcast_mut::().unwrap(); - let apply = apply.as_userdata() - .expect("Somehow a non-userdata Lua Value was provided to a LuaProxy") - .borrow::()?; + let apply = T::from_lua(apply.clone(), lua)?; - *this = apply.clone(); + *this = apply; Ok(()) } diff --git a/lyra-scripting/src/lua/world.rs b/lyra-scripting/src/lua/world.rs index ddaf43d..ab414b7 100644 --- a/lyra-scripting/src/lua/world.rs +++ b/lyra-scripting/src/lua/world.rs @@ -10,14 +10,20 @@ use lyra_resource::ResourceManager; use mlua::{IntoLua, ObjectLike}; use super::{ - reflect_user_data, wrappers::LuaResHandleToComponent, Error, TypeLookup, ReflectLuaProxy, ReflectedIterator, FN_NAME_INTERNAL_AS_COMPONENT, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE + reflect_user_data, + wrappers::{LuaResHandleToComponent, LuaWrappedEventProxy}, + Error, ReflectLuaProxy, ReflectedIterator, TypeLookup, FN_NAME_INTERNAL_AS_COMPONENT, + FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE, }; impl mlua::FromLua for ScriptEntity { fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result { match value { mlua::Value::UserData(ud) => Ok(ud.borrow::()?.clone()), - mlua::Value::Nil => Err(mlua::Error::external(Error::type_mismatch("ScriptEntity", "Nil"))), + mlua::Value::Nil => Err(mlua::Error::external(Error::type_mismatch( + "ScriptEntity", + "Nil", + ))), _ => panic!(), } } @@ -41,7 +47,10 @@ impl mlua::FromLua for ScriptWorldPtr { fn from_lua(val: mlua::Value, _: &mlua::Lua) -> mlua::Result { match val { mlua::Value::UserData(ud) => Ok(ud.borrow::()?.clone()), - mlua::Value::Nil => Err(mlua::Error::external(Error::type_mismatch("ScriptWorldPtr", "Nil"))), + mlua::Value::Nil => Err(mlua::Error::external(Error::type_mismatch( + "ScriptWorldPtr", + "Nil", + ))), _ => panic!(), } } @@ -64,8 +73,7 @@ impl mlua::UserData for ScriptWorldPtr { })?; let comp_borrow = { - if let Ok(as_comp) = ud.get::(FN_NAME_INTERNAL_AS_COMPONENT) - { + if let Ok(as_comp) = ud.get::(FN_NAME_INTERNAL_AS_COMPONENT) { let ud = match as_comp.call(ud.clone())? { mlua::Value::UserData(ud) => ud, mlua::Value::Nil => ud.clone(), @@ -115,11 +123,12 @@ impl mlua::UserData for ScriptWorldPtr { mlua::Value::Table(t) => { let name: String = t.get(mlua::MetaMethod::Type.name())?; - let lookup = world.get_resource::().ok_or( - mlua::Error::runtime( - "Unable to lookup table proxy, none were ever registered!", - ), - )?; + let lookup = + world + .get_resource::() + .ok_or(mlua::Error::runtime( + "Unable to lookup table proxy, none were ever registered!", + ))?; let info = lookup.comp_info_from_name.get(&name).ok_or_else(|| { mlua::Error::BadArgument { to: Some("World:view".into()), @@ -139,10 +148,7 @@ impl mlua::UserData for ScriptWorldPtr { } mlua::Value::UserData(ud) => { let reflect = ud - .call_function::( - FN_NAME_INTERNAL_REFLECT_TYPE, - (), - ) + .call_function::(FN_NAME_INTERNAL_REFLECT_TYPE, ()) .expect("Type does not implement 'reflect_type' properly"); let refl_comp = reflect.reflect_branch.as_component_unchecked(); @@ -172,7 +178,9 @@ impl mlua::UserData for ScriptWorldPtr { let mut world = this.write(); while let Some(row) = reflected_iter.next_lua(lua) { - let r = row.row.into_iter() + let r = row + .row + .into_iter() .into_iter() .map(|r| (r.comp_val, r.comp_ptr.cast::<()>())) .collect::>(); @@ -183,7 +191,6 @@ impl mlua::UserData for ScriptWorldPtr { // if values were returned, find the type in the type registry, and apply the new values if res.len() <= ptrs.len() { - for (comp, ptr) in res.into_iter().zip(ptrs) { let lua_typeid = match &comp { mlua::Value::UserData(ud) => { @@ -310,5 +317,41 @@ impl mlua::UserData for ScriptWorldPtr { Ok((data.fn_to_lua)(lua, handle).unwrap()) }); + methods.add_method_mut( + "read_event", + |lua, this, either: mlua::Either| { + let mut world = this.write(); + + // get the type name + let name = match either { + mlua::Either::Left(name) => name, + mlua::Either::Right(table) => table.get(mlua::MetaMethod::Type.name())?, + }; + + // lookup the type id using the name, return an error if not found + let lookup = world.get_resource::().unwrap(); + let tyid = lookup.typeid_from_name.get(&name).cloned(); + if tyid.is_none() { + return Err(mlua::Error::runtime(format!( + "failed to lookup type id from type name: {}", + name + ))); + } + let tyid = tyid.unwrap(); + drop(lookup); + + let registry = world.get_resource::().unwrap(); + let ty = registry + .get_type(tyid) + .expect("Could not find asset type in registry"); + let data = ty + .get_data::() + .expect("Asset type does not have 'LuaWrappedEventProxy' as TypeData") + .clone(); + drop(registry); + + data.reader(&mut world).into_lua(lua) + }, + ); } } diff --git a/lyra-scripting/src/lua/wrappers/events.rs b/lyra-scripting/src/lua/wrappers/events/device_event.rs similarity index 88% rename from lyra-scripting/src/lua/wrappers/events.rs rename to lyra-scripting/src/lua/wrappers/events/device_event.rs index 22af8b7..36b5d72 100644 --- a/lyra-scripting/src/lua/wrappers/events.rs +++ b/lyra-scripting/src/lua/wrappers/events/device_event.rs @@ -4,16 +4,18 @@ use lyra_game::{ math::Vec2, winit::{ dpi::PhysicalPosition, DeviceEvent, DeviceEventPair, DeviceId, MouseScrollDelta, - PhysicalKey, + PhysicalKey, RawKeyEvent, }, }; +use lyra_reflect::Reflect; use lyra_scripting_derive::wrap_lua_struct; +use crate as lyra_scripting; use crate::lyra_engine; use super::LuaVec2; -wrap_lua_struct!(DeviceId, skip(lua_reflect, lua_wrapper)); +wrap_lua_struct!(DeviceId, skip(lua_reflect)); /// Wraps [`DeviceEvent`] and implements [`IntoLua`] #[derive(Clone)] @@ -42,7 +44,7 @@ impl mlua::FromLua for LuaDeviceEventRaw { message: Some("expected Lua Table".into()), })?; - let type_str: String = table.get("type")?; + let type_str: String = table.get("kind")?; let type_str = type_str.as_str(); let ret = match type_str { @@ -125,8 +127,9 @@ impl mlua::FromLua for LuaDeviceEventRaw { message: Some(format!("unknown key code: '{}'", code_str)), }); } + let code = code.unwrap(); - let phy = PhysicalKey::Code(code); + let phy = PhysicalKey::Code(code.as_winit_keycode().unwrap()); Self(DeviceEvent::Key(RawKeyEvent { physical_key: phy, state, @@ -140,12 +143,19 @@ impl mlua::FromLua for LuaDeviceEventRaw { "unknown" => NativeKeyCode::Unidentified, "android" => NativeKeyCode::Android(native_code), "macos" => NativeKeyCode::MacOS(native_code as u16), - "windows" => NativeKeyCode::Windows(native_code), + "windows" => NativeKeyCode::Windows(native_code as u16), "xkb" => NativeKeyCode::Xkb(native_code), + _ => { + return Err(mlua::Error::FromLuaConversionError { + from: ty, + to: "LuaDeviceEventRaw".into(), + message: Some(format!("unknown native code type: {}", native_str)), + }); + } }; Self(DeviceEvent::Key(RawKeyEvent { - physical_key: PhysicalKey::Unidentified(native_code), + physical_key: PhysicalKey::Unidentified(native_code.into()), state, })) } else { @@ -179,17 +189,17 @@ impl mlua::IntoLua for LuaDeviceEventRaw { match self.0 { DeviceEvent::Added => { - table.set("type", "added")?; + table.set("kind", "added")?; } DeviceEvent::Removed => { - table.set("type", "removed")?; + table.set("kind", "removed")?; } DeviceEvent::MouseMotion { delta } => { - table.set("type", "mouse_motion")?; + table.set("kind", "mouse_motion")?; table.set("delta", LuaVec2(Vec2::new(delta.0 as _, delta.1 as _)))?; } DeviceEvent::MouseWheel { delta } => { - table.set("type", "mouse_wheel")?; + table.set("kind", "mouse_wheel")?; match delta { MouseScrollDelta::LineDelta(x, y) => { @@ -204,12 +214,12 @@ impl mlua::IntoLua for LuaDeviceEventRaw { } } DeviceEvent::Motion { axis, value } => { - table.set("type", "motion")?; + table.set("kind", "motion")?; table.set("axis", axis)?; table.set("value", value)?; } DeviceEvent::Button { button, state } => { - table.set("type", "button")?; + table.set("kind", "button")?; table.set("button", button)?; let state = match state { @@ -219,7 +229,7 @@ impl mlua::IntoLua for LuaDeviceEventRaw { table.set("state", state)?; } DeviceEvent::Key(raw_key_event) => { - table.set("type", "key")?; + table.set("kind", "key")?; let state = match raw_key_event.state { lyra_game::winit::ElementState::Pressed => "pressed", @@ -262,7 +272,16 @@ impl mlua::IntoLua for LuaDeviceEventRaw { } } -#[derive(Clone)] +impl LuaWrapper for LuaDeviceEventRaw { + type Wrap = DeviceEvent; + + #[inline(always)] + fn into_wrapped(self) -> Self::Wrap { + self.0 + } +} + +#[derive(Clone, Reflect)] pub struct LuaDeviceEvent(pub(crate) DeviceEventPair); impl std::ops::Deref for LuaDeviceEvent { @@ -292,8 +311,8 @@ impl mlua::FromLua for LuaDeviceEvent { let ev: LuaDeviceEventRaw = table.get("event")?; Ok(Self(DeviceEventPair { - device_src: id, - event: ev, + device_src: id.0, + event: ev.0, })) } } @@ -303,7 +322,6 @@ impl mlua::IntoLua for LuaDeviceEvent { let table = lua.create_table()?; table.set("device", LuaDeviceId(self.device_src))?; table.set("event", LuaDeviceEventRaw(self.event.clone()))?; - table.into_lua(lua) } } @@ -315,4 +333,9 @@ impl LuaWrapper for LuaDeviceEvent { fn into_wrapped(self) -> Self::Wrap { self.0 } -} + + #[inline(always)] + fn from_wrapped(wrap: Self::Wrap) -> Option { + Some(Self(wrap)) + } +} \ No newline at end of file diff --git a/lyra-scripting/src/lua/wrappers/events/mod.rs b/lyra-scripting/src/lua/wrappers/events/mod.rs new file mode 100644 index 0000000..2ff647e --- /dev/null +++ b/lyra-scripting/src/lua/wrappers/events/mod.rs @@ -0,0 +1,151 @@ +use std::{marker::PhantomData, sync::Arc}; + +use crate::lua::LuaWrapper; +use atomic_refcell::AtomicRefCell; +use lyra_ecs::World; +use lyra_game::{ + Event, EventReader, EventWriter, Events, +}; +use lyra_reflect::{FromType, Reflect}; +use mlua::{FromLua, IntoLua}; + +use super::LuaVec2; + +mod device_event; +pub use device_event::*; + +/// Helper trait to type erase EventReader +trait UntypedEventReader: Send + Sync { + fn next(&self, lua: &mlua::Lua) -> mlua::Result; +} + +/// Helper trait to type erase EventWriter +trait UntypedEventWriter: Send + Sync { + fn push(&self, lua: &mlua::Lua, event: mlua::Value) -> mlua::Result<()>; +} + +/// Event reader exposed to Lua +#[derive(Clone)] +pub struct LuaEventReader(Arc>); + +impl mlua::FromLua for LuaEventReader { + fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result { + let ty = value.type_name(); + let ud = value.as_userdata() + .ok_or(mlua::Error::FromLuaConversionError { from: ty, to: "EventReader".into(), message: None })?; + let reader = ud.borrow::()?; + Ok(reader.clone()) + } +} + +impl mlua::UserData for LuaEventReader { + fn add_methods>(methods: &mut M) { + methods.add_method("read", |lua, this, ()| { + let reg = lua.create_registry_value(this.clone())?; + + lua.create_function(move |lua: &mlua::Lua, ()| { + let reader = lua.registry_value::(®)?; + let reader = reader.0.borrow(); + reader.next(lua) + }) + }); + } +} + +/// Event writer exposed to Lua +#[derive(Clone)] +pub struct LuaEventWriter(Arc>); + +impl mlua::UserData for LuaEventWriter { + fn add_methods>(methods: &mut M) { + methods.add_method("write", |lua, this, event: mlua::Value| { + let writer = this.0.borrow(); + writer.push(lua, event)?; + Ok(()) + }); + } +} + +struct TypedReader +where + T: Reflect + LuaWrapper + IntoLua + Send + Sync, + T::Wrap: Clone + Event, +{ + reader: EventReader, + _marker: PhantomData, +} + +impl UntypedEventReader for TypedReader +where + T: Reflect + LuaWrapper + IntoLua + Send + Sync, + T::Wrap: Clone + Event, +{ + fn next(&self, lua: &mlua::Lua) -> mlua::Result { + self.reader + .read() + .map(|e| T::from_wrapped(e.clone()).into_lua(lua)) + .unwrap_or(Ok(mlua::Value::Nil)) + } +} + +struct TypedWriter +where + T: Reflect + LuaWrapper + FromLua, + T::Wrap: Clone + Event, +{ + writer: EventWriter, + _marker: PhantomData, +} + +impl UntypedEventWriter for TypedWriter +where + T: Reflect + LuaWrapper + FromLua, + T::Wrap: Clone + Event, +{ + fn push(&self, lua: &mlua::Lua, event: mlua::Value) -> mlua::Result<()> { + let ev = T::from_lua(event, lua)?.into_wrapped(); + self.writer.write(ev); + Ok(()) + } +} + +#[derive(Clone)] +pub struct LuaWrappedEventProxy { + fn_reader: for<'a> fn(world: &'a mut World) -> Arc>, + fn_writer: for<'a> fn(world: &'a mut World) -> Arc>, +} + +impl FromType for LuaWrappedEventProxy +where + T: Reflect + LuaWrapper + FromLua + IntoLua + Send + Sync, + T::Wrap: Clone + Event, +{ + fn from_type() -> Self { + Self { + fn_reader: |world| { + let events = world.get_resource_or_default::>(); + Arc::new(AtomicRefCell::new(TypedReader { + reader: events.reader(), + _marker: PhantomData::, + })) + }, + fn_writer: |world| { + let events = world.get_resource_or_default::>(); + Arc::new(AtomicRefCell::new(TypedWriter { + writer: events.writer(), + _marker: PhantomData::, + })) + }, + } + } +} + +impl LuaWrappedEventProxy { + pub fn reader(&self, world: &mut World) -> LuaEventReader { + LuaEventReader((self.fn_reader)(world)) + } + + pub fn writer(&self, world: &mut World) -> LuaEventWriter { + LuaEventWriter((self.fn_writer)(world)) + } +} diff --git a/lyra-scripting/src/lua/wrappers/input_actions.rs b/lyra-scripting/src/lua/wrappers/input_actions.rs index be75765..400cf2f 100644 --- a/lyra-scripting/src/lua/wrappers/input_actions.rs +++ b/lyra-scripting/src/lua/wrappers/input_actions.rs @@ -1,4 +1,4 @@ -use lyra_game::input::{keycode_from_str, Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource, ActionState, LayoutId, MouseAxis, MouseInput}; +use lyra_game::input::{Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource, ActionState, KeyCode, LayoutId, MouseAxis, MouseInput}; use mlua::IntoLua; use crate::{lua::Error, lyra_engine}; @@ -185,7 +185,7 @@ impl LuaWrapper for LuaActionHandler { } fn process_keyboard_string(key_name: &str) -> Option { - let key = keycode_from_str(key_name)?; + let key = KeyCode::from_str(key_name)?; Some(ActionSource::Keyboard(key)) } -- 2.40.1 From d0e6fc6ecd975cd2d5a73d81c1b0c34497b7d9d2 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sun, 13 Oct 2024 12:30:06 -0400 Subject: [PATCH 14/15] lua: make it easier to expose events and asset handle wrappers --- .../lyra-scripting-derive/src/handle_macro.rs | 8 + lyra-scripting/src/lua/mod.rs | 60 ++- lyra-scripting/src/lua/providers/ecs.rs | 37 +- lyra-scripting/src/lua/wrappers/asset/gltf.rs | 237 ++++++++++++ lyra-scripting/src/lua/wrappers/asset/mod.rs | 346 ++---------------- 5 files changed, 335 insertions(+), 353 deletions(-) create mode 100644 lyra-scripting/src/lua/wrappers/asset/gltf.rs diff --git a/lyra-scripting/lyra-scripting-derive/src/handle_macro.rs b/lyra-scripting/lyra-scripting-derive/src/handle_macro.rs index cb2b4fa..28cb1ae 100644 --- a/lyra-scripting/lyra-scripting-derive/src/handle_macro.rs +++ b/lyra-scripting/lyra-scripting-derive/src/handle_macro.rs @@ -257,5 +257,13 @@ pub(crate) fn lua_wrap_handle_impl(input: proc_macro::TokenStream) -> proc_macro self.0 } } + + impl LuaHandleWrapper for #wrapper_name { + type ResourceType = #handle_path; + + fn from_handle(handle: ResHandle) -> Self { + Self(handle) + } + } }.into() } \ No newline at end of file diff --git a/lyra-scripting/src/lua/mod.rs b/lyra-scripting/src/lua/mod.rs index 787c850..a04e86f 100644 --- a/lyra-scripting/src/lua/mod.rs +++ b/lyra-scripting/src/lua/mod.rs @@ -2,6 +2,7 @@ pub mod dynamic_iter; pub use dynamic_iter::*; pub mod world; +use lyra_resource::ResourceData; use mlua::ObjectLike; pub use world::*; @@ -19,11 +20,12 @@ pub use proxy::*; pub mod system; pub use system::*; +use wrappers::{LuaHandleWrapper, LuaResHandleToComponent, LuaWrappedEventProxy}; use std::{any::TypeId, sync::Mutex}; use lyra_ecs::World; -use lyra_reflect::{Reflect, TypeRegistry}; +use lyra_reflect::{FromType, Reflect, TypeRegistry}; use crate::ScriptBorrow; pub type LuaContext = Mutex; @@ -140,10 +142,27 @@ pub trait RegisterLuaType { T: Clone + mlua::FromLua + mlua::IntoLua + LuaWrapper + 'static, T::Wrap: lyra_ecs::Component + Reflect; + /// Register an asset handle wrapper. + fn register_asset_handle(&mut self, name: &str) + where + T: LuaHandleWrapper + Reflect + LuaProxy, + T::ResourceType: ResourceData; + + /// Add an entry for a non-component in the [`TypeLookup`] table. + fn add_lua_event(&mut self, name: &str) + where + T: Reflect + LuaWrapper + mlua::FromLua + mlua::IntoLua + Send + Sync, + T::Wrap: Clone + lyra_game::Event; + /// Add an entry for a component in the [`TypeLookup`] table. fn add_component_lookup_entry(&mut self, name: &str) where T: lyra_ecs::Component; + + /// Add an entry for a non-component in the [`TypeLookup`] table. + fn add_lookup_entry(&mut self, name: &str) + where + T: 'static; } impl RegisterLuaType for World { @@ -188,6 +207,37 @@ impl RegisterLuaType for World { self.add_component_lookup_entry::(name); } + fn register_asset_handle(&mut self, name: &str) + where + T: LuaHandleWrapper + Reflect + LuaProxy, + T::ResourceType: ResourceData + { + { + let mut registry = self.get_resource_mut::().unwrap(); + let reg_type = registry.get_type_or_default(TypeId::of::()); + reg_type.add_data(ReflectLuaProxy::from_lua_proxy::()); + let l: LuaResHandleToComponent = FromType::::from_type(); + reg_type.add_data(l); + } + + self.add_lookup_entry::(name); + } + + fn add_lua_event(&mut self, name: &str) + where + T: Reflect + LuaWrapper + mlua::FromLua + mlua::IntoLua + Send + Sync, + T::Wrap: Clone + lyra_game::Event + { + { + let mut registry = self.get_resource_mut::().unwrap(); + let reg_type = registry.get_type_or_default(TypeId::of::()); + let proxy: LuaWrappedEventProxy = FromType::::from_type(); + reg_type.add_data(proxy); + } + + self.add_lookup_entry::(name); + } + fn add_component_lookup_entry(&mut self, name: &str) where T: lyra_ecs::Component @@ -196,6 +246,14 @@ impl RegisterLuaType for World { lookup.comp_info_from_name.insert(name.into(), lyra_ecs::ComponentInfo::new::()); lookup.typeid_from_name.insert(name.into(), std::any::TypeId::of::()); } + + fn add_lookup_entry(&mut self, name: &str) + where + T: 'static + { + let mut lookup = self.get_resource_or_default::(); + lookup.typeid_from_name.insert(name.into(), std::any::TypeId::of::()); + } } impl mlua::FromLua for ScriptBorrow { diff --git a/lyra-scripting/src/lua/providers/ecs.rs b/lyra-scripting/src/lua/providers/ecs.rs index 896550a..c26fd0d 100644 --- a/lyra-scripting/src/lua/providers/ecs.rs +++ b/lyra-scripting/src/lua/providers/ecs.rs @@ -1,11 +1,7 @@ -use std::any::TypeId; - use lyra_ecs::ResourceObject; -use lyra_game::winit::DeviceEventPair; -use lyra_reflect::{FromType, Reflect, TypeRegistry}; -use lyra_resource::gltf::Gltf; +use lyra_reflect::Reflect; -use crate::{lua::{wrappers::*, LuaContext, LuaWrapper, ReflectLuaProxy, RegisterLuaType, TypeLookup, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE}, ScriptApiProvider, ScriptBorrow, ScriptData, ScriptDynamicBundle, ScriptWorldPtr}; +use crate::{lua::{wrappers::*, LuaContext, LuaWrapper, RegisterLuaType, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE}, ScriptApiProvider, ScriptBorrow, ScriptData, ScriptDynamicBundle, ScriptWorldPtr}; //fn register_lua_proxy::(); world.register_lua_wrapper::(); world.register_lua_wrapper::(); + world.register_lua_convert_component::("Camera"); world.register_lua_convert_component::("FreeFlyCamera"); + world.register_lua_wrapper::(); world.register_lua_convert::(); world.register_lua_convert::(); + world.add_lua_event::("DeviceEvent"); - // Add typeid of 'DeviceEvent' - let mut lookup = world.get_resource_or_default::(); - lookup.typeid_from_name.insert("DeviceEvent".into(), std::any::TypeId::of::()); - drop(lookup); - - let mut registry = world.get_resource_mut::().unwrap(); - - // add LuaWrappedEventProxy - let reg_type = registry.get_type_or_default(TypeId::of::()); - let l: LuaWrappedEventProxy = FromType::::from_type(); - reg_type.add_data(l); - - // add Gltf handle - let reg_type = registry.get_type_or_default(TypeId::of::()); - reg_type.add_data(ReflectLuaProxy::from_lua_proxy::()); - let l = LuaResHandleToComponent::new( - |lua, res| { - if let Some(gltf) = res.as_typed::() { - Some(lua.create_userdata(LuaGltfHandle(gltf)).unwrap()) - } else { - None - } - } - ); - reg_type.add_data(l); + world.register_asset_handle::("Gltf"); } fn expose_api(&mut self, _: &ScriptData, ctx: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> { diff --git a/lyra-scripting/src/lua/wrappers/asset/gltf.rs b/lyra-scripting/src/lua/wrappers/asset/gltf.rs new file mode 100644 index 0000000..4968e33 --- /dev/null +++ b/lyra-scripting/src/lua/wrappers/asset/gltf.rs @@ -0,0 +1,237 @@ +use std::{any::TypeId, ops::Deref}; +use lyra_resource::{gltf::{Gltf, Material, Mesh}, FilterMode, ResHandle, Texture, WrappingMode}; +use lyra_game::scene::SceneGraph; +use lyra_reflect::Reflect; +use lyra_scripting_derive::{lua_wrap_handle, wrap_lua_struct}; + +use crate::{lua::{LuaWrapper, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE}, lyra_engine, ScriptBorrow}; +use crate as lyra_scripting; + +use super::LuaHandleWrapper; + +use mlua::IntoLua; + +fn filter_mode_to_str(fm: FilterMode) -> &'static str { + match fm { + FilterMode::Nearest => "nearest", + FilterMode::Linear => "linear", + } +} + +fn wrapping_mode_to_str(wm: WrappingMode) -> &'static str { + match wm { + WrappingMode::ClampToEdge => "clamp_to_edge", + WrappingMode::MirroredRepeat => "mirrored_repeat", + WrappingMode::Repeat => "repeat", + } +} + +wrap_lua_struct!(lyra_resource::TextureSampler, + // this can be safely skipped since it wont be a component or resource. + skip(lua_reflect), + fields={ + // don't need to specify field types since setters are skipped + (mag_filter, skip_set, get={ + this.mag_filter.map(|f| filter_mode_to_str(f)) + .into_lua(lua) + }), + (min_filter, skip_set, get={ + this.min_filter.map(|f| filter_mode_to_str(f)) + .into_lua(lua) + }), + (mipmap_filter, skip_set, get={ + this.mipmap_filter.map(|f| filter_mode_to_str(f)) + .into_lua(lua) + }), + (wrap_u, skip_set, get={ + wrapping_mode_to_str(this.wrap_u) + .into_lua(lua) + }), + (wrap_v, skip_set, get={ + wrapping_mode_to_str(this.wrap_v) + .into_lua(lua) + }), + (wrap_w, skip_set, get={ + wrapping_mode_to_str(this.wrap_w) + .into_lua(lua) + }), + } +); + +wrap_lua_struct!(lyra_resource::gltf::PbrGlossiness, + // this can be safely skipped since it wont be a component or resource. + skip(lua_reflect), + fields={ + (diffuse_color: wrap(crate::lua::wrappers::LuaVec4), skip_set), + (specular: wrap(crate::lua::wrappers::LuaVec3), skip_set), + (glossiness, skip_set), + } +); + +wrap_lua_struct!(lyra_resource::gltf::Specular, + // this can be safely skipped since it wont be a component or resource. + skip(lua_reflect), + fields={ + (factor, skip_set), + (color_factor: wrap(crate::lua::wrappers::LuaVec3), skip_set), + (texture, skip_set, get={ + this.texture.clone() + .map(|t| LuaTextureHandle(t)) + .into_lua(lua) + }), + (color_texture, skip_set, get={ + this.color_texture.clone() + .map(|t| LuaTextureHandle(t)) + .into_lua(lua) + }) + } +); + +// TODO: fields +lua_wrap_handle!(SceneGraph, name=Scene, {}); + +lua_wrap_handle!(Mesh, + field_getters={ + (material, { + data.material.clone() + .map(|v| LuaMaterialHandle(v.clone())) + .into_lua(lua) + }) + }, + { + methods.add_method("indices", |lua, this, ()| { + if let Some(data) = this.0.data_ref() { + let table = lua.create_table()?; + + match &data.indices { + Some(lyra_resource::gltf::MeshIndices::U16(v)) => { + for (i, ind) in v.iter().enumerate() { + let i = i as i64 + 1; // lua indexes start at 1 + table.raw_set(i, *ind)?; + } + }, + Some(lyra_resource::gltf::MeshIndices::U32(v)) => { + for (i, ind) in v.iter().enumerate() { + let i = i as i64 + 1; // lua indexes start at 1 + table.raw_set(i, *ind)?; + } + }, + None => {}, + } + + Ok(mlua::Value::Table(table)) + } else { + Ok(mlua::Value::Nil) + } + }); + + // TODO: mesh attributes + } +); + +lua_wrap_handle!(lyra_resource::Image, + field_getters={ + (width, { + data.width().into_lua(lua) + }), + (height, { + data.height().into_lua(lua) + }) + } +); + +lua_wrap_handle!(Texture, + field_getters={ + (image, wrapper=LuaImageHandle), + (sampler, { + data.sampler.clone() + .map(|s| LuaTextureSampler(s)) + .into_lua(lua) + }) + } + +); + +lua_wrap_handle!(Material, + field_getters={ + shader_uuid, + name, + double_sided, + (base_color, wrapper=crate::lua::wrappers::LuaVec4), + metallic, + roughness, + (base_color_texture, { + data.base_color_texture.clone() + .map(|v| LuaTextureHandle(v.clone())) + .into_lua(lua) + }), + (metallic_roughness_texture, { + data.metallic_roughness_texture.clone() + .map(|v| LuaTextureHandle(v.clone())) + .into_lua(lua) + }), + (pbr_glossiness, { + data.pbr_glossiness.clone() + .map(|v| LuaPbrGlossiness(v.clone())) + .into_lua(lua) + }), + alpha_cutoff, + (alpha_mode, { + match data.alpha_mode { + lyra_resource::gltf::AlphaMode::Opaque => "opaque", + lyra_resource::gltf::AlphaMode::Mask => "mask", + lyra_resource::gltf::AlphaMode::Blend => "blend", + }.into_lua(lua) + }), + (specular, { + data.specular.clone() + .map(|v| LuaSpecular(v.clone())) + .into_lua(lua) + }), + } +); + +lua_wrap_handle!(Gltf, { + methods.add_method("scenes", |lua, this, ()| { + if let Some(data) = this.0.data_ref() { + let table = lua.create_table()?; + + for (i, scene) in data.scenes.iter().enumerate() { + let i = i as i64 + 1; // lua indexes start at 1 + table.raw_set(i, LuaSceneHandle(scene.clone()))?; + } + + Ok(mlua::Value::Table(table)) + } else { + Ok(mlua::Value::Nil) + } + }); + methods.add_method("materials", |lua, this, ()| { + if let Some(data) = this.0.data_ref() { + let table = lua.create_table()?; + + for (i, mat) in data.materials.iter().enumerate() { + let i = i as i64 + 1; // lua indexes start at 1 + table.raw_set(i, LuaMaterialHandle(mat.clone()))?; + } + + Ok(mlua::Value::Table(table)) + } else { + Ok(mlua::Value::Nil) + } + }); + methods.add_method("meshes", |lua, this, ()| { + if let Some(data) = this.0.data_ref() { + let table = lua.create_table()?; + + for (i, mesh) in data.meshes.iter().enumerate() { + let i = i as i64 + 1; // lua indexes start at 1 + table.raw_set(i, LuaMeshHandle(mesh.clone()))?; + } + + Ok(mlua::Value::Table(table)) + } else { + Ok(mlua::Value::Nil) + } + }); +}); \ No newline at end of file diff --git a/lyra-scripting/src/lua/wrappers/asset/mod.rs b/lyra-scripting/src/lua/wrappers/asset/mod.rs index a1a3798..5dab8ca 100644 --- a/lyra-scripting/src/lua/wrappers/asset/mod.rs +++ b/lyra-scripting/src/lua/wrappers/asset/mod.rs @@ -1,21 +1,37 @@ -use std::{any::TypeId, ops::Deref}; -//use mlua::{AnyUserData, IntoLua, FromLua, Lua, Value}; -use lyra_resource::{gltf::{Gltf, Material, Mesh}, FilterMode, ResHandle, Texture, UntypedResHandle, WrappingMode}; -use lyra_game::scene::SceneGraph; -use lyra_reflect::{Reflect, TypeData}; -use lyra_scripting_derive::{lua_wrap_handle, wrap_lua_struct}; +use lyra_resource::{ResHandle, ResourceData, UntypedResHandle}; +use lyra_reflect::FromType; +use crate::lua::LuaWrapper; -use crate::{lua::{Error, LuaWrapper, FN_NAME_INTERNAL_AS_COMPONENT, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE}, lyra_engine, ScriptBorrow}; +mod gltf; +pub use gltf::*; -use crate as lyra_scripting; +pub trait LuaHandleWrapper: LuaWrapper + mlua::UserData + Send + 'static { + type ResourceType: lyra_resource::ResourceData; -use mlua::IntoLua; + fn from_handle(handle: ResHandle) -> Self; +} +#[derive(Clone)] pub struct LuaResHandleToComponent { /// Create the userdata component that pub fn_to_lua: fn(&mlua::Lua, UntypedResHandle) -> Option, } +impl FromType for LuaResHandleToComponent +where + T: LuaHandleWrapper, + T::ResourceType: ResourceData, +{ + fn from_type() -> Self { + Self { + fn_to_lua: |lua: &mlua::Lua, handle: UntypedResHandle| { + handle.as_typed::() + .map(|res| lua.create_userdata(T::from_handle(res)).unwrap()) + }, + } + } +} + impl LuaResHandleToComponent { pub fn new(f: fn(&mlua::Lua, UntypedResHandle) -> Option) -> Self { Self { @@ -23,315 +39,3 @@ impl LuaResHandleToComponent { } } } - -impl TypeData for LuaResHandleToComponent { - fn as_any(&self) -> &dyn std::any::Any { - self - } - - fn as_any_mut(&mut self) -> &mut dyn std::any::Any { - self - } - - fn boxed_clone(&self) -> Box { - todo!() - } -} - -#[derive(Clone)] -pub struct LuaResHandle(pub UntypedResHandle); - -impl Deref for LuaResHandle { - type Target = UntypedResHandle; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl From for LuaResHandle { - fn from(value: UntypedResHandle) -> Self { - LuaResHandle(value) - } -} - -impl mlua::UserData for LuaResHandle { - fn add_fields>(fields: &mut F) { - fields.add_field_method_get("path", |_, this| Ok(this.path())); - fields.add_field_method_get("version", |_, this| Ok(this.version())); - fields.add_field_method_get("uuid", |_, this| Ok(this.uuid().to_string())); - fields.add_field_method_get("state", |_, this| { - let name = if this.is_loaded() { - "ready" - } else if this.get_error().is_some() { - "error" - } else { "loading" }; - - Ok(name) - }); - } - - fn add_methods>(methods: &mut M) { - methods.add_method("is_watched", |_, this, ()| { - Ok(this.is_watched()) - }); - - methods.add_method("is_loaded", |_, this, ()| { - Ok(this.is_loaded()) - }); - - methods.add_method("wait_until_loaded", |_, this, ()| { - this.wait_recurse_dependencies_load(); - - Ok(()) - }); - - methods.add_method(FN_NAME_INTERNAL_AS_COMPONENT, |lua, this, ()| { - let handle = &this.0; - - if let Some(handle) = handle.as_typed::() { - LuaSceneHandle(handle).into_lua(lua) - } else if let Some(handle) = handle.as_typed::() { - LuaGltfHandle(handle).into_lua(lua) - } else { - Ok(mlua::Value::Nil) - } - }); - } -} - -impl mlua::FromLua for LuaResHandle { - fn from_lua(val: mlua::Value, _: &mlua::Lua) -> mlua::Result { - let tyname = val.type_name(); - let ud = val.as_userdata() - .ok_or(mlua::Error::external(Error::type_mismatch("Handle", &tyname)))?; - let handle = ud.borrow::()?; - - Ok(handle.clone()) - } -} - -fn filter_mode_to_str(fm: FilterMode) -> &'static str { - match fm { - FilterMode::Nearest => "nearest", - FilterMode::Linear => "linear", - } -} - -fn wrapping_mode_to_str(wm: WrappingMode) -> &'static str { - match wm { - WrappingMode::ClampToEdge => "clamp_to_edge", - WrappingMode::MirroredRepeat => "mirrored_repeat", - WrappingMode::Repeat => "repeat", - } -} - -wrap_lua_struct!(lyra_resource::TextureSampler, - // this can be safely skipped since it wont be a component or resource. - skip(lua_reflect), - fields={ - // don't need to specify field types since setters are skipped - (mag_filter, skip_set, get={ - this.mag_filter.map(|f| filter_mode_to_str(f)) - .into_lua(lua) - }), - (min_filter, skip_set, get={ - this.min_filter.map(|f| filter_mode_to_str(f)) - .into_lua(lua) - }), - (mipmap_filter, skip_set, get={ - this.mipmap_filter.map(|f| filter_mode_to_str(f)) - .into_lua(lua) - }), - (wrap_u, skip_set, get={ - wrapping_mode_to_str(this.wrap_u) - .into_lua(lua) - }), - (wrap_v, skip_set, get={ - wrapping_mode_to_str(this.wrap_v) - .into_lua(lua) - }), - (wrap_w, skip_set, get={ - wrapping_mode_to_str(this.wrap_w) - .into_lua(lua) - }), - } -); - -wrap_lua_struct!(lyra_resource::gltf::PbrGlossiness, - // this can be safely skipped since it wont be a component or resource. - skip(lua_reflect), - fields={ - (diffuse_color: wrap(crate::lua::wrappers::LuaVec4), skip_set), - (specular: wrap(crate::lua::wrappers::LuaVec3), skip_set), - (glossiness, skip_set), - } -); - -wrap_lua_struct!(lyra_resource::gltf::Specular, - // this can be safely skipped since it wont be a component or resource. - skip(lua_reflect), - fields={ - (factor, skip_set), - (color_factor: wrap(crate::lua::wrappers::LuaVec3), skip_set), - (texture, skip_set, get={ - this.texture.clone() - .map(|t| LuaTextureHandle(t)) - .into_lua(lua) - }), - (color_texture, skip_set, get={ - this.color_texture.clone() - .map(|t| LuaTextureHandle(t)) - .into_lua(lua) - }) - } -); - -// TODO: fields -lua_wrap_handle!(SceneGraph, name=Scene, {}); - -lua_wrap_handle!(Mesh, - field_getters={ - (material, { - data.material.clone() - .map(|v| LuaMaterialHandle(v.clone())) - .into_lua(lua) - }) - }, - { - methods.add_method("indices", |lua, this, ()| { - if let Some(data) = this.0.data_ref() { - let table = lua.create_table()?; - - match &data.indices { - Some(lyra_resource::gltf::MeshIndices::U16(v)) => { - for (i, ind) in v.iter().enumerate() { - let i = i as i64 + 1; // lua indexes start at 1 - table.raw_set(i, *ind)?; - } - }, - Some(lyra_resource::gltf::MeshIndices::U32(v)) => { - for (i, ind) in v.iter().enumerate() { - let i = i as i64 + 1; // lua indexes start at 1 - table.raw_set(i, *ind)?; - } - }, - None => {}, - } - - Ok(mlua::Value::Table(table)) - } else { - Ok(mlua::Value::Nil) - } - }); - - // TODO: mesh attributes - } -); - -lua_wrap_handle!(lyra_resource::Image, - field_getters={ - (width, { - data.width().into_lua(lua) - }), - (height, { - data.height().into_lua(lua) - }) - } -); - -lua_wrap_handle!(Texture, - field_getters={ - (image, wrapper=LuaImageHandle), - (sampler, { - data.sampler.clone() - .map(|s| LuaTextureSampler(s)) - .into_lua(lua) - }) - } - -); - -lua_wrap_handle!(Material, - field_getters={ - shader_uuid, - name, - double_sided, - (base_color, wrapper=crate::lua::wrappers::LuaVec4), - metallic, - roughness, - (base_color_texture, { - data.base_color_texture.clone() - .map(|v| LuaTextureHandle(v.clone())) - .into_lua(lua) - }), - (metallic_roughness_texture, { - data.metallic_roughness_texture.clone() - .map(|v| LuaTextureHandle(v.clone())) - .into_lua(lua) - }), - (pbr_glossiness, { - data.pbr_glossiness.clone() - .map(|v| LuaPbrGlossiness(v.clone())) - .into_lua(lua) - }), - alpha_cutoff, - (alpha_mode, { - match data.alpha_mode { - lyra_resource::gltf::AlphaMode::Opaque => "opaque", - lyra_resource::gltf::AlphaMode::Mask => "mask", - lyra_resource::gltf::AlphaMode::Blend => "blend", - }.into_lua(lua) - }), - (specular, { - data.specular.clone() - .map(|v| LuaSpecular(v.clone())) - .into_lua(lua) - }), - } -); - -lua_wrap_handle!(Gltf, { - methods.add_method("scenes", |lua, this, ()| { - if let Some(data) = this.0.data_ref() { - let table = lua.create_table()?; - - for (i, scene) in data.scenes.iter().enumerate() { - let i = i as i64 + 1; // lua indexes start at 1 - table.raw_set(i, LuaSceneHandle(scene.clone()))?; - } - - Ok(mlua::Value::Table(table)) - } else { - Ok(mlua::Value::Nil) - } - }); - methods.add_method("materials", |lua, this, ()| { - if let Some(data) = this.0.data_ref() { - let table = lua.create_table()?; - - for (i, mat) in data.materials.iter().enumerate() { - let i = i as i64 + 1; // lua indexes start at 1 - table.raw_set(i, LuaMaterialHandle(mat.clone()))?; - } - - Ok(mlua::Value::Table(table)) - } else { - Ok(mlua::Value::Nil) - } - }); - methods.add_method("meshes", |lua, this, ()| { - if let Some(data) = this.0.data_ref() { - let table = lua.create_table()?; - - for (i, mesh) in data.meshes.iter().enumerate() { - let i = i as i64 + 1; // lua indexes start at 1 - table.raw_set(i, LuaMeshHandle(mesh.clone()))?; - } - - Ok(mlua::Value::Table(table)) - } else { - Ok(mlua::Value::Nil) - } - }); -}); \ No newline at end of file -- 2.40.1 From d001e136d088fb543e2ab832008e27175eb15d2f Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Thu, 17 Oct 2024 17:11:46 -0400 Subject: [PATCH 15/15] lua: expose WorldTransform --- examples/lua-scripting/scripts/test.lua | 10 ++-- examples/lua-scripting/src/main.rs | 21 ++++---- lyra-scene/src/world_transform.rs | 3 +- .../lyra-scripting-derive/src/to_lua_macro.rs | 54 +++++++++++++++---- .../scripts/lua/types/ecs/window.lua | 2 + .../scripts/lua/types/ecs/world_transform.lua | 9 ++++ lyra-scripting/src/lua/providers/ecs.rs | 7 ++- lyra-scripting/src/lua/wrappers/mod.rs | 5 +- .../src/lua/wrappers/world_transform.rs | 49 +++++++++++++++++ 9 files changed, 131 insertions(+), 29 deletions(-) create mode 100644 lyra-scripting/scripts/lua/types/ecs/world_transform.lua create mode 100644 lyra-scripting/src/lua/wrappers/world_transform.rs diff --git a/examples/lua-scripting/scripts/test.lua b/examples/lua-scripting/scripts/test.lua index 63a273c..2c4fb14 100644 --- a/examples/lua-scripting/scripts/test.lua +++ b/examples/lua-scripting/scripts/test.lua @@ -93,13 +93,15 @@ function on_update() ---@type number local dt = world:resource(DeltaTime) - --[[ world:view( + world:view( ---@param t Transform - function (t) + ---@param wt WorldTransform + function (t, wt) + print("Entity is at: " .. tostring(wt)) t:translate(0, 0.15 * dt, 0) return t - end, Transform - ) ]] + end, Transform, WorldTransform + ) --[[ world:view( ---@param c Camera diff --git a/examples/lua-scripting/src/main.rs b/examples/lua-scripting/src/main.rs index e0436f9..7835d03 100644 --- a/examples/lua-scripting/src/main.rs +++ b/examples/lua-scripting/src/main.rs @@ -1,18 +1,10 @@ use lyra_engine::{ - assets::{gltf::Gltf, ResourceManager}, - game::App, - input::{ + assets::{gltf::Gltf, ResourceManager}, game::App, input::{ Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource, InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput, - }, - script::{lua::{LuaScript, LuaScriptingPlugin}, Script, ScriptList}, - math::{self, Transform, Vec3}, - render::light::directional::DirectionalLight, - scene::{ - CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform, - ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN, - ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN, - }, + }, math::{self, Transform, Vec3}, render::light::directional::DirectionalLight, scene::{ + self, CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform, ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN, ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN + }, script::{lua::{LuaScript, LuaScriptingPlugin}, Script, ScriptList} }; #[async_std::main] @@ -91,6 +83,11 @@ async fn main() { app.with_plugin(setup_scene_plugin); app.with_plugin(action_handler_plugin); app.with_plugin(setup_script_plugin); + app.with_system( + "update_world_transforms", + scene::system_update_world_transforms, + &[], + ); //app.with_plugin(camera_debug_plugin); app.with_plugin(FreeFlyCameraPlugin); app.run(); diff --git a/lyra-scene/src/world_transform.rs b/lyra-scene/src/world_transform.rs index bd964f8..dfd5529 100644 --- a/lyra-scene/src/world_transform.rs +++ b/lyra-scene/src/world_transform.rs @@ -2,6 +2,7 @@ use std::ops::Deref; use lyra_ecs::{query::{filter::{Has, Not}, Entities, View}, relation::{ChildOf, RelationOriginComponent}, Component, Entity, World}; use lyra_math::Transform; +use lyra_reflect::Reflect; use crate::lyra_engine; /// The world transform of an entity. @@ -11,7 +12,7 @@ use crate::lyra_engine; /// you should use its [`Transform`]. You cannot mutate [`WorldTransform`] as its managed completey /// by the [`system_update_world_transforms`] system. For the WorldTransform to work properly, you /// must have both a [`Transform`] and [`WorldTransform`] on the entities in the scene. -#[derive(Debug, Copy, Clone, PartialEq, Default, Component)] +#[derive(Debug, Copy, Clone, PartialEq, Default, Component, Reflect)] pub struct WorldTransform(pub(crate) Transform); impl Deref for WorldTransform { diff --git a/lyra-scripting/lyra-scripting-derive/src/to_lua_macro.rs b/lyra-scripting/lyra-scripting-derive/src/to_lua_macro.rs index 225d14e..5e6d34c 100644 --- a/lyra-scripting/lyra-scripting-derive/src/to_lua_macro.rs +++ b/lyra-scripting/lyra-scripting-derive/src/to_lua_macro.rs @@ -47,7 +47,7 @@ fn field_table_getter(field: &Field) -> proc_macro2::TokenStream { } } -fn wrapper_creation(wrapper: &syn::Ident, type_path: &syn::Path, create: Option<&syn::Block>, fields: &Vec) -> proc_macro2::TokenStream { +fn wrapper_creation(wrapper: &syn::Ident, type_path: &syn::Path, struct_type: StructType, create: Option<&syn::Block>, fields: &Vec) -> proc_macro2::TokenStream { match create { Some(b) => quote!(#b), @@ -59,19 +59,28 @@ fn wrapper_creation(wrapper: &syn::Ident, type_path: &syn::Path, create: Option< }); */ let field_iter = fields.iter().map(|f| { let ident = &f.field; - if f.field_ty.is_wrapped() { + if f.field_ty.is_wrapped() && struct_type == StructType::Fields { quote!(#ident: (*#ident).clone()) } else { quote!(#ident) } }); - quote! { - #wrapper(#type_path { - #( - #field_iter - ),* - }) + match struct_type { + StructType::Fields => { + quote! { + #wrapper(#type_path { + #( + #field_iter + ),* + }) + } + }, + StructType::Tuple => { + quote! { + #wrapper(#type_path( #(#field_iter),* )) + } + }, } } } @@ -104,8 +113,17 @@ enum ReflectType { Resource, } +/// The type of the wrapping struct +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +enum StructType { + #[default] + Fields, + Tuple, +} + struct IntoLuaUsage { type_path: syn::Path, + struct_type: StructType, override_name: Option, table_name: String, derives: Vec, @@ -126,6 +144,7 @@ impl syn::parse::Parse for IntoLuaUsage { let mut s = Self { type_path, + struct_type: StructType::Fields, override_name: None, table_name: lua_name, derives: vec![], @@ -149,6 +168,23 @@ impl syn::parse::Parse for IntoLuaUsage { let name: syn::Ident = input.parse()?; s.override_name = Some(name); }, + "struct_type" => { + let _eq: Token![=] = input.parse()?; + + let st_token = input.parse::()?; + let st_str = st_token.value().to_lowercase(); + let st_str = st_str.as_str(); + + let st = match st_str { + "fields" => StructType::Fields, + "tuple" => StructType::Tuple, + _ => return Err(syn::Error::new_spanned( + st_token, + format!("unknown struct type: '{}', expected 'fields', or `tuple`", st_str), + )), + }; + s.struct_type = st; + }, "lua_name" => { let _eq: Token![=] = input.parse()?; s.table_name = input.parse::()?.value(); @@ -242,7 +278,7 @@ pub fn to_lua_struct_impl(input: proc_macro::TokenStream) -> proc_macro::TokenSt let lua_name = &input.table_name; let field_getters_iter = input.fields.iter().map(|f| field_table_getter(f)); let field_setters_iter = input.fields.iter().map(|f| field_table_setter(f)); - let struct_creator = wrapper_creation(&wrapper, type_path, input.create.as_ref(), &input.fields); + let struct_creator = wrapper_creation(&wrapper, type_path, input.struct_type, input.create.as_ref(), &input.fields); let reflect_fn = get_reflect_lua_functions(reflect_type, &input.type_path, true); let reflect_type_fn = get_reflect_lua_functions(reflect_type, &input.type_path, false); diff --git a/lyra-scripting/scripts/lua/types/ecs/window.lua b/lyra-scripting/scripts/lua/types/ecs/window.lua index cedacb7..76179c4 100644 --- a/lyra-scripting/scripts/lua/types/ecs/window.lua +++ b/lyra-scripting/scripts/lua/types/ecs/window.lua @@ -1,3 +1,5 @@ +---@meta + ---@class Window: userdata Window = { ---Gets or sets the window's focus. diff --git a/lyra-scripting/scripts/lua/types/ecs/world_transform.lua b/lyra-scripting/scripts/lua/types/ecs/world_transform.lua new file mode 100644 index 0000000..4bf9627 --- /dev/null +++ b/lyra-scripting/scripts/lua/types/ecs/world_transform.lua @@ -0,0 +1,9 @@ +---@meta + +--- A Transform represents the relative position of the entity to its parent entity, while +--- a world transform is the position relative to the World. When wanting to move an entity, +--- you should use its [`Transform`]. You cannot mutate [`WorldTransform`] as its managed completey +--- by the [`system_update_world_transforms`] system. For the WorldTransform to work properly, you +--- must have both a [`Transform`] and [`WorldTransform`] on the entities in the scene. +---@alias WorldTransform Transform The world transform of an entity. +WorldTransform = {} \ No newline at end of file diff --git a/lyra-scripting/src/lua/providers/ecs.rs b/lyra-scripting/src/lua/providers/ecs.rs index c26fd0d..1ec3d13 100644 --- a/lyra-scripting/src/lua/providers/ecs.rs +++ b/lyra-scripting/src/lua/providers/ecs.rs @@ -19,6 +19,7 @@ impl ScriptApiProvider for LyraEcsApiProvider { world.register_lua_convert_component::("Camera"); world.register_lua_convert_component::("FreeFlyCamera"); + world.register_lua_convert_component::("WorldTransform"); world.register_lua_wrapper::(); world.register_lua_convert::(); @@ -44,6 +45,7 @@ impl ScriptApiProvider for LyraEcsApiProvider { expose_comp_table_wrapper::(&ctx, &globals, "Camera")?; expose_comp_table_wrapper::(&ctx, &globals, "FreeFlyCamera")?; + expose_comp_table_wrapper::(&ctx, &globals, "WorldTransform")?; expose_table_wrapper::(&ctx, &globals, "DeviceEvent")?; let dt_table = create_reflect_table::(&ctx)?; @@ -86,11 +88,12 @@ where table.set(mlua::MetaMethod::Type.name(), name)?; - Ok(table) } -/// Expose a wrapper that converts to/from a lua type. +/// Expose a wrapper of a component that converts to/from a lua type. +/// +/// This type of wrapper could convert to/from a Lua table, or number, string, etc., any Lua type. /// /// This creates the reflection functions on a table specified in globals. /// The table name is set to `name`, which is also how the script will use the table. diff --git a/lyra-scripting/src/lua/wrappers/mod.rs b/lyra-scripting/src/lua/wrappers/mod.rs index c942ffc..f6e32ad 100644 --- a/lyra-scripting/src/lua/wrappers/mod.rs +++ b/lyra-scripting/src/lua/wrappers/mod.rs @@ -20,4 +20,7 @@ mod free_fly_camera; pub use free_fly_camera::*; mod events; -pub use events::*; \ No newline at end of file +pub use events::*; + +mod world_transform; +pub use world_transform::*; \ No newline at end of file diff --git a/lyra-scripting/src/lua/wrappers/world_transform.rs b/lyra-scripting/src/lua/wrappers/world_transform.rs new file mode 100644 index 0000000..ab28662 --- /dev/null +++ b/lyra-scripting/src/lua/wrappers/world_transform.rs @@ -0,0 +1,49 @@ +use crate::lua::{wrappers::LuaTransform, LuaWrapper}; +use crate::lyra_engine; +use lyra_game::scene::WorldTransform; +use lyra_reflect::Reflect; + +#[derive(Clone, Default, Reflect)] +pub struct LuaWorldTransform(pub(crate) WorldTransform); + +impl std::ops::Deref for LuaWorldTransform { + type Target = WorldTransform; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::DerefMut for LuaWorldTransform { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl mlua::FromLua for LuaWorldTransform { + fn from_lua(v: mlua::Value, lua: &mlua::Lua) -> mlua::Result { + let t = LuaTransform::from_lua(v, lua)?; + Ok(Self(WorldTransform::from(*t))) + } +} + +impl mlua::IntoLua for LuaWorldTransform { + fn into_lua(self, lua: &mlua::Lua) -> mlua::Result { + let t = LuaTransform(*self.0); + t.into_lua(lua) + } +} + +impl LuaWrapper for LuaWorldTransform { + type Wrap = WorldTransform; + + #[inline(always)] + fn into_wrapped(self) -> Self::Wrap { + self.0 + } + + #[inline(always)] + fn from_wrapped(wrap: Self::Wrap) -> Option { + Some(Self(wrap)) + } +} -- 2.40.1