Create an early scripting engine #2

Merged
SeanOMik merged 42 commits from feature/early-scripting into main 2024-03-03 03:28:57 +00:00
100 changed files with 6958 additions and 1170 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "lyra-scripting/elua"]
path = lyra-scripting/elua
url = git@git.seanomik.net:SeanOMik/elua.git

2
.vscode/launch.json vendored
View File

@ -72,7 +72,7 @@
"--no-run", "--no-run",
"--lib", "--lib",
"--package=lyra-ecs", "--package=lyra-ecs",
"world::tests::view_change_tracking", "command::tests::deferred_commands",
"--", "--",
"--exact --nocapture" "--exact --nocapture"
], ],

1130
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -10,11 +10,12 @@ members = [
"lyra-ecs", "lyra-ecs",
"lyra-reflect", "lyra-reflect",
"lyra-scripting", "lyra-scripting",
"lyra-game"] "lyra-game", "lyra-math"]
[features] [features]
scripting = ["dep:lyra-scripting"] scripting = ["dep:lyra-scripting"]
lua_scripting = ["scripting", "lyra-scripting/lua"]
[dependencies] [dependencies]
lyra-game = { path = "lyra-game" } lyra-game = { path = "lyra-game" }
lyra-scripting = { path = "lyra-scripting", optional = true } lyra-scripting = { path = "lyra-scripting", optional = true }

View File

@ -6,7 +6,8 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
lyra-engine = { path = "../../", version = "0.0.1", features = ["scripting"] } lyra-engine = { path = "../../", version = "0.0.1", features = ["lua_scripting"] }
lyra-scripting = { path = "../../lyra-scripting", features = ["lua", "teal"] }
#lyra-ecs = { path = "../../lyra-ecs"} #lyra-ecs = { path = "../../lyra-ecs"}
anyhow = "1.0.75" anyhow = "1.0.75"
async-std = "1.12.0" async-std = "1.12.0"

View File

@ -0,0 +1,85 @@
function on_init()
local cube = world:request_res("assets/cube-texture-embedded.gltf")
print("Loaded textured cube")
local pos = Transform.from_translation(Vec3.new(0, 0, -8.0))
local e = world:spawn(pos, cube)
print("spawned entity " .. tostring(e))
local handler = ActionHandler.new {
layouts = { 0 },
actions = {
MoveForwardBackward = "Axis",
MoveLeftRight = "Axis",
MoveUpDown = "Axis",
LookLeftRight = "Axis",
LookUpDown = "Axis",
LookRoll = "Axis",
ObjectsMoveUpDown = "Axis"
},
mappings = {
{
layout = 0,
binds = {
MoveForwardBackward = {
"key:w=1.0", "key:s=-1.0"
},
MoveLeftRight = {
"key:a=-1.0", "key:d=1.0"
},
MoveUpDown = {
"key:c=1.0", "key:z=-1.0"
},
LookLeftRight = {
"key:left=-1.0", "key:right=1.0",
"mouse:axis:x"
},
LookUpDown = {
"key:up=-1.0", "key:down=1.0",
"mouse:axis:y",
},
LookRoll = {
"key:e=-1.0", "key:q=1.0",
},
ObjectsMoveUpDown = {
"key:u=1.0", "key:j=-1.0"
}
}
}
}
}
world:add_resource(handler)
end
--[[ function on_first()
print("Lua's first function was called")
end
function on_pre_update()
print("Lua's pre-update function was called")
end ]]
function on_update()
---@type number
local dt = world:resource(DeltaTime)
local act = world:resource(ActionHandler)
---@type number
local move_objs = act:get_axis("ObjectsMoveUpDown")
world:view(function (t)
if move_objs ~= nil then
t:translate(0, move_objs * 0.35 * dt, 0)
return t
end
end, Transform)
end
--[[ function on_post_update()
print("Lua's post-update function was called")
end
function on_last()
print("Lua's last function was called")
end ]]

View File

@ -55,9 +55,9 @@ pub fn free_fly_camera_controller(delta_time: Res<DeltaTime>, handler: Res<Actio
let left = cam.transform.left(); let left = cam.transform.left();
let up = Vec3::Y; let up = Vec3::Y;
let move_y = handler.get_axis_modifier(CommonActionLabel::MoveUpDown).unwrap_or(0.0); let move_y = handler.get_axis_modifier("MoveUpDown").unwrap_or(0.0);
let move_x = handler.get_axis_modifier(CommonActionLabel::MoveLeftRight).unwrap_or(0.0); let move_x = handler.get_axis_modifier("MoveLeftRight").unwrap_or(0.0);
let move_z = handler.get_axis_modifier(CommonActionLabel::MoveForwardBackward).unwrap_or(0.0); let move_z = handler.get_axis_modifier("MoveForwardBackward").unwrap_or(0.0);
let mut velocity = Vec3::ZERO; let mut velocity = Vec3::ZERO;
velocity += move_y * up; velocity += move_y * up;
@ -68,9 +68,9 @@ pub fn free_fly_camera_controller(delta_time: Res<DeltaTime>, handler: Res<Actio
cam.transform.translation += velocity.normalize() * fly.speed * delta_time; // TODO: speeding up cam.transform.translation += velocity.normalize() * fly.speed * delta_time; // TODO: speeding up
} }
let motion_x = handler.get_axis_modifier(CommonActionLabel::LookLeftRight).unwrap_or(0.0); let motion_x = handler.get_axis_modifier("LookLeftRight").unwrap_or(0.0);
let motion_y = handler.get_axis_modifier(CommonActionLabel::LookUpDown).unwrap_or(0.0); let motion_y = handler.get_axis_modifier("LookUpDown").unwrap_or(0.0);
let motion_z = handler.get_axis_modifier(CommonActionLabel::LookRoll).unwrap_or(0.0); let motion_z = handler.get_axis_modifier("LookRoll").unwrap_or(0.0);
let mut camera_rot = Vec3::ZERO; let mut camera_rot = Vec3::ZERO;
camera_rot.y -= motion_x * fly.mouse_sensitivity; camera_rot.y -= motion_x * fly.mouse_sensitivity;

View File

@ -1,6 +1,6 @@
use std::ptr::NonNull; use std::ptr::NonNull;
use lyra_engine::{math::{self, Vec3}, math::Transform, input::{KeyCode, ActionHandler, Action, ActionKind, LayoutId, ActionMapping, ActionSource, ActionMappingId, InputActionPlugin, MouseInput, MouseAxis, CommonActionLabel}, game::Game, render::{window::{CursorGrabMode, WindowOptions}, light::{PointLight, directional::DirectionalLight, SpotLight}}, change_tracker::Ct, ecs::{system::{Criteria, CriteriaSchedule, BatchedSystem, IntoSystem}, world::World, Component}, DeltaTime, scene::{TransformComponent, ModelComponent, CameraComponent}}; use lyra_engine::{math::{self, Vec3}, math::Transform, input::{KeyCode, ActionHandler, Action, ActionKind, LayoutId, ActionMapping, ActionSource, ActionMappingId, InputActionPlugin, MouseInput, MouseAxis, CommonActionLabel}, game::Game, render::{window::{CursorGrabMode, WindowOptions}, light::{PointLight, directional::DirectionalLight, SpotLight}}, change_tracker::Ct, ecs::{system::{Criteria, CriteriaSchedule, BatchedSystem, IntoSystem}, world::World, Component}, DeltaTime, scene::{ModelComponent, CameraComponent}, lua::{LuaScriptingPlugin, LuaScript}, Script, ScriptList};
use lyra_engine::assets::{ResourceManager, Model}; use lyra_engine::assets::{ResourceManager, Model};
mod free_fly_camera; mod free_fly_camera;
@ -77,29 +77,29 @@ struct CubeFlag;
async fn main() { async fn main() {
let setup_sys = |world: &mut World| -> anyhow::Result<()> { let setup_sys = |world: &mut World| -> anyhow::Result<()> {
{ {
let mut window_options = world.get_resource_mut::<Ct<WindowOptions>>(); /* let mut window_options = world.get_resource_mut::<Ct<WindowOptions>>();
window_options.cursor_grab = CursorGrabMode::Confined; window_options.cursor_grab = CursorGrabMode::Confined;
window_options.cursor_visible = false; window_options.cursor_visible = false; */
} }
let mut resman = world.get_resource_mut::<ResourceManager>(); let mut resman = world.get_resource_mut::<ResourceManager>();
//let diffuse_texture = resman.request::<Texture>("assets/happy-tree.png").unwrap(); //let diffuse_texture = resman.request::<Texture>("assets/happy-tree.png").unwrap();
let antique_camera_model = resman.request::<Model>("assets/AntiqueCamera.glb").unwrap(); //let antique_camera_model = resman.request::<Model>("assets/AntiqueCamera.glb").unwrap();
//let cube_model = resman.request::<Model>("assets/cube-texture-bin.glb").unwrap(); //let cube_model = resman.request::<Model>("assets/cube-texture-bin.glb").unwrap();
let cube_model = resman.request::<Model>("assets/texture-sep/texture-sep.gltf").unwrap(); let cube_model = resman.request::<Model>("assets/texture-sep/texture-sep.gltf").unwrap();
let crate_model = resman.request::<Model>("assets/crate/crate.gltf").unwrap(); let crate_model = resman.request::<Model>("assets/crate/crate.gltf").unwrap();
drop(resman); drop(resman);
world.spawn(( /* world.spawn((
ModelComponent(antique_camera_model), ModelComponent(antique_camera_model),
TransformComponent::from(Transform::from_xyz(0.0, -5.0, -10.0)), Transform::from_xyz(0.0, -5.0, -10.0),
)); )); */
{ {
let cube_tran = Transform::from_xyz(-3.5, 0.0, -8.0); let cube_tran = Transform::from_xyz(-3.5, 0.0, -8.0);
//cube_tran.rotate_y(math::Angle::Degrees(180.0)); //cube_tran.rotate_y(math::Angle::Degrees(180.0));
world.spawn(( world.spawn((
TransformComponent::from(cube_tran), cube_tran,
ModelComponent(crate_model.clone()), ModelComponent(crate_model.clone()),
CubeFlag, CubeFlag,
)); ));
@ -117,7 +117,7 @@ async fn main() {
diffuse: 1.0, diffuse: 1.0,
specular: 1.3, specular: 1.3,
}, },
TransformComponent::from(light_tran), light_tran,
ModelComponent(cube_model.clone()), ModelComponent(cube_model.clone()),
)); ));
} }
@ -139,7 +139,7 @@ async fn main() {
diffuse: 7.0, diffuse: 7.0,
specular: 1.0, specular: 1.0,
}, },
TransformComponent::from(light_tran), Transform::from(light_tran),
ModelComponent(cube_model.clone()), ModelComponent(cube_model.clone()),
)); ));
} }
@ -183,7 +183,7 @@ async fn main() {
diffuse: 1.0, diffuse: 1.0,
specular: 1.3, specular: 1.3,
}, },
TransformComponent::from(light_tran), Transform::from(light_tran),
ModelComponent(cube_model), ModelComponent(cube_model),
)); ));
} }
@ -206,7 +206,7 @@ async fn main() {
Ok(()) Ok(())
}; };
let fps_plugin = move |game: &mut Game| { let fps_plugin = move |game: &mut Game| {
let world = game.world(); let world = game.world_mut();
world.add_resource(fps_counter::FPSCounter::new()); world.add_resource(fps_counter::FPSCounter::new());
}; };
@ -214,13 +214,13 @@ async fn main() {
const SPEED: f32 = 4.0; const SPEED: f32 = 4.0;
let delta_time = **world.get_resource::<DeltaTime>(); let delta_time = **world.get_resource::<DeltaTime>();
for (mut transform, _) in world.view_iter::<(&mut TransformComponent, &CubeFlag)>() { for (mut transform, _) in world.view_iter::<(&mut Transform, &CubeFlag)>() {
let t = &mut transform.transform; let t = &mut transform;
t.rotate_y(math::Angle::Degrees(SPEED * delta_time)); t.rotate_y(math::Angle::Degrees(SPEED * delta_time));
} }
for (mut transform, _s) in world.view_iter::<(&mut TransformComponent, &mut SpotLight)>() { for (mut transform, _s) in world.view_iter::<(&mut Transform, &mut SpotLight)>() {
let t = &mut transform.transform; let t = &mut transform;
t.rotate_x(math::Angle::Degrees(SPEED * delta_time)); t.rotate_x(math::Angle::Degrees(SPEED * delta_time));
} }
@ -228,19 +228,19 @@ async fn main() {
}; };
let jiggle_plugin = move |game: &mut Game| { let jiggle_plugin = move |game: &mut Game| {
game.world().add_resource(TpsAccumulator(0.0)); game.world_mut().add_resource(TpsAccumulator(0.0));
let mut sys = BatchedSystem::new(); let mut sys = BatchedSystem::new();
sys.with_criteria(FixedTimestep::new(45)); sys.with_criteria(FixedTimestep::new(45));
sys.with_system(spin_system.into_system()); sys.with_system(spin_system.into_system());
//sys.with_system(fps_system); //sys.with_system(fps_system);
game.with_system("fixed", sys, &[]); //game.with_system("fixed", sys, &[]);
fps_plugin(game); //fps_plugin(game);
}; };
let action_handler_plugin = |game: &mut Game| { let action_handler_plugin = |game: &mut Game| {
let action_handler = ActionHandler::new() /* let action_handler = ActionHandler::builder()
.add_layout(LayoutId::from(0)) .add_layout(LayoutId::from(0))
.add_action(CommonActionLabel::MoveForwardBackward, Action::new(ActionKind::Axis)) .add_action(CommonActionLabel::MoveForwardBackward, Action::new(ActionKind::Axis))
@ -250,7 +250,7 @@ async fn main() {
.add_action(CommonActionLabel::LookUpDown, Action::new(ActionKind::Axis)) .add_action(CommonActionLabel::LookUpDown, Action::new(ActionKind::Axis))
.add_action(CommonActionLabel::LookRoll, Action::new(ActionKind::Axis)) .add_action(CommonActionLabel::LookRoll, Action::new(ActionKind::Axis))
.add_mapping(ActionMapping::new(LayoutId::from(0), ActionMappingId::from(0)) .add_mapping(ActionMapping::builder(LayoutId::from(0), ActionMappingId::from(0))
.bind(CommonActionLabel::MoveForwardBackward, &[ .bind(CommonActionLabel::MoveForwardBackward, &[
ActionSource::Keyboard(KeyCode::W).into_binding_modifier(1.0), ActionSource::Keyboard(KeyCode::W).into_binding_modifier(1.0),
ActionSource::Keyboard(KeyCode::S).into_binding_modifier(-1.0) ActionSource::Keyboard(KeyCode::S).into_binding_modifier(-1.0)
@ -282,26 +282,31 @@ async fn main() {
.finish() .finish()
); );
/* #[allow(unused_variables)] let world = game.world_mut();
let test_system = |world: &mut World| -> anyhow::Result<()> { world.add_resource(action_handler); */
let handler = world.get_resource::<ActionHandler>();
if let Some(alpha) = handler.get_axis_modifier("look_rotate") {
debug!("'look_rotate': {alpha}");
}
Ok(())
}; */
game.world().add_resource(action_handler);
game.with_plugin(InputActionPlugin); game.with_plugin(InputActionPlugin);
//game.with_system("input_test", test_system, &[]); };
let script_test_plugin = |game: &mut Game| {
game.with_plugin(LuaScriptingPlugin);
let world = game.world_mut();
let mut res_man = world.get_resource_mut::<ResourceManager>();
let script = res_man.request::<LuaScript>("scripts/test.lua").unwrap();
res_man.watch("scripts/test.lua", false).unwrap();
drop(res_man);
let script = Script::new("test.lua", script);
let scripts = ScriptList::new(vec![script]);
world.spawn((scripts,));
}; };
Game::initialize().await Game::initialize().await
.with_plugin(lyra_engine::DefaultPlugins) .with_plugin(lyra_engine::DefaultPlugins)
.with_startup_system(setup_sys.into_system()) .with_startup_system(setup_sys.into_system())
.with_plugin(action_handler_plugin) .with_plugin(action_handler_plugin)
.with_plugin(script_test_plugin)
//.with_plugin(fps_plugin) //.with_plugin(fps_plugin)
.with_plugin(jiggle_plugin) .with_plugin(jiggle_plugin)
.with_plugin(FreeFlyCameraPlugin) .with_plugin(FreeFlyCameraPlugin)

View File

@ -5,10 +5,15 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
math = ["dep:lyra-math"]
[dependencies] [dependencies]
lyra-ecs-derive = { path = "./lyra-ecs-derive" } lyra-ecs-derive = { path = "./lyra-ecs-derive" }
lyra-math = { path = "../lyra-math", optional = true }
anyhow = "1.0.75" anyhow = "1.0.75"
thiserror = "1.0.50" thiserror = "1.0.50"
paste = "1.0.14"
[dev-dependencies] [dev-dependencies]
rand = "0.8.5" # used for tests rand = "0.8.5" # used for tests

View File

@ -1,6 +1,6 @@
use std::{ptr::{NonNull, self}, alloc::{self, Layout, alloc, dealloc}, mem, collections::HashMap, cell::{RefCell, Ref, RefMut, BorrowError, BorrowMutError}, ops::{DerefMut, Deref}}; use std::{ptr::{NonNull, self}, alloc::{self, Layout, alloc, dealloc}, mem, collections::HashMap, cell::{RefCell, Ref, RefMut, BorrowError, BorrowMutError}, ops::{DerefMut, Deref}};
use crate::{world::{Entity, ArchetypeEntityId}, bundle::Bundle, component_info::ComponentInfo, DynTypeId, Tick}; use crate::{world::ArchetypeEntityId, bundle::Bundle, component_info::ComponentInfo, DynTypeId, Tick, Entity};
#[derive(Clone)] #[derive(Clone)]
pub struct ComponentColumn { pub struct ComponentColumn {
@ -210,6 +210,8 @@ impl ArchetypeId {
pub struct Archetype { pub struct Archetype {
pub id: ArchetypeId, pub id: ArchetypeId,
pub(crate) entities: HashMap<Entity, ArchetypeEntityId>, pub(crate) entities: HashMap<Entity, ArchetypeEntityId>,
/// map an Archetype entity id to an entity
pub(crate) ids_to_entity: HashMap<ArchetypeEntityId, Entity>,
pub(crate) columns: Vec<ComponentColumn>, pub(crate) columns: Vec<ComponentColumn>,
pub capacity: usize, pub capacity: usize,
} }
@ -226,6 +228,7 @@ impl Archetype {
Archetype { Archetype {
id: new_id, id: new_id,
entities: HashMap::new(), entities: HashMap::new(),
ids_to_entity: HashMap::new(),
columns, columns,
capacity: DEFAULT_CAPACITY, capacity: DEFAULT_CAPACITY,
} }
@ -246,16 +249,17 @@ impl Archetype {
self.capacity = new_cap; self.capacity = new_cap;
} }
let entity_index = self.entities.len(); let entity_index = ArchetypeEntityId(self.entities.len() as u64);
self.entities.insert(entity, ArchetypeEntityId(entity_index as u64)); self.entities.insert(entity, entity_index);
self.ids_to_entity.insert(entity_index, entity);
bundle.take(|data, type_id, _size| { bundle.take(|data, type_id, _size| {
let col = self.get_column_mut(type_id).unwrap(); let col = self.get_column_mut(type_id).unwrap();
unsafe { col.set_at(entity_index, data, *tick); } unsafe { col.set_at(entity_index.0 as usize, data, *tick); }
col.len += 1; col.len += 1;
}); });
ArchetypeEntityId(entity_index as u64) entity_index
} }
/// Removes an entity from the Archetype and frees its components. Returns the entity record that took its place in the component column. /// Removes an entity from the Archetype and frees its components. Returns the entity record that took its place in the component column.
@ -286,6 +290,7 @@ impl Archetype {
// safe from the .expect at the start of this method. // safe from the .expect at the start of this method.
self.entities.remove(&entity).unwrap(); self.entities.remove(&entity).unwrap();
self.ids_to_entity.remove(&entity_index).unwrap();
// now change the ArchetypeEntityId to be the index that the moved entity was moved into. // now change the ArchetypeEntityId to be the index that the moved entity was moved into.
removed_entity.map(|(e, _a)| (e, entity_index)) removed_entity.map(|(e, _a)| (e, entity_index))
@ -351,14 +356,15 @@ impl Archetype {
self.capacity = new_cap; self.capacity = new_cap;
} }
let entity_index = self.entities.len(); let entity_index = ArchetypeEntityId(self.entities.len() as u64);
self.entities.insert(entity, ArchetypeEntityId(entity_index as u64)); self.entities.insert(entity, entity_index);
self.ids_to_entity.insert(entity_index, entity);
for col in self.columns.iter_mut() { for col in self.columns.iter_mut() {
col.len += 1; col.len += 1;
} }
ArchetypeEntityId(entity_index as u64) entity_index
} }
/// Moves the entity from this archetype into another one. /// Moves the entity from this archetype into another one.
@ -401,6 +407,10 @@ impl Archetype {
pub fn entities(&self) -> &HashMap<Entity, ArchetypeEntityId> { pub fn entities(&self) -> &HashMap<Entity, ArchetypeEntityId> {
&self.entities &self.entities
} }
pub fn entity_of_index(&self, id: ArchetypeEntityId) -> Option<Entity> {
self.ids_to_entity.get(&id).cloned()
}
} }
#[cfg(test)] #[cfg(test)]
@ -409,7 +419,7 @@ mod tests {
use rand::Rng; use rand::Rng;
use crate::{tests::{Vec2, Vec3}, world::{Entity, EntityId}, bundle::Bundle, ComponentInfo, MemoryLayout, DynTypeId, DynamicBundle, Tick}; use crate::{bundle::Bundle, tests::{Vec2, Vec3}, ComponentInfo, DynTypeId, DynamicBundle, Entity, EntityId, MemoryLayout, Tick};
use super::Archetype; use super::Archetype;

150
lyra-ecs/src/command.rs Normal file
View File

@ -0,0 +1,150 @@
use std::{any::Any, cell::RefMut, collections::VecDeque, ptr::{self, NonNull}};
use crate::{system::FnArgFetcher, Access, Bundle, Entities, Entity, World};
pub trait Command: Any {
fn as_any_boxed(self: Box<Self>) -> Box<dyn Any>;
fn run(self, world: &mut World) -> anyhow::Result<()>;
}
impl<F> Command for F
where
F: FnOnce(&mut World) -> anyhow::Result<()> + 'static
{
fn as_any_boxed(self: Box<Self>) -> Box<dyn Any> {
self
}
fn run(self, world: &mut World) -> anyhow::Result<()> {
self(world)
}
}
type RunCommand = unsafe fn(cmd: Box<dyn Command>, world: &mut World) -> anyhow::Result<()>;
#[derive(Default)]
pub struct CommandQueue(VecDeque<(RunCommand, Box<dyn Command>)>);
pub struct Commands<'a, 'b> {
queue: &'b mut CommandQueue,
entities: &'a mut Entities,
}
impl<'a, 'b> Commands<'a, 'b> {
pub fn new(queue: &'b mut CommandQueue, world: &'a mut World) -> Self {
Self {
queue,
entities: &mut world.entities,
}
}
/// Add a command to the end of the command queue
pub fn add<C: Command>(&mut self, cmd: C) {
let cmd = Box::new(cmd);
let run_fn = |cmd: Box<dyn Command>, world: &mut World| {
let cmd = cmd.as_any_boxed()
.downcast::<C>()
.unwrap();
cmd.run(world)?;
Ok(())
};
self.queue.0.push_back((run_fn, cmd));
}
pub fn spawn<B: Bundle + 'static>(&mut self, bundle: B) -> Entity {
let e = self.entities.reserve();
self.add(move |world: &mut World| {
world.spawn_into(e, bundle);
Ok(())
});
e
}
/// Execute all commands in the queue, in order of insertion
pub fn execute(&mut self, world: &mut World) -> anyhow::Result<()> {
while let Some((cmd_fn, cmd_ptr)) = self.queue.0.pop_front() {
unsafe {
cmd_fn(cmd_ptr, world)?;
}
}
Ok(())
}
}
impl FnArgFetcher for Commands<'_, '_> {
type State = CommandQueue;
type Arg<'a, 'state> = Commands<'a, 'state>;
fn world_access(&self) -> Access {
Access::Write
}
unsafe fn get<'a, 'state>(state: &'state mut Self::State, mut world_ptr: ptr::NonNull<World>) -> Self::Arg<'a, 'state> {
let world = world_ptr.as_mut();
Commands::new(state, world)
}
fn create_state(_: NonNull<World>) -> Self::State {
CommandQueue::default()
}
fn apply_deferred<'a>(mut state: Self::State, mut world_ptr: NonNull<World>) {
let world = unsafe { world_ptr.as_mut() };
let mut cmds = Commands::new(&mut state, world);
// safety: Commands has a mut borrow only to entities in the world
let world = unsafe { world_ptr.as_mut() };
cmds.execute(world).unwrap()
}
}
pub fn execute_deferred_commands(world: &mut World, mut commands: RefMut<Commands>) -> anyhow::Result<()> {
commands.execute(world)?;
Ok(())
}
#[cfg(test)]
mod tests {
use std::ptr::NonNull;
use crate::{system::{GraphExecutor, IntoSystem}, tests::Vec2, Commands, DynTypeId, World};
#[test]
fn deferred_commands() {
let mut world = World::new();
let vecs = vec![Vec2::rand(), Vec2::rand(), Vec2::rand()];
world.spawn((vecs[0],));
world.spawn((vecs[1],));
world.spawn((vecs[2],));
let spawned_vec = Vec2::rand();
let spawned_vec_cl = spawned_vec.clone();
let test_sys = move |mut commands: Commands| -> anyhow::Result<()> {
commands.spawn((spawned_vec_cl.clone(),));
Ok(())
};
let mut graph_exec = GraphExecutor::new();
graph_exec.insert_system("test", test_sys.into_system(), &[]);
graph_exec.execute(NonNull::from(&world), true).unwrap();
assert_eq!(world.entities.len(), 4);
// there's only one archetype
let arch = world.archetypes.values().next().unwrap();
let col = arch.get_column(DynTypeId::of::<Vec2>()).unwrap();
let vec2: &Vec2 = unsafe { col.get(3) };
assert_eq!(vec2.clone(), spawned_vec);
}
}

62
lyra-ecs/src/entity.rs Normal file
View File

@ -0,0 +1,62 @@
use std::collections::{HashMap, VecDeque};
use crate::Record;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct EntityId(pub u64);
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct Entity {
pub(crate) id: EntityId,
pub(crate) generation: u64,
}
pub struct Entities {
pub(crate) arch_index: HashMap<EntityId, Record>,
dead: VecDeque<Entity>,
next_id: EntityId,
}
impl Default for Entities {
fn default() -> Self {
Self {
arch_index: Default::default(),
dead: Default::default(),
next_id: EntityId(0),
}
}
}
impl Entities {
pub fn reserve(&mut self) -> Entity {
match self.dead.pop_front() {
Some(mut e) => {
e.generation += 1;
e
}
None => {
let new_id = self.next_id;
self.next_id.0 += 1;
Entity {
id: new_id,
generation: 0,
}
}
}
}
/// Returns the number of spawned entities
pub fn len(&self) -> usize {
self.next_id.0 as usize - self.dead.len()
}
/// Retrieves the Archetype Record for the entity
pub(crate) fn entity_record(&self, entity: Entity) -> Option<Record> {
self.arch_index.get(&entity.id).cloned()
}
pub(crate) fn insert_entity_record(&mut self, entity: Entity, record: Record) {
self.arch_index.insert(entity.id, record);
}
}

View File

@ -8,11 +8,19 @@ pub(crate) mod lyra_engine {
} }
pub mod archetype; pub mod archetype;
use std::ops::BitOr;
pub use archetype::*; pub use archetype::*;
pub mod entity;
pub use entity::*;
pub mod world; pub mod world;
pub use world::*; pub use world::*;
pub mod command;
pub use command::*;
pub mod bundle; pub mod bundle;
pub use bundle::*; pub use bundle::*;
@ -34,6 +42,10 @@ pub mod system;
pub mod tick; pub mod tick;
pub use tick::*; pub use tick::*;
/// Implements Component for glam math types
#[cfg(feature = "math")]
pub mod math;
pub use lyra_ecs_derive::*; pub use lyra_ecs_derive::*;
#[cfg(test)] #[cfg(test)]
@ -44,4 +56,18 @@ pub enum Access {
None, None,
Read, Read,
Write, Write,
}
impl BitOr for Access {
type Output = Access;
fn bitor(self, rhs: Self) -> Self::Output {
if self == Access::Write || rhs == Access::Write {
Access::Write
} else if self == Access::Read || rhs == Access::Read {
Access::Read
} else {
Access::None
}
}
} }

20
lyra-ecs/src/math.rs Normal file
View File

@ -0,0 +1,20 @@
use crate::Component;
use lyra_math::{Vec3, Quat, Vec2, Vec4, Mat4, Transform};
macro_rules! impl_external_component {
($type: ident) => {
impl Component for $type {
fn name() -> &'static str {
stringify!($type)
}
}
};
}
impl_external_component!(Vec2);
impl_external_component!(Vec3);
impl_external_component!(Vec4);
impl_external_component!(Mat4);
impl_external_component!(Quat);
impl_external_component!(Transform);

View File

@ -221,7 +221,7 @@ impl<T: Component> AsQuery for &mut T {
mod tests { mod tests {
use std::{mem::size_of, marker::PhantomData, ptr::NonNull}; use std::{mem::size_of, marker::PhantomData, ptr::NonNull};
use crate::{world::{World, Entity, EntityId}, archetype::{Archetype, ArchetypeId}, query::{View, Fetch}, tests::Vec2, bundle::Bundle, DynTypeId, Tick}; use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{Fetch, View}, tests::Vec2, world::World, DynTypeId, Entity, EntityId, Tick};
use super::{QueryBorrow, QueryBorrowMut, FetchBorrowMut}; use super::{QueryBorrow, QueryBorrowMut, FetchBorrowMut};

View File

@ -1,4 +1,4 @@
use crate::{world::{Entity, World}, archetype::Archetype}; use crate::{archetype::Archetype, world::World, Entity};
use super::{Fetch, Query, AsQuery}; use super::{Fetch, Query, AsQuery};

View File

@ -131,7 +131,7 @@ impl<'a, Q: Query> ViewOne<'a, Q> {
} }
pub fn get(&self) -> Option<Q::Item<'a>> { pub fn get(&self) -> Option<Q::Item<'a>> {
if let Some(record) = self.world.entity_index.get(&self.entity) { if let Some(record) = self.world.entities.arch_index.get(&self.entity) {
let arch = self.world.archetypes.get(&record.id) let arch = self.world.archetypes.get(&record.id)
.expect("An invalid record was specified for an entity"); .expect("An invalid record was specified for an entity");

View File

@ -6,7 +6,7 @@ impl<T: 'static> ResourceObject for T {}
/// A type erased storage for a Resource. /// A type erased storage for a Resource.
pub struct ResourceData { pub struct ResourceData {
data: Box<RefCell<dyn Any>>, pub(crate) data: Box<RefCell<dyn Any>>,
type_id: TypeId, type_id: TypeId,
} }

View File

@ -79,6 +79,10 @@ impl System for BatchedSystem {
Ok(()) Ok(())
} }
fn execute_deferred(&mut self, _: std::ptr::NonNull<World>) -> anyhow::Result<()> {
todo!()
}
} }
impl IntoSystem<()> for BatchedSystem { impl IntoSystem<()> for BatchedSystem {

View File

@ -1,13 +1,17 @@
use std::{ptr::NonNull, marker::PhantomData}; use std::{any::Any, marker::PhantomData, ptr::NonNull};
use paste::paste;
use crate::{world::World, Access, ResourceObject, query::{Query, View, AsQuery, ResMut, Res}}; use crate::{world::World, Access, ResourceObject, query::{Query, View, AsQuery, ResMut, Res}};
use super::{System, IntoSystem}; use super::{System, IntoSystem};
pub trait FnArgFetcher { pub trait FnArgFetcher {
type Arg<'a>: FnArg<Fetcher = Self>; /// stores data that persists after an execution of a system
type State: 'static;
type Arg<'a, 'state>: FnArgFetcher<State = Self::State>;
fn new() -> Self; fn create_state(world: NonNull<World>) -> Self::State;
/// Return the appropriate world access if this fetcher gets the world directly. /// Return the appropriate world access if this fetcher gets the world directly.
/// Return [`Access::None`] if you're only fetching components, or resources. /// Return [`Access::None`] if you're only fetching components, or resources.
@ -20,7 +24,10 @@ pub trait FnArgFetcher {
/// # Safety /// # Safety
/// The system executor must ensure that on execution of the system, it will be safe to /// The system executor must ensure that on execution of the system, it will be safe to
/// borrow the world. /// borrow the world.
unsafe fn get<'a>(&mut self, world: NonNull<World>) -> Self::Arg<'a>; unsafe fn get<'a, 'state>(state: &'state mut Self::State, world: NonNull<World>) -> Self::Arg<'a, 'state>;
/// Apply some action after the system was ran.
fn apply_deferred(state: Self::State, world: NonNull<World>);
} }
pub trait FnArg { pub trait FnArg {
@ -29,8 +36,10 @@ pub trait FnArg {
pub struct FnSystem<F, Args> { pub struct FnSystem<F, Args> {
inner: F, inner: F,
#[allow(dead_code)] //#[allow(dead_code)]
args: Args, //args: Args,
arg_state: Option<Vec<Box<dyn Any>>>,
_marker: PhantomData<Args>,
} }
macro_rules! impl_fn_system_tuple { macro_rules! impl_fn_system_tuple {
@ -38,47 +47,65 @@ macro_rules! impl_fn_system_tuple {
#[allow(non_snake_case)] #[allow(non_snake_case)]
impl<F, $($name: FnArgFetcher,)+> System for FnSystem<F, ($($name,)+)> impl<F, $($name: FnArgFetcher,)+> System for FnSystem<F, ($($name,)+)>
where where
F: for<'a> FnMut($($name::Arg<'a>,)+) -> anyhow::Result<()>, F: for<'a> FnMut($($name::Arg<'a, '_>,)+) -> anyhow::Result<()>,
{ {
fn world_access(&self) -> Access { fn world_access(&self) -> Access {
todo!() todo!()
} }
fn execute(&mut self, world: NonNull<World>) -> anyhow::Result<()> { fn execute(&mut self, world: NonNull<World>) -> anyhow::Result<()> {
$(let $name = unsafe { $name::new().get(world) };)+ unsafe {
paste! {
$(
// get the arg fetcher, create its state, and get the arg
let mut [<state_ $name:lower>]: $name::State = $name::create_state(world);
let [<$name:lower>] = $name::get(&mut [<state_ $name:lower>], world);
)+
(self.inner)($( [<$name:lower>] ),+)?;
let mut state = Vec::new();
$(
// type erase the now modified state, and store it
let boxed = Box::new([<state_ $name:lower>]) as Box<dyn Any>;
state.push(boxed);
)+
self.arg_state = Some(state);
}
Ok(())
}
}
fn execute_deferred(&mut self, world: NonNull<World>) -> anyhow::Result<()> {
let state = self.arg_state.as_mut().expect("Somehow there was no state");
state.reverse();
$(
let arg_state_box = state.pop()
.expect("Missing expected arg state");
let arg_state = *arg_state_box.downcast::<$name::State>()
.expect("Somehow the state cannot be downcasted from boxed Any");
$name::apply_deferred(arg_state, world);
)+
(self.inner)($($name,)+)?;
Ok(()) Ok(())
} }
} }
/* impl<F, $($name: FnArg,)+> IntoSystem for F impl<F, $($name: FnArgFetcher,)+> IntoSystem<($($name,)+)> for F
where where
F: FnMut($($name,)+) -> anyhow::Result<()>, F: FnMut($($name,)+) -> anyhow::Result<()>,
F: for<'a> FnMut($(<$name::Fetcher as FnArgFetcher>::Arg<'a>,)+) -> anyhow::Result<()>, F: for<'a> FnMut($($name::Arg<'a, '_>,)+) -> anyhow::Result<()>,
{ {
type System = FnSystem<F, ($($name::Fetcher,)+)>; type System = FnSystem<F, ($($name,)+)>;
fn into_system(self) -> Self::System { fn into_system(self) -> Self::System {
FnSystem { FnSystem {
args: ($($name::Fetcher::new(),)+), inner: self,
inner: self arg_state: None,
} _marker: PhantomData::<($($name,)+)>::default(),
}
} */
impl<F, $($name: FnArg,)+> IntoSystem<($($name,)+)> for F
where
F: FnMut($($name,)+) -> anyhow::Result<()>,
F: for<'a> FnMut($(<$name::Fetcher as FnArgFetcher>::Arg<'a>,)+) -> anyhow::Result<()>,
{
type System = FnSystem<F, ($($name::Fetcher,)+)>;
fn into_system(self) -> Self::System {
FnSystem {
args: ($($name::Fetcher::new(),)+),
inner: self
} }
} }
} }
@ -103,127 +130,116 @@ impl_fn_system_tuple!{ A, B, C, D, E, F2, G, H, I, J, K, L, M, N, O }
impl_fn_system_tuple!{ A, B, C, D, E, F2, G, H, I, J, K, L, M, N, O, P } impl_fn_system_tuple!{ A, B, C, D, E, F2, G, H, I, J, K, L, M, N, O, P }
/// An ArgFetcher implementation for query [`View`]s /// An ArgFetcher implementation for query [`View`]s
pub struct ViewArgFetcher<Q: AsQuery> { /* pub struct ViewArgFetcher<Q: AsQuery> {
query: Q::Query query: Q::Query
} }
impl<'a, Q: AsQuery> FnArg for View<'a, Q> { impl<'a, Q: AsQuery> FnArg for View<'a, Q> {
type Fetcher = ViewArgFetcher<Q>; type Fetcher = ViewArgFetcher<Q>;
} } */
impl<Q: AsQuery> FnArgFetcher for ViewArgFetcher<Q> { impl<'c, Q> FnArgFetcher for View<'c, Q>
type Arg<'a> = View<'a, Q>; where
Q: AsQuery,
fn new() -> Self { <Q as AsQuery>::Query: 'static
ViewArgFetcher { {
query: <Q::Query as Query>::new(), type State = Q::Query;
} type Arg<'a, 'state> = View<'a, Q>;
}
fn world_access(&self) -> Access { fn world_access(&self) -> Access {
todo!() todo!()
} }
unsafe fn get<'a>(&mut self, world: NonNull<World>) -> Self::Arg<'a> { unsafe fn get<'a, 'state>(state: &'state mut Self::State, world: NonNull<World>) -> Self::Arg<'a, 'state> {
let world = &*world.as_ptr(); let world = &*world.as_ptr();
let arch = world.archetypes.values().collect(); let arch = world.archetypes.values().collect();
let v = View::new(world, self.query, arch); let v = View::new(world, state.clone(), arch);
v v
} }
fn apply_deferred(_: Self::State, _: NonNull<World>) { }
fn create_state(_: NonNull<World>) -> Self::State {
<Q::Query as Query>::new()
}
} }
/// An ArgFetcher implementation for borrowing the [`World`]. /// An ArgFetcher implementation for borrowing the [`World`].
pub struct WorldArgFetcher; /* pub struct WorldArgFetcher;
impl<'a> FnArg for &'a World { impl<'a> FnArg for &'a World {
type Fetcher = WorldArgFetcher; type Fetcher = WorldArgFetcher;
} } */
impl FnArgFetcher for WorldArgFetcher { impl FnArgFetcher for &'_ World {
type Arg<'a> = &'a World; type State = ();
type Arg<'a, 'state> = &'a World;
fn new() -> Self {
WorldArgFetcher
}
fn world_access(&self) -> Access { fn world_access(&self) -> Access {
Access::Read Access::Read
} }
unsafe fn get<'a>(&mut self, world: NonNull<World>) -> Self::Arg<'a> { unsafe fn get<'a, 'state>(_: &'state mut Self::State, world: NonNull<World>) -> Self::Arg<'a, 'state> {
&*world.as_ptr() &*world.as_ptr()
} }
fn apply_deferred(_: Self::State, _: NonNull<World>) { }
fn create_state(_: NonNull<World>) -> Self::State { () }
} }
/// An ArgFetcher implementation for mutably borrowing the [`World`]. impl FnArgFetcher for &'_ mut World {
pub struct WorldMutArgFetcher; type State = ();
type Arg<'a, 'state> = &'a mut World;
impl<'a> FnArg for &'a mut World {
type Fetcher = WorldMutArgFetcher;
}
impl FnArgFetcher for WorldMutArgFetcher {
type Arg<'a> = &'a mut World;
fn new() -> Self {
WorldMutArgFetcher
}
fn world_access(&self) -> Access { fn world_access(&self) -> Access {
Access::Write Access::Write
} }
unsafe fn get<'a>(&mut self, world: NonNull<World>) -> Self::Arg<'a> { unsafe fn get<'a, 'state>(_: &'state mut Self::State, world: NonNull<World>) -> Self::Arg<'a, 'state> {
&mut *world.as_ptr() &mut *world.as_ptr()
} }
fn apply_deferred(_: Self::State, _: NonNull<World>) { }
fn create_state(_: NonNull<World>) -> Self::State { () }
} }
pub struct ResourceArgFetcher<R: ResourceObject> { /* pub struct ResourceArgFetcher<R: ResourceObject> {
phantom: PhantomData<fn() -> R> phantom: PhantomData<fn() -> R>
} }
impl<'a, R: ResourceObject> FnArg for Res<'a, R> { impl<'a, R: ResourceObject> FnArg for Res<'a, R> {
type Fetcher = ResourceArgFetcher<R>; type Fetcher = ResourceArgFetcher<R>;
} } */
impl<R: ResourceObject> FnArgFetcher for ResourceArgFetcher<R> { impl<R: ResourceObject> FnArgFetcher for Res<'_, R> {
type Arg<'a> = Res<'a, R>; type State = ();
type Arg<'a, 'state> = Res<'a, R>;
fn new() -> Self { unsafe fn get<'a, 'state>(_: &'state mut Self::State, world: NonNull<World>) -> Self::Arg<'a, 'state> {
ResourceArgFetcher {
phantom: PhantomData
}
}
unsafe fn get<'a>(&mut self, world: NonNull<World>) -> Self::Arg<'a> {
let world = world.as_ref(); let world = world.as_ref();
Res(world.get_resource::<R>()) Res(world.get_resource::<R>())
} }
fn apply_deferred(_: Self::State, _: NonNull<World>) { }
fn create_state(_: NonNull<World>) -> Self::State { () }
} }
pub struct ResourceMutArgFetcher<R: ResourceObject> { impl<R: ResourceObject> FnArgFetcher for ResMut<'_, R> {
phantom: PhantomData<fn() -> R> type State = ();
} type Arg<'a, 'state> = ResMut<'a, R>;
impl<'a, R: ResourceObject> FnArg for ResMut<'a, R> { unsafe fn get<'a, 'state>(_: &'state mut Self::State, world: NonNull<World>) -> Self::Arg<'a, 'state> {
type Fetcher = ResourceMutArgFetcher<R>;
}
impl<R: ResourceObject> FnArgFetcher for ResourceMutArgFetcher<R> {
type Arg<'a> = ResMut<'a, R>;
fn new() -> Self {
ResourceMutArgFetcher {
phantom: PhantomData
}
}
unsafe fn get<'a>(&mut self, world: NonNull<World>) -> Self::Arg<'a> {
let world = world.as_ref(); let world = world.as_ref();
ResMut(world.get_resource_mut::<R>()) ResMut(world.get_resource_mut::<R>())
} }
fn apply_deferred(_: Self::State, _: NonNull<World>) { }
fn create_state(_: NonNull<World>) -> Self::State { () }
} }
#[cfg(test)] #[cfg(test)]

View File

@ -2,14 +2,16 @@ use std::{collections::{HashMap, VecDeque, HashSet}, ptr::NonNull};
use super::System; use super::System;
use crate::world::World; use crate::{world::World, CommandQueue, Commands};
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum GraphExecutorError { pub enum GraphExecutorError {
#[error("could not find a system's dependency named `{0}`")] #[error("could not find a system's dependency named `{0}`")]
MissingSystem(String), MissingSystem(String),
#[error("system `{0}` returned with an error: `{1}`")] #[error("system `{0}` returned with an error: `{1}`")]
SystemError(String, anyhow::Error) SystemError(String, anyhow::Error),
#[error("a command returned with an error: `{0}`")]
Command(anyhow::Error)
} }
/// A single system in the graph. /// A single system in the graph.
@ -56,7 +58,7 @@ impl GraphExecutor {
} }
/// Executes the systems in the graph /// Executes the systems in the graph
pub fn execute(&mut self, world: NonNull<World>, stop_on_error: bool) -> Result<Vec<GraphExecutorError>, GraphExecutorError> { pub fn execute(&mut self, mut world_ptr: NonNull<World>, stop_on_error: bool) -> Result<Vec<GraphExecutorError>, GraphExecutorError> {
let mut stack = VecDeque::new(); let mut stack = VecDeque::new();
let mut visited = HashSet::new(); let mut visited = HashSet::new();
@ -69,7 +71,7 @@ impl GraphExecutor {
while let Some(node) = stack.pop_front() { while let Some(node) = stack.pop_front() {
let system = self.systems.get_mut(node.as_str()).unwrap(); let system = self.systems.get_mut(node.as_str()).unwrap();
if let Err(e) = system.system.execute(world) if let Err(e) = system.system.execute(world_ptr)
.map_err(|e| GraphExecutorError::SystemError(node, e)) { .map_err(|e| GraphExecutorError::SystemError(node, e)) {
if stop_on_error { if stop_on_error {
return Err(e); return Err(e);
@ -78,6 +80,36 @@ impl GraphExecutor {
possible_errors.push(e); possible_errors.push(e);
unimplemented!("Cannot resume topological execution from error"); // TODO: resume topological execution from error unimplemented!("Cannot resume topological execution from error"); // TODO: resume topological execution from error
} }
if let Err(e) = system.system.execute_deferred(world_ptr)
.map_err(|e| GraphExecutorError::Command(e)) {
if stop_on_error {
return Err(e);
}
possible_errors.push(e);
unimplemented!("Cannot resume topological execution from error"); // TODO: resume topological execution from error
}
let world = unsafe { world_ptr.as_mut() };
if let Some(mut queue) = world.try_get_resource_mut::<CommandQueue>() {
// Safety: Commands only borrows world.entities when adding commands
let world = unsafe { world_ptr.as_mut() };
let mut commands = Commands::new(&mut queue, world);
let world = unsafe { world_ptr.as_mut() };
if let Err(e) = commands.execute(world)
.map_err(|e| GraphExecutorError::Command(e)) {
if stop_on_error {
return Err(e);
}
possible_errors.push(e);
unimplemented!("Cannot resume topological execution from error"); // TODO: resume topological execution from error
}
}
} }
Ok(possible_errors) Ok(possible_errors)

View File

@ -27,6 +27,8 @@ pub trait System {
let _ = world; let _ = world;
Ok(()) Ok(())
} }
fn execute_deferred(&mut self, world: NonNull<World>) -> anyhow::Result<()>;
} }
pub trait IntoSystem<T> { pub trait IntoSystem<T> {

View File

@ -1,21 +1,12 @@
use std::{collections::{HashMap, VecDeque}, any::TypeId, cell::{Ref, RefMut}, ptr::NonNull}; use std::{any::TypeId, cell::{Ref, RefMut}, collections::HashMap, ptr::NonNull};
use crate::{archetype::{ArchetypeId, Archetype}, bundle::Bundle, query::{Query, ViewIter, View, AsQuery}, resource::ResourceData, query::{dynamic::DynamicView, ViewOne}, ComponentInfo, DynTypeId, TickTracker, Tick}; use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{dynamic::DynamicView, AsQuery, Query, View, ViewIter, ViewOne}, resource::ResourceData, ComponentInfo, DynTypeId, Entities, Entity, ResourceObject, Tick, TickTracker};
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct EntityId(pub u64);
/// The id of the entity for the Archetype. /// The id of the entity for the Archetype.
/// The Archetype struct uses this as the index in the component columns /// The Archetype struct uses this as the index in the component columns
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct ArchetypeEntityId(pub u64); pub struct ArchetypeEntityId(pub u64);
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct Entity {
pub(crate) id: EntityId,
pub(crate) generation: u64,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct Record { pub struct Record {
pub id: ArchetypeId, pub id: ArchetypeId,
@ -25,11 +16,9 @@ pub struct Record {
pub struct World { pub struct World {
pub(crate) archetypes: HashMap<ArchetypeId, Archetype>, pub(crate) archetypes: HashMap<ArchetypeId, Archetype>,
next_archetype_id: ArchetypeId, next_archetype_id: ArchetypeId,
pub(crate) entity_index: HashMap<EntityId, Record>,
dead_entities: VecDeque<Entity>,
next_entity_id: EntityId,
resources: HashMap<TypeId, ResourceData>, resources: HashMap<TypeId, ResourceData>,
tracker: TickTracker, tracker: TickTracker,
pub(crate) entities: Entities,
} }
impl Default for World { impl Default for World {
@ -37,11 +26,9 @@ impl Default for World {
Self { Self {
archetypes: HashMap::new(), archetypes: HashMap::new(),
next_archetype_id: ArchetypeId(0), next_archetype_id: ArchetypeId(0),
entity_index: HashMap::new(),
dead_entities: VecDeque::new(),
next_entity_id: EntityId(0),
resources: HashMap::new(), resources: HashMap::new(),
tracker: TickTracker::new(), tracker: TickTracker::new(),
entities: Entities::default(),
} }
} }
} }
@ -51,32 +38,30 @@ impl World {
Self::default() Self::default()
} }
/// Gets a new Entity, will recycle dead entities and increment their generation. /// Reserves an entity in the world
fn get_new_entity(&mut self) -> Entity { pub fn reserve_entity(&mut self) -> Entity {
match self.dead_entities.pop_front() { self.entities.reserve()
Some(mut e) => {
e.generation += 1;
e
},
None => {
let new_id = self.next_entity_id;
self.next_entity_id.0 += 1;
Entity {
id: new_id,
generation: 0,
}
}
}
} }
/// Spawns a new entity and inserts the component `bundle` into it.
pub fn spawn<B>(&mut self, bundle: B) -> Entity pub fn spawn<B>(&mut self, bundle: B) -> Entity
where
B: Bundle
{
let new_entity = self.reserve_entity();
self.spawn_into(new_entity, bundle);
new_entity
}
/// Spawn the components into a reserved entity. Only do this with entities that
/// were 'reserved' with [`World::reserve`]
///
/// # Safety
/// Do not use this method with an entity that is currently alive, it WILL cause undefined behavior.
pub fn spawn_into<B>(&mut self, entity: Entity, bundle: B)
where where
B: Bundle B: Bundle
{ {
let bundle_types = bundle.type_ids(); let bundle_types = bundle.type_ids();
let new_entity = self.get_new_entity();
let tick = self.tick(); let tick = self.tick();
@ -86,7 +71,11 @@ impl World {
.find(|a| a.is_archetype_for(&bundle_types)); .find(|a| a.is_archetype_for(&bundle_types));
if let Some(archetype) = archetype { if let Some(archetype) = archetype {
let arche_idx = archetype.add_entity(new_entity, bundle, &tick); // make at just one check to ensure you're not spawning twice
debug_assert!(!archetype.entities.contains_key(&entity),
"You attempted to spawn components into an entity that already exists!");
let arche_idx = archetype.add_entity(entity, bundle, &tick);
// Create entity record and store it // Create entity record and store it
let record = Record { let record = Record {
@ -94,14 +83,14 @@ impl World {
index: arche_idx, index: arche_idx,
}; };
self.entity_index.insert(new_entity.id, record); self.entities.insert_entity_record(entity, record);
} }
// create a new archetype if one isn't found // create a new archetype if one isn't found
else { else {
// create archetype // create archetype
let new_arch_id = self.next_archetype_id.increment(); let new_arch_id = self.next_archetype_id.increment();
let mut archetype = Archetype::from_bundle_info(new_arch_id, bundle.info()); let mut archetype = Archetype::from_bundle_info(new_arch_id, bundle.info());
let entity_arch_id = archetype.add_entity(new_entity, bundle, &tick); let entity_arch_id = archetype.add_entity(entity, bundle, &tick);
// store archetype // store archetype
self.archetypes.insert(new_arch_id, archetype); self.archetypes.insert(new_arch_id, archetype);
@ -113,27 +102,25 @@ impl World {
index: entity_arch_id, index: entity_arch_id,
}; };
self.entity_index.insert(new_entity.id, record); self.entities.insert_entity_record(entity, record);
} }
new_entity
} }
/// Despawn an entity from the World /// Despawn an entity from the World
pub fn despawn(&mut self, entity: Entity) { pub fn despawn(&mut self, entity: Entity) {
// Tick the tracker if the entity is spawned. This is done here instead of the `if let` // Tick the tracker if the entity is spawned. This is done here instead of the `if let`
// below due to the borrow checker complaining about multiple mutable borrows to self. // below due to the borrow checker complaining about multiple mutable borrows to self.
let tick = if self.entity_index.contains_key(&entity.id) { let tick = if self.entities.arch_index.contains_key(&entity.id) {
Some(self.tick()) Some(self.tick())
} else { None }; } else { None };
if let Some(record) = self.entity_index.get_mut(&entity.id) { if let Some(record) = self.entities.arch_index.get_mut(&entity.id) {
let tick = tick.unwrap(); let tick = tick.unwrap();
let arch = self.archetypes.get_mut(&record.id).unwrap(); let arch = self.archetypes.get_mut(&record.id).unwrap();
if let Some((moved, new_index)) = arch.remove_entity(entity, &tick) { if let Some((moved, new_index)) = arch.remove_entity(entity, &tick) {
// replace the archetype index of the moved index with its new index. // replace the archetype index of the moved index with its new index.
self.entity_index.get_mut(&moved.id).unwrap().index = new_index; self.entities.arch_index.get_mut(&moved.id).unwrap().index = new_index;
} }
} }
} }
@ -152,7 +139,7 @@ impl World {
let tick = self.tick(); let tick = self.tick();
let record = *self.entity_index.get(&entity.id).unwrap(); let record = self.entities.entity_record(entity).unwrap();
let current_arch = self.archetypes.get(&record.id).unwrap(); let current_arch = self.archetypes.get(&record.id).unwrap();
let mut col_types: Vec<DynTypeId> = current_arch.columns.iter().map(|c| c.info.type_id).collect(); let mut col_types: Vec<DynTypeId> = current_arch.columns.iter().map(|c| c.info.type_id).collect();
@ -188,7 +175,7 @@ impl World {
id: arch.id, id: arch.id,
index: res_index, index: res_index,
}; };
self.entity_index.insert(entity.id, new_record); self.entities.insert_entity_record(entity, new_record);
} else { } else {
let new_arch_id = self.next_archetype_id.increment(); let new_arch_id = self.next_archetype_id.increment();
let mut archetype = Archetype::from_bundle_info(new_arch_id, col_infos); let mut archetype = Archetype::from_bundle_info(new_arch_id, col_infos);
@ -202,13 +189,23 @@ impl World {
index: entity_arch_id, index: entity_arch_id,
}; };
self.entity_index.insert(entity.id, record); self.entities.insert_entity_record(entity, record);
} }
let current_arch = self.archetypes.get_mut(&record.id).unwrap(); let current_arch = self.archetypes.get_mut(&record.id).unwrap();
current_arch.remove_entity(entity, &tick); current_arch.remove_entity(entity, &tick);
} }
pub fn entity_archetype(&self, entity: Entity) -> Option<&Archetype> {
self.entities.entity_record(entity)
.and_then(|record| self.archetypes.get(&record.id))
}
pub fn entity_archetype_mut(&mut self, entity: Entity) -> Option<&mut Archetype> {
self.entities.entity_record(entity)
.and_then(|record| self.archetypes.get_mut(&record.id))
}
/// View into the world for a set of entities that satisfy the queries. /// View into the world for a set of entities that satisfy the queries.
pub fn view_iter<T: 'static + AsQuery>(&self) -> ViewIter<T::Query> { pub fn view_iter<T: 'static + AsQuery>(&self) -> ViewIter<T::Query> {
let archetypes = self.archetypes.values().collect(); let archetypes = self.archetypes.values().collect();
@ -245,15 +242,29 @@ impl World {
.get_mut() .get_mut()
} }
/// Get a resource from the world, or insert it into the world as its default.
pub fn get_resource_or_default<T: Default + 'static>(&mut self) -> RefMut<T>
{
self.resources.entry(TypeId::of::<T>())
.or_insert_with(|| ResourceData::new(T::default()))
.get_mut()
}
/// Gets a resource from the World. /// Gets a resource from the World.
/// ///
/// Will panic if the resource is not in the world. See [`try_get_resource`] for /// Will panic if the resource is not in the world. See [`try_get_resource`] for
/// a function that returns an option. /// a function that returns an option.
pub fn get_resource<T: 'static>(&self) -> Ref<T> { pub fn get_resource<T: 'static>(&self) -> Ref<T> {
self.resources.get(&TypeId::of::<T>()).unwrap() self.resources.get(&TypeId::of::<T>())
.expect(&format!("World is missing resource of type '{}'", std::any::type_name::<T>()))
.get() .get()
} }
/// Returns boolean indicating if the World contains a resource of type `T`.
pub fn has_resource<T: 'static>(&self) -> bool {
self.resources.contains_key(&TypeId::of::<T>())
}
/// Attempts to get a resource from the World. /// Attempts to get a resource from the World.
/// ///
/// Returns `None` if the resource was not found. /// Returns `None` if the resource was not found.
@ -267,7 +278,8 @@ impl World {
/// Will panic if the resource is not in the world. See [`try_get_resource_mut`] for /// Will panic if the resource is not in the world. See [`try_get_resource_mut`] for
/// a function that returns an option. /// a function that returns an option.
pub fn get_resource_mut<T: 'static>(&self) -> RefMut<T> { pub fn get_resource_mut<T: 'static>(&self) -> RefMut<T> {
self.resources.get(&TypeId::of::<T>()).unwrap() self.resources.get(&TypeId::of::<T>())
.expect(&format!("World is missing resource of type '{}'", std::any::type_name::<T>()))
.get_mut() .get_mut()
} }
@ -296,6 +308,12 @@ impl World {
pub fn tick_tracker(&self) -> &TickTracker { pub fn tick_tracker(&self) -> &TickTracker {
&self.tracker &self.tracker
} }
/// Attempts to find a resource in the world and returns a NonNull pointer to it
pub unsafe fn try_get_resource_ptr<T: ResourceObject>(&self) -> Option<NonNull<T>> {
self.resources.get(&TypeId::of::<T>())
.map(|d| unsafe { NonNull::new_unchecked(d.data.as_ptr() as *mut T) })
}
} }
#[cfg(test)] #[cfg(test)]
@ -348,7 +366,7 @@ mod tests {
world.despawn(middle_en); world.despawn(middle_en);
let record = world.entity_index.get(&last_en.id).unwrap(); let record = world.entities.entity_record(last_en).unwrap();
assert_eq!(record.index.0, 1); assert_eq!(record.index.0, 1);
} }

View File

@ -5,8 +5,9 @@ edition = "2021"
[dependencies] [dependencies]
lyra-resource = { path = "../lyra-resource" } lyra-resource = { path = "../lyra-resource" }
lyra-ecs = { path = "../lyra-ecs" } lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] }
lyra-reflect = { path = "../lyra-reflect" } lyra-reflect = { path = "../lyra-reflect", features = [ "math" ] }
lyra-math = { path = "../lyra-math" }
winit = "0.28.1" winit = "0.28.1"
tracing = "0.1.37" tracing = "0.1.37"

View File

@ -1,10 +1,11 @@
use instant::Instant; use instant::Instant;
use lyra_ecs::{Component, world::World}; use lyra_ecs::{Component, world::World};
use lyra_reflect::Reflect;
use crate::{plugin::Plugin, game::GameStages}; use crate::{plugin::Plugin, game::GameStages};
#[derive(Clone, Component)] #[derive(Clone, Component, Default, Reflect)]
pub struct DeltaTime(f32, Option<Instant>); pub struct DeltaTime(f32, #[reflect(skip)] Option<Instant>);
impl std::ops::Deref for DeltaTime { impl std::ops::Deref for DeltaTime {
type Target = f32; type Target = f32;
@ -36,7 +37,7 @@ pub struct DeltaTimePlugin;
impl Plugin for DeltaTimePlugin { impl Plugin for DeltaTimePlugin {
fn setup(&self, game: &mut crate::game::Game) { fn setup(&self, game: &mut crate::game::Game) {
game.world().add_resource(DeltaTime(0.0, None)); game.world_mut().add_resource(DeltaTime(0.0, None));
game.add_system_to_stage(GameStages::First, "delta_time", delta_time_system, &[]); game.add_system_to_stage(GameStages::First, "delta_time", delta_time_system, &[]);
} }
} }

View File

@ -78,6 +78,6 @@ pub struct EventsPlugin;
impl Plugin for EventsPlugin { impl Plugin for EventsPlugin {
fn setup(&self, game: &mut crate::game::Game) { fn setup(&self, game: &mut crate::game::Game) {
game.world().add_resource(EventQueue::new()); game.world_mut().add_resource(EventQueue::new());
} }
} }

View File

@ -246,11 +246,17 @@ impl Game {
} }
/// Get the world of this game /// Get the world of this game
pub fn world(&mut self) -> &mut World { pub fn world_mut(&mut self) -> &mut World {
// world is always `Some`, so unwrapping is safe // world is always `Some`, so unwrapping is safe
self.world.as_mut().unwrap() self.world.as_mut().unwrap()
} }
/// Get the world of this game
pub fn world(&self) -> &World {
// world is always `Some`, so unwrapping is safe
self.world.as_ref().unwrap()
}
/// Add a system to the ecs world /// Add a system to the ecs world
pub fn with_system<S, A>(&mut self, name: &str, system: S, depends: &[&str]) -> &mut Self pub fn with_system<S, A>(&mut self, name: &str, system: S, depends: &[&str]) -> &mut Self
where where
@ -313,12 +319,14 @@ impl Game {
self self
} }
/// Add a plugin to the game. These will be executed before the window is initiated and opened /// Add a plugin to the game. These are executed as they are added.
pub fn with_plugin<P>(&mut self, plugin: P) -> &mut Self pub fn with_plugin<P>(&mut self, plugin: P) -> &mut Self
where where
P: Plugin + 'static P: Plugin + 'static
{ {
self.plugins.push_back(Box::new(plugin)); let plugin = Box::new(plugin);
plugin.as_ref().setup(self);
self.plugins.push_back(plugin);
self self
} }
@ -339,16 +347,12 @@ impl Game {
tracing_subscriber::registry() tracing_subscriber::registry()
.with(fmt::layer().with_writer(stdout_layer)) .with(fmt::layer().with_writer(stdout_layer))
.with(filter::Targets::new() .with(filter::Targets::new()
.with_target("lyra_engine", Level::TRACE) // done by prefix, so it includes all lyra subpackages
.with_target("wgpu_core", Level::WARN) .with_target("lyra", Level::DEBUG)
.with_default(Level::DEBUG)) .with_target("wgpu", Level::WARN)
.with_default(Level::INFO))
.init(); .init();
// setup all the plugins
while let Some(plugin) = self.plugins.pop_front() {
plugin.as_ref().setup(self);
}
let world = self.world.take().unwrap_or_default(); let world = self.world.take().unwrap_or_default();
// run startup systems // run startup systems

View File

@ -2,6 +2,7 @@ use std::{collections::HashMap, ops::Deref, hash::{Hash, DefaultHasher, Hasher},
use glam::Vec2; use glam::Vec2;
use lyra_ecs::world::World; use lyra_ecs::world::World;
use lyra_reflect::Reflect;
use crate::{plugin::Plugin, game::GameStages, EventQueue}; use crate::{plugin::Plugin, game::GameStages, EventQueue};
@ -213,7 +214,7 @@ impl Action {
} }
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] #[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct LayoutId(u32); pub struct LayoutId(pub u32);
impl From<u32> for LayoutId { impl From<u32> for LayoutId {
fn from(value: u32) -> Self { fn from(value: u32) -> Self {
@ -240,7 +241,7 @@ impl Layout {
} }
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] #[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct ActionMappingId(u32); pub struct ActionMappingId(pub u32);
impl From<u32> for ActionMappingId { impl From<u32> for ActionMappingId {
fn from(value: u32) -> Self { fn from(value: u32) -> Self {
@ -264,6 +265,10 @@ impl ActionMapping {
} }
} }
pub fn builder(layout: LayoutId, id: ActionMappingId) -> ActionMappingBuilder {
ActionMappingBuilder::new(ActionMapping::new(layout, id))
}
/// Creates a binding for the action. /// Creates a binding for the action.
/// ///
/// If the action is not in this layout, this will panic! /// If the action is not in this layout, this will panic!
@ -271,7 +276,7 @@ impl ActionMapping {
/// Parameters: /// Parameters:
/// * `action` - The label corresponding to the action in this Layout. /// * `action` - The label corresponding to the action in this Layout.
/// * `bind` - The Binding to add to the Action. /// * `bind` - The Binding to add to the Action.
pub fn bind<L>(mut self, action: L, bindings: &[Binding]) -> Self pub fn bind<L>(&mut self, action: L, bindings: &[Binding]) -> &mut Self
where where
L: ActionLabel L: ActionLabel
{ {
@ -283,32 +288,48 @@ impl ActionMapping {
self self
} }
/// Creates multiple binding for the action.
///
/// If the action is not in this layout, this will panic!
///
/// Parameters:
/// * `action_label` - The label corresponding to the action in this Layout.
/// * `bindings` - The list of Bindings to add to the Action.
/* pub fn add_bindings(&mut self, action_label: String, bindings: &[Binding]) -> &mut Self {
let mut bindings = bindings.to_vec();
let action_binds = self.action_binds.entry(action_label)
.or_insert_with(Vec::new);
action_binds.append(&mut bindings);
self
} */
pub fn finish(self) -> Self { pub fn finish(self) -> Self {
self self
} }
} }
#[derive(Clone, Default)] pub struct ActionMappingBuilder {
mapping: ActionMapping,
}
impl ActionMappingBuilder {
fn new(mapping: ActionMapping) -> Self {
Self {
mapping,
}
}
pub fn bind<L>(mut self, action: L, bindings: &[Binding]) -> Self
where
L: ActionLabel
{
let mut bindings = bindings.to_vec();
let action_binds = self.mapping.action_binds.entry(action.label_hash()).or_default();
action_binds.append(&mut bindings);
self
}
pub fn finish(self) -> ActionMapping {
self.mapping
}
}
#[derive(Clone, Default, Reflect)]
pub struct ActionHandler { pub struct ActionHandler {
#[reflect(skip)] // TODO: dont just skip all these
pub actions: HashMap<u64, Action>, pub actions: HashMap<u64, Action>,
#[reflect(skip)]
pub layouts: HashMap<LayoutId, Layout>, pub layouts: HashMap<LayoutId, Layout>,
#[reflect(skip)]
pub current_layout: LayoutId, pub current_layout: LayoutId,
#[reflect(skip)]
pub current_mapping: ActionMappingId, pub current_mapping: ActionMappingId,
} }
@ -317,26 +338,31 @@ impl ActionHandler {
Self::default() Self::default()
} }
pub fn add_layout(mut self, id: LayoutId) -> Self { pub fn builder() -> ActionHandlerBuilder {
self.layouts.insert(id, Layout::new()); ActionHandlerBuilder::default()
self
} }
pub fn add_action<L>(mut self, label: L, action: Action) -> Self pub fn add_layout(&mut self, id: LayoutId) {
self.layouts.insert(id, Layout::new());
}
pub fn action<L>(&self, label: L) -> Option<&Action>
where
L: ActionLabel
{
self.actions.get(&label.label_hash())
}
pub fn add_action<L>(&mut self, label: L, action: Action)
where where
L: ActionLabel L: ActionLabel
{ {
self.actions.insert(label.label_hash(), action); self.actions.insert(label.label_hash(), action);
self
} }
pub fn add_mapping(mut self, mapping: ActionMapping) -> Self { pub fn add_mapping(&mut self, mapping: ActionMapping) {
let layout = self.layouts.get_mut(&mapping.layout).unwrap(); let layout = self.layouts.get_mut(&mapping.layout).unwrap();
layout.add_mapping(mapping); layout.add_mapping(mapping);
self
} }
/// Returns true if the action is pressed (or was just pressed). /// Returns true if the action is pressed (or was just pressed).
@ -443,6 +469,43 @@ impl ActionHandler {
} }
} }
#[derive(Default)]
pub struct ActionHandlerBuilder {
handler: ActionHandler,
}
impl ActionHandlerBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn add_layout(mut self, id: LayoutId) -> Self {
self.handler.layouts.insert(id, Layout::new());
self
}
pub fn add_action<L>(mut self, label: L, action: Action) -> Self
where
L: ActionLabel
{
self.handler.actions.insert(label.label_hash(), action);
self
}
pub fn add_mapping(mut self, mapping: ActionMapping) -> Self {
let layout = self.handler.layouts.get_mut(&mapping.layout).unwrap();
layout.add_mapping(mapping);
self
}
pub fn finish(self) -> ActionHandler {
self.handler
}
}
fn actions_system(world: &mut World) -> anyhow::Result<()> { fn actions_system(world: &mut World) -> anyhow::Result<()> {
let keys = world.try_get_resource::<InputButtons<KeyCode>>() let keys = world.try_get_resource::<InputButtons<KeyCode>>()
.map(|r| r.deref().clone()); .map(|r| r.deref().clone());

View File

@ -11,4 +11,182 @@ pub mod buttons;
pub use buttons::*; pub use buttons::*;
pub mod action; pub mod action;
pub use action::*; pub use action::*;
pub type KeyCode = winit::event::VirtualKeyCode;
/// Parses a [`KeyCode`] from a [`&str`].
///
/// There are some changes to a few keycodes. All the number keys `Key1`, `Key2`, etc., have
/// the `Key` prefix removed; so they are expected to be `1`, `2`, etc.
pub fn keycode_from_str(s: &str) -> Option<KeyCode> {
let s = s.to_lowercase();
let s = s.as_str();
match s {
"1" => Some(KeyCode::Key1),
"2" => Some(KeyCode::Key2),
"3" => Some(KeyCode::Key3),
"4" => Some(KeyCode::Key4),
"5" => Some(KeyCode::Key5),
"6" => Some(KeyCode::Key6),
"7" => Some(KeyCode::Key7),
"8" => Some(KeyCode::Key8),
"9" => Some(KeyCode::Key9),
"0" => Some(KeyCode::Key0),
"a" => Some(KeyCode::A),
"b" => Some(KeyCode::B),
"c" => Some(KeyCode::C),
"d" => Some(KeyCode::D),
"e" => Some(KeyCode::E),
"f" => Some(KeyCode::F),
"g" => Some(KeyCode::G),
"h" => Some(KeyCode::H),
"i" => Some(KeyCode::I),
"j" => Some(KeyCode::J),
"k" => Some(KeyCode::K),
"l" => Some(KeyCode::L),
"m" => Some(KeyCode::M),
"n" => Some(KeyCode::N),
"o" => Some(KeyCode::O),
"p" => Some(KeyCode::P),
"q" => Some(KeyCode::Q),
"r" => Some(KeyCode::R),
"s" => Some(KeyCode::S),
"t" => Some(KeyCode::T),
"u" => Some(KeyCode::U),
"v" => Some(KeyCode::V),
"w" => Some(KeyCode::W),
"x" => Some(KeyCode::X),
"y" => Some(KeyCode::Y),
"z" => Some(KeyCode::Z),
"escape" => Some(KeyCode::Escape),
"f1" => Some(KeyCode::F1),
"f2" => Some(KeyCode::F2),
"f3" => Some(KeyCode::F3),
"f4" => Some(KeyCode::F4),
"f5" => Some(KeyCode::F5),
"f6" => Some(KeyCode::F6),
"f7" => Some(KeyCode::F7),
"f8" => Some(KeyCode::F8),
"f9" => Some(KeyCode::F9),
"f10" => Some(KeyCode::F10),
"f11" => Some(KeyCode::F11),
"f12" => Some(KeyCode::F12),
"f13" => Some(KeyCode::F13),
"f14" => Some(KeyCode::F14),
"f15" => Some(KeyCode::F15),
"f16" => Some(KeyCode::F16),
"f17" => Some(KeyCode::F17),
"f18" => Some(KeyCode::F18),
"f19" => Some(KeyCode::F19),
"f20" => Some(KeyCode::F20),
"f21" => Some(KeyCode::F21),
"f22" => Some(KeyCode::F22),
"f23" => Some(KeyCode::F23),
"f24" => Some(KeyCode::F24),
"snapshot" => Some(KeyCode::Snapshot),
"scroll" => Some(KeyCode::Scroll),
"pause" => Some(KeyCode::Pause),
"insert" => Some(KeyCode::Insert),
"home" => Some(KeyCode::Home),
"delete" => Some(KeyCode::Delete),
"end" => Some(KeyCode::End),
"pagedown" => Some(KeyCode::PageDown),
"pageup" => Some(KeyCode::PageUp),
"left" => Some(KeyCode::Left),
"up" => Some(KeyCode::Up),
"right" => Some(KeyCode::Right),
"down" => Some(KeyCode::Down),
"back" => Some(KeyCode::Back),
"return" => Some(KeyCode::Return),
"space" => Some(KeyCode::Space),
"compose" => Some(KeyCode::Compose),
"caret" => Some(KeyCode::Caret),
"numlock" => Some(KeyCode::Numlock),
"numpad0" => Some(KeyCode::Numpad0),
"numpad1" => Some(KeyCode::Numpad1),
"numpad2" => Some(KeyCode::Numpad2),
"numpad3" => Some(KeyCode::Numpad3),
"numpad4" => Some(KeyCode::Numpad4),
"numpad5" => Some(KeyCode::Numpad5),
"numpad6" => Some(KeyCode::Numpad6),
"numpad7" => Some(KeyCode::Numpad7),
"numpad8" => Some(KeyCode::Numpad8),
"numpad9" => Some(KeyCode::Numpad9),
"numpadadd" => Some(KeyCode::NumpadAdd),
"numpaddivide" => Some(KeyCode::NumpadDivide),
"numpaddecimal" => Some(KeyCode::NumpadDecimal),
"numpadcomma" => Some(KeyCode::NumpadComma),
"numpadenter" => Some(KeyCode::NumpadEnter),
"numpadequals" => Some(KeyCode::NumpadEquals),
"numpadmultiply" => Some(KeyCode::NumpadMultiply),
"numpadsubtract" => Some(KeyCode::NumpadSubtract),
"abntc1" => Some(KeyCode::AbntC1),
"abntc2" => Some(KeyCode::AbntC2),
"apostrophe" => Some(KeyCode::Apostrophe),
"apps" => Some(KeyCode::Apps),
"asterisk" => Some(KeyCode::Asterisk),
"at" => Some(KeyCode::At),
"ax" => Some(KeyCode::Ax),
"backslash" => Some(KeyCode::Backslash),
"calculator" => Some(KeyCode::Calculator),
"capital" => Some(KeyCode::Capital),
"colon" => Some(KeyCode::Colon),
"comma" => Some(KeyCode::Comma),
"convert" => Some(KeyCode::Convert),
"equals" => Some(KeyCode::Equals),
"grave" => Some(KeyCode::Grave),
"kana" => Some(KeyCode::Kana),
"kanji" => Some(KeyCode::Kanji),
"lalt" => Some(KeyCode::LAlt),
"lbracket" => Some(KeyCode::LBracket),
"lcontrol" => Some(KeyCode::LControl),
"lshift" => Some(KeyCode::LShift),
"lwin" => Some(KeyCode::LWin),
"mail" => Some(KeyCode::Mail),
"mediaselect" => Some(KeyCode::MediaSelect),
"mediastop" => Some(KeyCode::MediaStop),
"minus" => Some(KeyCode::Minus),
"mute" => Some(KeyCode::Mute),
"mycomputer" => Some(KeyCode::MyComputer),
"navigateforward" => Some(KeyCode::NavigateForward),
"navigatebackward" => Some(KeyCode::NavigateBackward),
"nexttrack" => Some(KeyCode::NextTrack),
"noconvert" => Some(KeyCode::NoConvert),
"oem102" => Some(KeyCode::OEM102),
"period" => Some(KeyCode::Period),
"playpause" => Some(KeyCode::PlayPause),
"plus" => Some(KeyCode::Plus),
"power" => Some(KeyCode::Power),
"prevtrack" => Some(KeyCode::PrevTrack),
"ralt" => Some(KeyCode::RAlt),
"rbracket" => Some(KeyCode::RBracket),
"rcontrol" => Some(KeyCode::RControl),
"rshift" => Some(KeyCode::RShift),
"rwin" => Some(KeyCode::RWin),
"semicolon" => Some(KeyCode::Semicolon),
"slash" => Some(KeyCode::Slash),
"sleep" => Some(KeyCode::Sleep),
"stop" => Some(KeyCode::Stop),
"sysrq" => Some(KeyCode::Sysrq),
"tab" => Some(KeyCode::Tab),
"underline" => Some(KeyCode::Underline),
"unlabeled" => Some(KeyCode::Unlabeled),
"volumedown" => Some(KeyCode::VolumeDown),
"volumeup" => Some(KeyCode::VolumeUp),
"wake" => Some(KeyCode::Wake),
"webback" => Some(KeyCode::WebBack),
"webfavorites" => Some(KeyCode::WebFavorites),
"webforward" => Some(KeyCode::WebForward),
"webhome" => Some(KeyCode::WebHome),
"webrefresh" => Some(KeyCode::WebRefresh),
"websearch" => Some(KeyCode::WebSearch),
"webstop" => Some(KeyCode::WebStop),
"yen" => Some(KeyCode::Yen),
"copy" => Some(KeyCode::Copy),
"paste" => Some(KeyCode::Paste),
"cut" => Some(KeyCode::Cut),
_ => None
}
}

View File

@ -8,8 +8,6 @@ use crate::{EventQueue, plugin::Plugin, game::GameStages};
use super::{events::*, InputButtons, InputEvent}; use super::{events::*, InputButtons, InputEvent};
pub type KeyCode = winit::event::VirtualKeyCode;
#[derive(Default)] #[derive(Default)]
pub struct InputSystem; pub struct InputSystem;
@ -120,6 +118,10 @@ impl crate::ecs::system::System for InputSystem {
fn world_access(&self) -> lyra_ecs::Access { fn world_access(&self) -> lyra_ecs::Access {
lyra_ecs::Access::Write lyra_ecs::Access::Write
} }
fn execute_deferred(&mut self, _: NonNull<World>) -> anyhow::Result<()> {
Ok(())
}
} }
impl IntoSystem<()> for InputSystem { impl IntoSystem<()> for InputSystem {

View File

@ -7,7 +7,6 @@ extern crate self as lyra_engine;
pub mod game; pub mod game;
pub mod render; pub mod render;
pub mod resources; pub mod resources;
pub mod math;
pub mod input; pub mod input;
pub mod castable_any; pub mod castable_any;
pub mod plugin; pub mod plugin;
@ -26,5 +25,10 @@ pub mod scene;
pub use lyra_resource as assets; pub use lyra_resource as assets;
pub use lyra_ecs as ecs; 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; pub use plugin::DefaultPlugins;

View File

@ -1,3 +1,4 @@
use lyra_ecs::CommandQueue;
use lyra_resource::ResourceManager; use lyra_resource::ResourceManager;
use crate::EventsPlugin; use crate::EventsPlugin;
@ -98,7 +99,7 @@ pub struct ResourceManagerPlugin;
impl Plugin for ResourceManagerPlugin { impl Plugin for ResourceManagerPlugin {
fn setup(&self, game: &mut Game) { fn setup(&self, game: &mut Game) {
game.world().add_resource(ResourceManager::new()); game.world_mut().add_resource(ResourceManager::new());
} }
} }
@ -108,10 +109,22 @@ pub struct DefaultPlugins;
impl Plugin for DefaultPlugins { impl Plugin for DefaultPlugins {
fn setup(&self, game: &mut Game) { fn setup(&self, game: &mut Game) {
CommandQueuePlugin.setup(game);
EventsPlugin.setup(game); EventsPlugin.setup(game);
InputPlugin.setup(game); InputPlugin.setup(game);
ResourceManagerPlugin.setup(game); ResourceManagerPlugin.setup(game);
WindowPlugin::default().setup(game); WindowPlugin::default().setup(game);
DeltaTimePlugin.setup(game); DeltaTimePlugin.setup(game);
} }
}
/// A plugin that creates a CommandQueue, and inserts it into the world as a Resource.
/// The queue is processed at the end of every system execution in the GraphExecutor.
#[derive(Default)]
pub struct CommandQueuePlugin;
impl Plugin for CommandQueuePlugin {
fn setup(&self, game: &mut Game) {
game.world_mut().add_resource(CommandQueue::default());
}
} }

View File

@ -8,11 +8,9 @@ pub use spotlight::*;
use std::{collections::{VecDeque, HashMap}, marker::PhantomData}; use std::{collections::{VecDeque, HashMap}, marker::PhantomData};
use tracing::debug;
use std::mem; use std::mem;
use crate::{math::Transform, scene::TransformComponent}; use crate::math::Transform;
use self::directional::DirectionalLight; use self::directional::DirectionalLight;
@ -169,29 +167,29 @@ impl LightUniformBuffers {
pub fn update_lights(&mut self, queue: &wgpu::Queue, world_tick: Tick, world: &World) { pub fn update_lights(&mut self, queue: &wgpu::Queue, world_tick: Tick, world: &World) {
for (entity, point_light, transform, light_epoch, transform_epoch) for (entity, point_light, transform, light_epoch, transform_epoch)
in world.view_iter::<(Entities, &PointLight, &TransformComponent, TickOf<PointLight>, TickOf<TransformComponent>)>() { in world.view_iter::<(Entities, &PointLight, &Transform, TickOf<PointLight>, TickOf<Transform>)>() {
if !self.point_lights.has_light(entity) || light_epoch == world_tick || transform_epoch == world_tick { if !self.point_lights.has_light(entity) || light_epoch == world_tick || transform_epoch == world_tick {
let uniform = PointLightUniform::from_bundle(&point_light, &transform.transform); let uniform = PointLightUniform::from_bundle(&point_light, &transform);
self.point_lights.update_or_add(&mut self.lights_uniform.point_lights, entity, uniform); self.point_lights.update_or_add(&mut self.lights_uniform.point_lights, entity, uniform);
debug!("Updated point light"); //debug!("Updated point light");
} }
} }
for (entity, spot_light, transform, light_epoch, transform_epoch) for (entity, spot_light, transform, light_epoch, transform_epoch)
in world.view_iter::<(Entities, &SpotLight, &TransformComponent, TickOf<SpotLight>, TickOf<TransformComponent>)>() { in world.view_iter::<(Entities, &SpotLight, &Transform, TickOf<SpotLight>, TickOf<Transform>)>() {
if !self.spot_lights.has_light(entity) || light_epoch == world_tick || transform_epoch == world_tick { if !self.spot_lights.has_light(entity) || light_epoch == world_tick || transform_epoch == world_tick {
let uniform = SpotLightUniform::from_bundle(&spot_light, &transform.transform); let uniform = SpotLightUniform::from_bundle(&spot_light, &transform);
self.spot_lights.update_or_add(&mut self.lights_uniform.spot_lights, entity, uniform); self.spot_lights.update_or_add(&mut self.lights_uniform.spot_lights, entity, uniform);
//debug!("Updated spot light"); //debug!("Updated spot light");
} }
} }
if let Some((dir_light, transform)) = if let Some((dir_light, transform)) =
world.view_iter::<(&DirectionalLight, &TransformComponent)>().next() { world.view_iter::<(&DirectionalLight, &Transform)>().next() {
let uniform = DirectionalLightUniform::from_bundle(&dir_light, &transform.transform); let uniform = DirectionalLightUniform::from_bundle(&dir_light, &transform);
self.lights_uniform.directional_light = uniform; self.lights_uniform.directional_light = uniform;
} }

View File

@ -11,11 +11,11 @@ pub struct MaterialSpecular {
impl MaterialSpecular { impl MaterialSpecular {
pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc<wgpu::BindGroupLayout>, value: &lyra_resource::Specular) -> Self { pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc<wgpu::BindGroupLayout>, value: &lyra_resource::Specular) -> Self {
let tex = value.texture.as_ref().map(|t| &t.data.as_ref().unwrap().image) let tex = value.texture.as_ref().map(|t| t.data_ref())
.map(|i| RenderTexture::from_image(device, queue, bg_layout.clone(), i, None).unwrap()); .map(|i| RenderTexture::from_image(device, queue, bg_layout.clone(), &i.image, None).unwrap());
let color_tex = value.color_texture.as_ref().map(|t| &t.data.as_ref().unwrap().image) let color_tex = value.color_texture.as_ref().map(|t| t.data_ref())
.map(|i| RenderTexture::from_image(device, queue, bg_layout, i, None).unwrap()); .map(|i| RenderTexture::from_image(device, queue, bg_layout, &i.image, None).unwrap());
Self { Self {
factor: value.factor, factor: value.factor,
@ -39,8 +39,8 @@ pub struct Material {
impl Material { impl Material {
pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc<wgpu::BindGroupLayout>, value: &lyra_resource::Material) -> Self { pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc<wgpu::BindGroupLayout>, value: &lyra_resource::Material) -> Self {
let diffuse_texture = value.base_color_texture.as_ref().map(|t| &t.data.as_ref().unwrap().image) let diffuse_texture = value.base_color_texture.as_ref().map(|t| t.data_ref())
.map(|i| RenderTexture::from_image(device, queue, bg_layout.clone(), i, None).unwrap()); .map(|i| RenderTexture::from_image(device, queue, bg_layout.clone(), &i.image, None).unwrap());
let specular = value.specular.as_ref().map(|s| MaterialSpecular::from_resource(device, queue, bg_layout.clone(), s)); let specular = value.specular.as_ref().map(|s| MaterialSpecular::from_resource(device, queue, bg_layout.clone(), s));

View File

@ -16,7 +16,7 @@ use winit::window::Window;
use crate::math::Transform; use crate::math::Transform;
use crate::render::material::MaterialUniform; use crate::render::material::MaterialUniform;
use crate::render::render_buffer::BufferWrapperBuilder; use crate::render::render_buffer::BufferWrapperBuilder;
use crate::scene::{ModelComponent, TransformComponent, CameraComponent}; use crate::scene::{ModelComponent, CameraComponent};
use super::camera::{RenderCamera, CameraUniform}; use super::camera::{RenderCamera, CameraUniform};
use super::desc_buf_lay::DescVertexBufferLayout; use super::desc_buf_lay::DescVertexBufferLayout;
@ -395,13 +395,13 @@ impl Renderer for BasicRenderer {
let now_inst = Instant::now(); let now_inst = Instant::now();
for (entity, model, model_epoch, transform, transform_epoch) in main_world.view_iter::<(Entities, &ModelComponent, TickOf<ModelComponent>, &TransformComponent, TickOf<TransformComponent>)>() { for (entity, model, model_epoch, transform, transform_epoch) in main_world.view_iter::<(Entities, &ModelComponent, TickOf<ModelComponent>, &Transform, TickOf<Transform>)>() {
alive_entities.insert(entity); alive_entities.insert(entity);
let cached = match self.entity_last_transforms.get_mut(&entity) { let cached = match self.entity_last_transforms.get_mut(&entity) {
Some(last) if transform_epoch == last_epoch => { Some(last) if transform_epoch == last_epoch => {
last.from_transform = last.to_transform; last.from_transform = last.to_transform;
last.to_transform = transform.transform; last.to_transform = *transform;
last.last_updated_at = Some(last.cached_at); last.last_updated_at = Some(last.cached_at);
last.cached_at = now_inst; last.cached_at = now_inst;
@ -412,8 +412,8 @@ impl Renderer for BasicRenderer {
let cached = CachedTransform { let cached = CachedTransform {
last_updated_at: None, last_updated_at: None,
cached_at: now_inst, cached_at: now_inst,
from_transform: transform.transform, from_transform: *transform,
to_transform: transform.transform, to_transform: *transform,
}; };
self.entity_last_transforms.insert(entity, cached.clone()); self.entity_last_transforms.insert(entity, cached.clone());
cached cached
@ -430,7 +430,7 @@ impl Renderer for BasicRenderer {
let transform_val = cached.from_transform.lerp(cached.to_transform, alpha); let transform_val = cached.from_transform.lerp(cached.to_transform, alpha);
let model = model.data.as_ref().unwrap().as_ref(); let model = model.data_ref();
for mesh in model.meshes.iter() { for mesh in model.meshes.iter() {
if !self.process_mesh(entity, transform_val, mesh) && model_epoch == last_epoch { if !self.process_mesh(entity, transform_val, mesh) && model_epoch == last_epoch {
self.update_mesh_buffers(entity, mesh); self.update_mesh_buffers(entity, mesh);

View File

@ -1,7 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use image::GenericImageView; use image::GenericImageView;
use lyra_resource::{Resource, Texture}; use lyra_resource::{ResHandle, Texture};
use super::render_buffer::BindGroupPair; use super::render_buffer::BindGroupPair;
@ -153,8 +153,8 @@ impl RenderTexture {
}) })
} }
pub fn update_texture(&mut self, _device: &wgpu::Device, queue: &wgpu::Queue, texture: &Arc<Resource<Texture>>) { pub fn update_texture(&mut self, _device: &wgpu::Device, queue: &wgpu::Queue, texture: &Arc<ResHandle<Texture>>) {
let texture = &texture.data.as_ref().unwrap().image; let texture = &texture.data_ref().image;
let rgba = texture.to_rgba8(); let rgba = texture.to_rgba8();
let dimensions = texture.dimensions(); let dimensions = texture.dimensions();
let size = wgpu::Extent3d { let size = wgpu::Extent3d {

View File

@ -373,7 +373,7 @@ impl Plugin for WindowPlugin {
fn setup(&self, game: &mut crate::game::Game) { fn setup(&self, game: &mut crate::game::Game) {
let window_options = WindowOptions::default(); let window_options = WindowOptions::default();
game.world().add_resource(Ct::new(window_options)); game.world_mut().add_resource(Ct::new(window_options));
game.with_system("window_updater", window_updater_system, &[]); game.with_system("window_updater", window_updater_system, &[]);
} }
} }

View File

@ -4,9 +4,6 @@ pub use mesh::*;
pub mod model; pub mod model;
pub use model::*; pub use model::*;
pub mod transform;
pub use transform::*;
pub mod camera; pub mod camera;
pub use camera::*; pub use camera::*;

View File

@ -1,10 +1,11 @@
use lyra_ecs::Component; use lyra_ecs::Component;
use lyra_reflect::Reflect;
use lyra_resource::ResHandle; use lyra_resource::ResHandle;
use crate::assets::Model; use crate::assets::Model;
#[derive(Clone, Component)] #[derive(Clone, Component, Reflect)]
pub struct ModelComponent(pub ResHandle<Model>); pub struct ModelComponent(#[reflect(skip)] pub ResHandle<Model>);
impl From<ResHandle<Model>> for ModelComponent { impl From<ResHandle<Model>> for ModelComponent {
fn from(value: ResHandle<Model>) -> Self { fn from(value: ResHandle<Model>) -> Self {

View File

@ -1,26 +0,0 @@
use lyra_ecs::Component;
use crate::math::Transform;
#[derive(Clone, Component, Default)]
pub struct TransformComponent {
pub transform: Transform,
}
impl From<Transform> for TransformComponent {
fn from(transform: Transform) -> Self {
Self {
transform
}
}
}
impl TransformComponent {
pub fn new() -> Self {
Self::default()
}
pub fn from_transform(transform: Transform) -> Self {
Self::from(transform)
}
}

View File

@ -9,7 +9,9 @@ pub enum StagedExecutorError {
#[error("[stage={0}] could not find a system's dependency named `{1}`")] #[error("[stage={0}] could not find a system's dependency named `{1}`")]
MissingSystem(String, String), MissingSystem(String, String),
#[error("[stage={0}] system `{1}` returned with an error: `{2}`")] #[error("[stage={0}] system `{1}` returned with an error: `{2}`")]
SystemError(String, String, anyhow::Error) SystemError(String, String, anyhow::Error),
#[error("[stage={0}] a command returned with an error: `{1}`")]
CommandError(String, anyhow::Error),
} }
impl StagedExecutorError { impl StagedExecutorError {
@ -17,6 +19,7 @@ impl StagedExecutorError {
match value { match value {
GraphExecutorError::MissingSystem(s) => Self::MissingSystem(stage, s), GraphExecutorError::MissingSystem(s) => Self::MissingSystem(stage, s),
GraphExecutorError::SystemError(s, e) => Self::SystemError(stage, s, e), GraphExecutorError::SystemError(s, e) => Self::SystemError(stage, s, e),
GraphExecutorError::Command(e) => Self::CommandError(stage, e)
} }
} }
} }

9
lyra-math/Cargo.toml Normal file
View File

@ -0,0 +1,9 @@
[package]
name = "lyra-math"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
glam = { version = "0.24.0" }

0
lyra-game/src/math/mod.rs → lyra-math/src/lib.rs Executable file → Normal file
View File

View File

@ -1,9 +1,9 @@
use glam::{Vec3, Mat4, Quat}; use glam::{Vec3, Mat4, Quat};
use crate::math::angle::Angle; use super::Angle;
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone, PartialEq)]
pub struct Transform { pub struct Transform {
pub translation: Vec3, pub translation: Vec3,
pub rotation: Quat, pub rotation: Quat,
@ -85,12 +85,18 @@ impl Transform {
pub fn rotate_z(&mut self, angle: Angle) { pub fn rotate_z(&mut self, angle: Angle) {
self.rotate(Quat::from_rotation_z(angle.to_radians())) self.rotate(Quat::from_rotation_z(angle.to_radians()))
} }
pub fn translate(&mut self, x: f32, y: f32, z: f32) {
let trans = &mut self.translation;
trans.x += x;
trans.y += y;
trans.z += z;
}
/// Performs a linear interpolation between `self` and `rhs` based on the value `alpha`. /// Performs a linear interpolation between `self` and `rhs` based on the value `alpha`.
/// ///
/// When `alpha` is `0.0`, the result will be equal to `self`. When `alpha` is `1.0`, the result /// When `alpha` is `0.0`, the result will be equal to `self`. When `alpha` is `1.0`, the result
/// will be equal to `rhs`. When `alpha` is outside of range `[0, 1]`, the result is linearly /// will be equal to `rhs`.
/// extrapolated.
pub fn lerp(&self, rhs: Transform, alpha: f32) -> Self { pub fn lerp(&self, rhs: Transform, alpha: f32) -> Self {
if alpha.is_finite() { if alpha.is_finite() {
@ -104,4 +110,18 @@ impl Transform {
*self *self
} }
} }
}
/// Adds a transform to another one
///
/// The Translations of each transform is added and rotation and scale is multiplied.
impl std::ops::Add for Transform {
type Output = Transform;
fn add(mut self, rhs: Self) -> Self::Output {
self.translation += rhs.translation;
self.rotation *= rhs.rotation;
self.scale *= rhs.scale;
self
}
} }

View File

@ -5,6 +5,10 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
math = ["dep:lyra-math"]
[dependencies] [dependencies]
lyra-reflect-derive = { path = "lyra-reflect-derive" } lyra-reflect-derive = { path = "lyra-reflect-derive" }
lyra-ecs = { path = "../lyra-ecs" } lyra-ecs = { path = "../lyra-ecs" }
lyra-math = { path = "../lyra-math", optional = true }

View File

@ -32,7 +32,7 @@ impl From<&Variant> for VariantType {
/// Generates the following different outputs: /// Generates the following different outputs:
/// ///
/// ```rust /// ```compile_fail
/// // for struct variants /// // for struct variants
/// TestEnum::Error { msg, code } /// TestEnum::Error { msg, code }
/// ///
@ -98,7 +98,7 @@ fn gen_variant_if(enum_id: &proc_macro2::Ident, variant: &Variant, if_body: proc
/// Generates the following: /// Generates the following:
/// ///
/// ```rust /// ```compile_fail
/// /// generated one field here /// /// generated one field here
/// if name == "msg" { /// if name == "msg" {
/// return Some(msg); /// return Some(msg);
@ -113,7 +113,7 @@ fn gen_variant_if(enum_id: &proc_macro2::Ident, variant: &Variant, if_body: proc
fn gen_if_field_names(variant: &Variant) -> proc_macro2::TokenStream { fn gen_if_field_names(variant: &Variant) -> proc_macro2::TokenStream {
let field_ifs = variant.fields.iter().map(|field| { let field_ifs = variant.fields.iter().map(|field| {
let id = field.ident.as_ref().unwrap(); let id = field.ident.as_ref().unwrap();
let id_str = id.span().source_text().unwrap(); let id_str = id.to_string();
quote! { quote! {
if name == #id_str { if name == #id_str {
@ -129,7 +129,7 @@ fn gen_if_field_names(variant: &Variant) -> proc_macro2::TokenStream {
/// Generates the following rust code: /// Generates the following rust code:
/// ///
/// ```rust /// ```compile_fail
/// match name { /// match name {
/// "msg" | "code" => true, /// "msg" | "code" => true,
/// _ => false, /// _ => false,
@ -140,7 +140,7 @@ fn gen_match_names(variant: &Variant) -> proc_macro2::TokenStream {
let field_name_strs = variant.fields.iter().map(|field| { let field_name_strs = variant.fields.iter().map(|field| {
let id = field.ident.as_ref() let id = field.ident.as_ref()
.expect("Could not find identifier for enum field!"); .expect("Could not find identifier for enum field!");
id.span().source_text().unwrap() id.to_string()
}); });
quote! { quote! {
@ -153,7 +153,7 @@ fn gen_match_names(variant: &Variant) -> proc_macro2::TokenStream {
/// Generates the following: /// Generates the following:
/// ///
/// ```rust /// ```compile_fail
/// /// generated one field here /// /// generated one field here
/// if idx == 0 { /// if idx == 0 {
/// return Some(a); /// return Some(a);
@ -190,7 +190,7 @@ fn gen_if_field_indices(variant: &Variant) -> proc_macro2::TokenStream {
/// Generates the following: /// Generates the following:
/// ///
/// ```rust /// ```compile_fail
/// /// generated one field here /// /// generated one field here
/// if idx == 0 { /// if idx == 0 {
/// return Some("a"); /// return Some("a");
@ -226,7 +226,7 @@ fn gen_if_field_indices_names(variant: &Variant) -> proc_macro2::TokenStream {
} }
/// Generates the following: /// Generates the following:
/// ```rust /// ```compile_fail
/// /// when `by_index` is false: /// /// when `by_index` is false:
/// ///
/// if let TestEnum::Error{ msg, code} = self { /// if let TestEnum::Error{ msg, code} = self {
@ -292,7 +292,6 @@ fn gen_enum_if_stmts(enum_id: &proc_macro2::Ident, data: &DataEnum, by_index: bo
_ => quote! { }, _ => quote! { },
} }
}); });
println!("====");
quote! { quote! {
#( #struct_vars )* #( #struct_vars )*
@ -301,7 +300,7 @@ fn gen_enum_if_stmts(enum_id: &proc_macro2::Ident, data: &DataEnum, by_index: bo
/// Generates the following rust code: /// Generates the following rust code:
/// ///
/// ```rust /// ```compile_fail
/// if let TestEnum::Error { msg, code } = self { /// if let TestEnum::Error { msg, code } = self {
/// return match name { /// return match name {
/// // expands for continuing struct fields /// // expands for continuing struct fields
@ -332,7 +331,7 @@ fn gen_enum_has_field(enum_id: &proc_macro2::Ident, data: &DataEnum) -> proc_mac
/// Generates the following code: /// Generates the following code:
/// ///
/// ```rust /// ```compile_fail
/// match self { /// match self {
/// TestEnum::Start => 0, /// TestEnum::Start => 0,
/// TestEnum::Middle(a, b) => 2, /// TestEnum::Middle(a, b) => 2,
@ -359,7 +358,7 @@ fn gen_enum_fields_len(enum_id: &proc_macro2::Ident, data: &DataEnum) -> proc_ma
/// Generates the following code: /// Generates the following code:
/// ///
/// ```rust /// ```compile_fail
/// if let TestEnum::Error { msg, code } = self { /// if let TestEnum::Error { msg, code } = self {
/// if idx == 0 { /// if idx == 0 {
/// return Some("msg"); /// return Some("msg");
@ -390,7 +389,7 @@ fn gen_enum_field_name_at(enum_id: &proc_macro2::Ident, data: &DataEnum) -> proc
} }
/// Generates the following code: /// Generates the following code:
/// ```rust /// ```compile_fail
/// match self { /// match self {
/// TestEnum::Start => 0, /// TestEnum::Start => 0,
/// TestEnum::Middle(a, b) => 1, /// TestEnum::Middle(a, b) => 1,
@ -428,7 +427,7 @@ fn gen_enum_variant_name(enum_id: &proc_macro2::Ident, data: &DataEnum, gen_inde
/// Generates a match statement that returns the types of the variants of the enum. /// Generates a match statement that returns the types of the variants of the enum.
/// ///
/// Example: /// Example:
/// ```rust /// ```compile_fail
/// match self { /// match self {
/// TestEnum::Start => EnumType::Unit, /// TestEnum::Start => EnumType::Unit,
/// TestEnum::Middle(a, b) => EnumType::Tuple, /// TestEnum::Middle(a, b) => EnumType::Tuple,
@ -458,41 +457,35 @@ fn gen_enum_variant_type(enum_id: &proc_macro2::Ident, data: &DataEnum) -> proc_
/// Create a reflect implementation for an enum /// Create a reflect implementation for an enum
pub fn derive_reflect_enum(input: &DeriveInput, data_enum: &DataEnum) -> proc_macro2::TokenStream { pub fn derive_reflect_enum(input: &DeriveInput, data_enum: &DataEnum) -> proc_macro2::TokenStream {
let type_path = &input.ident; let input_ident = &input.ident;
let name = type_path.span().source_text().unwrap(); let ident_str = input.ident.to_string();
//println!("Got type path: {}", type_path);
let variant_count = data_enum.variants.len(); let variant_count = data_enum.variants.len();
/* let mut variants_iter = data_enum.variants.iter(); let field_ifs = gen_enum_if_stmts(input_ident, data_enum, false);
let field_mut_ifs = gen_enum_if_stmts(input_ident, data_enum, false);
let variant = variants_iter.next().unwrap(); let field_at_ifs = gen_enum_if_stmts(input_ident, data_enum, true);
let variant_name = &variant.ident; */ let field_at_mut_ifs = gen_enum_if_stmts(input_ident, data_enum, true);
let field_ifs = gen_enum_if_stmts(type_path, data_enum, false); let has_field = gen_enum_has_field(input_ident, data_enum);
let field_mut_ifs = gen_enum_if_stmts(type_path, data_enum, false); let field_len = gen_enum_fields_len(input_ident, data_enum);
let field_name_at = gen_enum_field_name_at(input_ident, data_enum);
let field_at_ifs = gen_enum_if_stmts(type_path, data_enum, true); let variant_name_match = gen_enum_variant_name(input_ident, data_enum, false);
let field_at_mut_ifs = gen_enum_if_stmts(type_path, data_enum, true); let variant_idx_match = gen_enum_variant_name(input_ident, data_enum, true);
let variant_type = gen_enum_variant_type(input_ident, data_enum);
let has_field = gen_enum_has_field(type_path, data_enum);
let field_len = gen_enum_fields_len(type_path, data_enum);
let field_name_at = gen_enum_field_name_at(type_path, data_enum);
let variant_name_match = gen_enum_variant_name(type_path, data_enum, false);
let variant_idx_match = gen_enum_variant_name(type_path, data_enum, true);
let variant_type = gen_enum_variant_type(type_path, data_enum);
let generics = add_trait_bounds(input.generics.clone(), vec![parse_quote!(Reflect), parse_quote!(Clone)]); let generics = add_trait_bounds(input.generics.clone(), vec![parse_quote!(Reflect), parse_quote!(Clone)]);
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
return proc_macro2::TokenStream::from(quote! { return proc_macro2::TokenStream::from(quote! {
impl #impl_generics lyra_engine::reflect::Reflect for #type_path #ty_generics #where_clause { impl #impl_generics lyra_engine::reflect::Reflect for #input_ident #ty_generics #where_clause {
fn name(&self) -> ::std::string::String { fn name(&self) -> ::std::string::String {
#name.to_string() #ident_str.to_string()
} }
fn type_id(&self) -> std::any::TypeId { fn type_id(&self) -> std::any::TypeId {
std::any::TypeId::of::<#type_path #ty_generics>() std::any::TypeId::of::<#input_ident #ty_generics>()
} }
fn as_any(&self) -> &dyn std::any::Any { fn as_any(&self) -> &dyn std::any::Any {
@ -530,7 +523,7 @@ pub fn derive_reflect_enum(input: &DeriveInput, data_enum: &DataEnum) -> proc_ma
} }
} }
impl #impl_generics lyra_engine::reflect::Enum for #type_path #ty_generics #where_clause { impl #impl_generics lyra_engine::reflect::Enum for #input_ident #ty_generics #where_clause {
fn field(&self, name: &str) -> Option<&dyn lyra_engine::reflect::Reflect> { fn field(&self, name: &str) -> Option<&dyn lyra_engine::reflect::Reflect> {
let name = name.to_lowercase(); let name = name.to_lowercase();
let name = name.as_str(); let name = name.as_str();

View File

@ -1,4 +1,5 @@
use proc_macro::TokenStream; use proc_macro::TokenStream;
use proc_macro2::Ident;
use quote::{quote, ToTokens}; use quote::{quote, ToTokens};
use syn::{Generics, Path, Attribute, GenericParam, parse_macro_input, DeriveInput, TypeParamBound}; use syn::{Generics, Path, Attribute, GenericParam, parse_macro_input, DeriveInput, TypeParamBound};
@ -10,8 +11,55 @@ mod struct_derive;
#[allow(unused_imports)] #[allow(unused_imports)]
use struct_derive::*; use struct_derive::*;
mod struct_macro;
/* #[proc_macro_attribute(attributes(reflect))]
pub fn reflect(_attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream {
item
} */
pub(crate) struct FieldAttributes(Vec<ReflectAttribute>);
impl FieldAttributes {
/// Searches for a usage of the 'reflect' attribute and returns a list of the
/// things used in the usage.
pub fn from_vec(v: &Vec<syn::Attribute>) -> Result<Self, syn::Error> {
let s: Result<Vec<ReflectAttribute>, _> = v.iter().filter_map(|att| match &att.meta {
syn::Meta::Path(_) => None,
syn::Meta::List(l) => {
Some(syn::parse::<ReflectAttribute>(l.tokens.clone().into()))
}
syn::Meta::NameValue(_) => None
}).collect();
Ok(Self(s?))
}
pub fn has_skip(&self) -> bool {
self.0.iter().any(|a| *a == ReflectAttribute::Skip)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ReflectAttribute {
Skip
}
impl syn::parse::Parse for ReflectAttribute {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let ident: Ident = input.parse()?;
let ident_str = ident.to_string().to_lowercase();
match ident_str.as_str() {
"skip" => Ok(Self::Skip),
_ => Err(syn::Error::new(ident.span(), "Unknown reflect attribute flag"))
}
}
}
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) struct ReflectDef { pub(crate) struct ReflectDef {
//pub ident: Ident,
pub type_path: Path, pub type_path: Path,
pub generics: Generics, pub generics: Generics,
pub attributes: Vec<Attribute> pub attributes: Vec<Attribute>
@ -21,10 +69,12 @@ impl syn::parse::Parse for ReflectDef {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> { fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let attributes = input.call(Attribute::parse_outer)?; let attributes = input.call(Attribute::parse_outer)?;
let type_path = Path::parse_mod_style(input)?; let type_path = Path::parse_mod_style(input)?;
//let ident = type_path. //type_path.require_ident()?;
let mut generics = input.parse::<Generics>()?; let mut generics = input.parse::<Generics>()?;
generics.where_clause = input.parse()?; generics.where_clause = input.parse()?;
Ok(Self { Ok(Self {
//ident: ident.clone(),
type_path, type_path,
generics, generics,
attributes, attributes,
@ -32,7 +82,7 @@ impl syn::parse::Parse for ReflectDef {
} }
} }
#[proc_macro_derive(Reflect)] #[proc_macro_derive(Reflect, attributes(reflect))]
pub fn derive_reflect(input: proc_macro::TokenStream) -> proc_macro::TokenStream { pub fn derive_reflect(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput); let input = parse_macro_input!(input as DeriveInput);
@ -55,18 +105,22 @@ pub fn derive_reflect(input: proc_macro::TokenStream) -> proc_macro::TokenStream
#[proc_macro] #[proc_macro]
pub fn impl_reflect_trait_value(input: TokenStream) -> TokenStream { pub fn impl_reflect_trait_value(input: TokenStream) -> TokenStream {
let reflect = syn::parse_macro_input!(input as ReflectDef); let reflect = syn::parse_macro_input!(input as ReflectDef);
let type_path = reflect.type_path.clone().into_token_stream();
let name_id = &reflect.type_path.segments.last().unwrap().ident; let type_path = reflect.type_path;
let name = name_id.span().source_text().unwrap(); // convert the type path to a string. This would not create a leading separator
let type_path_str = {
let idents: Vec<String> = type_path.segments.iter()
.map(|segment| segment.ident.to_string())
.collect();
idents.join("::")
};
let (impl_generics, ty_generics, where_clause) = reflect.generics.split_for_impl(); let (impl_generics, ty_generics, where_clause) = reflect.generics.split_for_impl();
TokenStream::from(quote! { TokenStream::from(quote! {
impl #impl_generics lyra_engine::reflect::Reflect for #type_path #ty_generics #where_clause { impl #impl_generics lyra_engine::reflect::Reflect for #type_path #ty_generics #where_clause {
fn name(&self) -> ::std::string::String { fn name(&self) -> ::std::string::String {
#name.to_string() #type_path_str.to_string()
} }
fn type_id(&self) -> std::any::TypeId { fn type_id(&self) -> std::any::TypeId {
@ -119,4 +173,9 @@ pub(crate) fn add_trait_bounds(mut generics: Generics, add_bounds: Vec<TypeParam
} }
} }
generics generics
}
#[proc_macro]
pub fn impl_reflect_simple_struct(input: TokenStream) -> TokenStream {
struct_macro::impl_reflect_simple_struct(input)
} }

View File

@ -1,13 +1,34 @@
use quote::{quote, ToTokens}; use quote::{quote, ToTokens};
use syn::{DeriveInput, parse_quote, DataStruct}; use syn::{DeriveInput, parse_quote, DataStruct};
use crate::add_trait_bounds; use crate::{add_trait_bounds, FieldAttributes};
#[derive(Debug, PartialEq, Eq)]
enum StructType {
Unit,
Named,
Tuple,
}
impl StructType {
pub fn new(data: &DataStruct) -> Self {
if let Some(first) = data.fields.iter().next() {
if first.ident.is_some() {
Self::Named
} else {
Self::Tuple
}
} else {
Self::Unit
}
}
}
/// Generates code that matches a string with a struct's field name, and returns an Option that /// Generates code that matches a string with a struct's field name, and returns an Option that
/// contains a borrow (mutable borrow if `is_mut` is true) to the matching struct field. /// contains a borrow (mutable borrow if `is_mut` is true) to the matching struct field.
/// ///
/// Example: /// Example:
/// ```rust /// ```compile_fail
/// // when `is_mut` = false /// // when `is_mut` = false
/// match name { /// match name {
/// "x" => Some(&self.x), /// "x" => Some(&self.x),
@ -22,35 +43,49 @@ use crate::add_trait_bounds;
/// _ => None, /// _ => None,
/// } /// }
/// ``` /// ```
///
/// If the struct is a unit or tuple struct, None will always be returned
fn gen_struct_field_match(data: &DataStruct, is_mut: bool) -> proc_macro2::TokenStream { fn gen_struct_field_match(data: &DataStruct, is_mut: bool) -> proc_macro2::TokenStream {
let mut_tkn = if is_mut { let ty = StructType::new(data);
quote! {
mut
}
} else { quote!{} };
let field_arms = data.fields.iter().map(|field| {
let field_ident = field.ident.as_ref().expect("Struct is missing field ident!");
let field_name_str = field_ident.to_string();
quote! {
#field_name_str => Some(&#mut_tkn self.#field_ident)
}
});
quote! { if ty == StructType::Named {
match name { let mut_tkn = if is_mut {
#(#field_arms,)* quote! {
_ => None, mut
}
} else { quote!{} };
let field_arms = data.fields.iter().map(|field| {
let field_ident = field.ident.as_ref().expect("Struct is missing field ident!");
let field_name_str = field_ident.to_string();
let attrs = FieldAttributes::from_vec(&field.attrs)
.expect("Failure to parse reflect attributes");
if attrs.has_skip() {
quote! {
#field_name_str => None
}
} else {
quote! {
#field_name_str => Some(&#mut_tkn self.#field_ident)
}
}
});
quote! {
match name {
#(#field_arms,)*
_ => None,
}
} }
} } else { quote!(None) }
} }
/// Generates code that matches a string with a struct's field name, and sets that field value /// Generates code that matches a string with a struct's field name, and sets that field value
/// with the provided `val`. /// with the provided `val`.
/// ///
/// Example: /// Example:
/// ```rust /// ```compile_fail
/// match name { /// match name {
/// "x" => self.x = any_val.downcast_ref::<f32>() /// "x" => self.x = any_val.downcast_ref::<f32>()
/// .expect(&format!("Cannot set struct's field of {} type to the provided type of {}", "f32", val.name())) /// .expect(&format!("Cannot set struct's field of {} type to the provided type of {}", "f32", val.name()))
@ -64,34 +99,48 @@ fn gen_struct_field_match(data: &DataStruct, is_mut: bool) -> proc_macro2::Token
/// } /// }
/// ``` /// ```
fn gen_struct_set_field_match(data: &DataStruct) -> proc_macro2::TokenStream { fn gen_struct_set_field_match(data: &DataStruct) -> proc_macro2::TokenStream {
let field_arms = data.fields.iter().map(|field| { let ty = StructType::new(data);
let field_ident = field.ident.as_ref().expect("Struct is missing field ident!");
let field_name_str = field_ident.to_string();
let field_ty = &field.ty;
quote! {
#field_name_str => self.#field_ident = any_val.downcast_ref::<#field_ty>()
.expect(&format!("Cannot set struct's field of {} type to the provided type of {}", #field_name_str, val.name()))
.clone() //Some(&#mut_tkn self.#field_ident)
}
});
quote! { if ty == StructType::Named {
let any_val = val.as_any(); let field_arms = data.fields.iter().map(|field| {
match name { let field_ident = field.ident.as_ref().expect("Struct is missing field ident!");
#(#field_arms,)* let field_name_str = field_ident.to_string();
_ => { let field_ty = &field.ty;
return false;
}, let attrs = FieldAttributes::from_vec(&field.attrs)
.expect("Failure to parse reflect attributes");
if attrs.has_skip() {
quote! {
#field_name_str => {}
}
} else {
quote! {
#field_name_str => self.#field_ident = any_val.downcast_ref::<#field_ty>()
.expect(&format!("Cannot set struct's field of {} type to the provided type of {}", #field_name_str, val.name()))
.clone() //Some(&#mut_tkn self.#field_ident)
}
}
});
quote! {
let any_val = val.as_any();
match name {
#(#field_arms,)*
_ => {
return false;
},
}
true
} }
} } else { quote!(return false;) }
} }
/// Generates code that matches a string with a struct's field name, and returns a string that is /// Generates code that matches a string with a struct's field name, and returns a string that is
/// the type of the field. /// the type of the field.
/// ///
/// Example: /// Example:
/// ```rust /// ```compile_fail
/// match name { /// match name {
/// "x" => Some("f32"), /// "x" => Some("f32"),
/// "y" => Some("f32"), /// "y" => Some("f32"),
@ -99,32 +148,36 @@ fn gen_struct_set_field_match(data: &DataStruct) -> proc_macro2::TokenStream {
/// } /// }
/// ``` /// ```
fn gen_struct_field_name_match(data: &DataStruct) -> proc_macro2::TokenStream { fn gen_struct_field_name_match(data: &DataStruct) -> proc_macro2::TokenStream {
let field_arms = data.fields.iter().map(|field| { let ty = StructType::new(data);
let field_ident = field.ident.as_ref().expect("Struct is missing field ident!");
let field_name_str = field_ident.to_string(); if ty == StructType::Named {
let field_arms = data.fields.iter().map(|field| {
let field_ident = field.ident.as_ref().expect("Struct is missing field ident!");
let field_name_str = field_ident.to_string();
let mut field_ty_stream = proc_macro2::TokenStream::new();
field.ty.to_tokens(&mut field_ty_stream);
let s = field_ty_stream.to_string();
quote! {
#field_name_str => Some(#s)
}
});
let mut field_ty_stream = proc_macro2::TokenStream::new();
field.ty.to_tokens(&mut field_ty_stream);
let s = field_ty_stream.to_string();
quote! { quote! {
#field_name_str => Some(#s) match name {
#(#field_arms,)*
_ => None,
}
} }
}); } else { quote!(None) }
quote! {
match name {
#(#field_arms,)*
_ => None,
}
}
} }
/// Generates code that matches a string with a struct's field name, and sets that field value /// Generates code that matches a string with a struct's field name, and sets that field value
/// with the provided `val`. /// with the provided `val`.
/// ///
/// Example: /// Example:
/// ```rust /// ```compile_fail
/// match name { /// match name {
/// 0 => self.x = any_val.downcast_ref::<f32>() /// 0 => self.x = any_val.downcast_ref::<f32>()
/// .expect(&format!("Cannot set struct's field of {} type to the provided type of {}", "f32", val.name())) /// .expect(&format!("Cannot set struct's field of {} type to the provided type of {}", "f32", val.name()))
@ -138,15 +191,38 @@ fn gen_struct_field_name_match(data: &DataStruct) -> proc_macro2::TokenStream {
/// } /// }
/// ``` /// ```
fn gen_struct_set_field_match_idx(data: &DataStruct) -> proc_macro2::TokenStream { fn gen_struct_set_field_match_idx(data: &DataStruct) -> proc_macro2::TokenStream {
let ty = StructType::new(data);
if ty == StructType::Unit {
return quote!( return false; );
}
let field_arms = data.fields.iter().enumerate().map(|(idx, field)| { let field_arms = data.fields.iter().enumerate().map(|(idx, field)| {
let field_ident = field.ident.as_ref().expect("Struct is missing field ident!");
let field_name_str = field_ident.to_string();
let field_ty = &field.ty; let field_ty = &field.ty;
quote! { let attrs = FieldAttributes::from_vec(&field.attrs)
#idx => self.#field_ident = any_val.downcast_ref::<#field_ty>() .expect("Failure to parse reflect attributes");
.expect(&format!("Cannot set struct's field of {} type to the provided type of {}", #field_name_str, val.name())) if attrs.has_skip() {
.clone() return quote! {
#idx => {}
};
}
if let Some(field_ident) = &field.ident {
let field_name_str = field_ident.to_string();
quote! {
#idx => self.#field_ident = any_val.downcast_ref::<#field_ty>()
.expect(&format!("Cannot set struct's field of {} to the provided type of {}", #field_name_str, val.name()))
.clone()
}
} else {
let sidx = syn::Index::from(idx);
quote! {
#idx => self.#sidx = any_val.downcast_ref::<#field_ty>()
.expect(&format!("Cannot set struct's field at {} to the provided type of {}", #idx, val.name()))
.clone()
}
} }
}); });
@ -158,6 +234,8 @@ fn gen_struct_set_field_match_idx(data: &DataStruct) -> proc_macro2::TokenStream
return false; return false;
}, },
} }
true
} }
} }
@ -165,7 +243,7 @@ fn gen_struct_set_field_match_idx(data: &DataStruct) -> proc_macro2::TokenStream
/// type of the field. /// type of the field.
/// ///
/// Example: /// Example:
/// ```rust /// ```compile_fail
/// match name { /// match name {
/// 0 => Some("f32"), /// 0 => Some("f32"),
/// 1 => Some("f32"), /// 1 => Some("f32"),
@ -196,7 +274,7 @@ fn gen_struct_field_name_match_idx(data: &DataStruct) -> proc_macro2::TokenStrea
/// to the matching struct field. /// to the matching struct field.
/// ///
/// Example: /// Example:
/// ```rust /// ```compile_fail
/// // when `is_mut` = false /// // when `is_mut` = false
/// match idx { /// match idx {
/// 0 => Some(&self.x), /// 0 => Some(&self.x),
@ -212,6 +290,12 @@ fn gen_struct_field_name_match_idx(data: &DataStruct) -> proc_macro2::TokenStrea
/// } /// }
/// ``` /// ```
fn gen_struct_field_match_idx(data: &DataStruct, is_mut: bool) -> proc_macro2::TokenStream { fn gen_struct_field_match_idx(data: &DataStruct, is_mut: bool) -> proc_macro2::TokenStream {
let ty = StructType::new(data);
if ty == StructType::Unit {
return quote!(None);
}
let mut_tkn = if is_mut { let mut_tkn = if is_mut {
quote! { quote! {
mut mut
@ -219,10 +303,23 @@ fn gen_struct_field_match_idx(data: &DataStruct, is_mut: bool) -> proc_macro2::T
} else { quote!{} }; } else { quote!{} };
let field_arms = data.fields.iter().enumerate().map(|(idx, field)| { let field_arms = data.fields.iter().enumerate().map(|(idx, field)| {
let field_ident = field.ident.as_ref().expect("Struct is missing field ident!"); let attrs = FieldAttributes::from_vec(&field.attrs)
.expect("Failure to parse reflect attributes");
if attrs.has_skip() {
return quote! {
#idx => None
};
}
quote! { if let Some(field_ident) = &field.ident {
#idx => Some(&#mut_tkn self.#field_ident) quote! {
#idx => Some(&#mut_tkn self.#field_ident)
}
} else {
let sidx = syn::Index::from(idx);
quote! {
#idx => Some(&#mut_tkn self.#sidx)
}
} }
}); });
@ -238,7 +335,7 @@ fn gen_struct_field_match_idx(data: &DataStruct, is_mut: bool) -> proc_macro2::T
/// and returns an Option that contains the name of the field. /// and returns an Option that contains the name of the field.
/// ///
/// Example: /// Example:
/// ```rust /// ```compile_fail
/// match idx { /// match idx {
/// 0 => Some("x"), /// 0 => Some("x"),
/// 1 => Some("y"), /// 1 => Some("y"),
@ -246,27 +343,34 @@ fn gen_struct_field_match_idx(data: &DataStruct, is_mut: bool) -> proc_macro2::T
/// } /// }
/// ``` /// ```
fn gen_struct_field_name_idx(data: &DataStruct) -> proc_macro2::TokenStream { fn gen_struct_field_name_idx(data: &DataStruct) -> proc_macro2::TokenStream {
let field_arms = data.fields.iter().enumerate().map(|(idx, field)| { let ty = StructType::new(data);
let field_ident = field.ident.as_ref().expect("Struct is missing field ident!");
let field_name_str = field_ident.to_string();
quote! {
#idx => Some(#field_name_str)
}
});
quote! { if ty == StructType::Named {
match idx { let field_arms = data.fields.iter().enumerate().map(|(idx, field)| {
#(#field_arms,)* let field_ident = field.ident.as_ref().expect("Struct is missing field ident!");
_ => None, let field_name_str = field_ident.to_string();
quote! {
#idx => Some(#field_name_str)
}
});
quote! {
match idx {
#(#field_arms,)*
_ => None,
}
} }
} else {
quote!(None)
} }
} }
/// Generates a token stream that implements Reflect and Struct for the provided struct /// Generates a token stream that implements Reflect and Struct for the provided struct
pub fn derive_reflect_struct(input: &DeriveInput, data_struct: &DataStruct) -> proc_macro2::TokenStream { pub fn derive_reflect_struct(input: &DeriveInput, data_struct: &DataStruct) -> proc_macro2::TokenStream {
let type_path = &input.ident; let type_path = &input.ident;
let name = type_path.span().source_text().unwrap(); let name = type_path.to_string();
//let name = type_path.span().source_text().unwrap();
let field_len = data_struct.fields.len(); let field_len = data_struct.fields.len();
let get_field_match = gen_struct_field_match(data_struct, false); let get_field_match = gen_struct_field_match(data_struct, false);
@ -360,12 +464,10 @@ pub fn derive_reflect_struct(input: &DeriveInput, data_struct: &DataStruct) -> p
fn set_field(&mut self, name: &str, val: &dyn lyra_engine::reflect::Reflect) -> bool { fn set_field(&mut self, name: &str, val: &dyn lyra_engine::reflect::Reflect) -> bool {
#set_field_named #set_field_named
true
} }
fn set_field_at(&mut self, idx: usize, val: &dyn lyra_engine::reflect::Reflect) -> bool { fn set_field_at(&mut self, idx: usize, val: &dyn lyra_engine::reflect::Reflect) -> bool {
#set_field_idx #set_field_idx
true
} }
fn field_type(&self, name: &str) -> Option<&'static str> { fn field_type(&self, name: &str) -> Option<&'static str> {

View File

@ -0,0 +1,270 @@
use quote::quote;
use syn::{Token, parenthesized, punctuated::Punctuated};
struct Field {
name: syn::Ident,
_eq: Token![=],
ty: syn::Path,
}
impl syn::parse::Parse for Field {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
Ok(Self {
name: input.parse()?,
_eq: input.parse()?,
ty: input.parse()?,
})
}
}
struct SimpleStruct {
type_path: syn::Path,
pub generics: syn::Generics,
fields: Vec<Field>,
}
impl syn::parse::Parse for SimpleStruct {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
//let type_path = syn::Path::parse_mod_style(input)?;
let type_path: syn::Path = input.parse()?;
/* let mut generics = input.parse::<syn::Generics>()?;
generics.where_clause = input.parse()?; */
let mut fields = vec![];
// parse fields if a comma is found
if input.peek(Token![,]) {
let _: Token![,] = input.parse()?;
let ident: syn::Ident = input.parse()?;
let ident_str = ident.to_string();
match ident_str.as_str() {
"fields" => {
let content;
let _parens: syn::token::Paren = parenthesized!(content in input);
let f: Punctuated<Field, Token![,]> = content.parse_terminated(Field::parse, Token![,])?;
fields = f.into_iter().collect();
},
_ => return Err(syn::Error::new(ident.span(), "Unknown macro command, expected `fields`")),
}
}
Ok(Self {
type_path,
generics: syn::Generics::default(),
fields,
})
}
}
fn gen_match_field_name_arm(simple: &SimpleStruct, default: &proc_macro2::TokenStream, arm_gen: fn(field: &Field) -> proc_macro2::TokenStream) -> proc_macro2::TokenStream {
let field_arms_iter = simple.fields.iter().map(|f| {
let fname = &f.name;
let arm = arm_gen(f);
quote! {
stringify!(#fname) => #arm
}
});
quote! {
match name {
#(#field_arms_iter,)*
_ => #default,
}
}
}
fn gen_match_field_index_arm(simple: &SimpleStruct, default: &proc_macro2::TokenStream, arm_gen: fn(field: &Field) -> proc_macro2::TokenStream) -> proc_macro2::TokenStream {
let field_arms_iter = simple.fields.iter().enumerate().map(|(idx, f)| {
let arm = arm_gen(f);
quote! {
#idx => #arm
}
});
quote! {
match idx {
#(#field_arms_iter,)*
_ => #default,
}
}
}
pub(crate) fn impl_reflect_simple_struct(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let simple = syn::parse_macro_input!(input as SimpleStruct);
let type_path = &simple.type_path;
let (impl_generics, ty_generics, where_clause) = simple.generics.split_for_impl();
// convert the type path to a string. This would not create a leading separator
/* let type_path_str = {
let idents: Vec<String> = type_path.segments.iter()
.map(|segment| segment.ident.to_string())
.collect();
idents.join("::")
}; */
let borrow_fn = |field: &Field| {
let name = &field.name;
quote! {
Some(&self.#name)
}
};
let borrow_mut_fn = |field: &Field| {
let name = &field.name;
quote! {
Some(&mut self.#name)
}
};
let none_default = quote!(None);
let false_return_default = quote!( { return false; });
let field_count = simple.fields.len();
let field_fn = gen_match_field_name_arm(&simple, &none_default, borrow_fn);
let field_mut_fn = gen_match_field_name_arm(&simple, &none_default, borrow_mut_fn);
let field_at_fn = gen_match_field_index_arm(&simple, &none_default, borrow_fn);
let field_at_mut_fn = gen_match_field_index_arm(&simple, &none_default, borrow_mut_fn);
let field_name_at_fn = gen_match_field_index_arm(&simple, &none_default, |f| {
let name = &f.name;
quote! {
Some(stringify!(#name))
}
});
let set_field_arm = |f: &Field| {
let name = &f.name;
let ty = &f.ty;
quote! {
self.#name = any_val.downcast_ref::<#ty>()
.expect(&format!("Cannot set struct's field of {} type to the provided type of {}", stringify!(#name), val.name()))
.clone() //Some(&#mut_tkn self.#field_ident)
}
};
let set_field_fn = gen_match_field_name_arm(&simple, &false_return_default, set_field_arm);
let set_field_at_fn = gen_match_field_index_arm(&simple, &false_return_default, set_field_arm);
let get_field_ty_arm = |f: &Field| {
let fty = &f.ty;
quote! {
Some(stringify!(#fty))
}
};
let field_type_fn = gen_match_field_name_arm(&simple, &none_default, get_field_ty_arm);
let field_type_at_fn = gen_match_field_index_arm(&simple, &none_default, get_field_ty_arm);
proc_macro::TokenStream::from(quote! {
impl #impl_generics lyra_engine::reflect::Reflect for #type_path #ty_generics #where_clause {
fn name(&self) -> ::std::string::String {
stringify!(#type_path).to_string()
}
fn type_id(&self) -> std::any::TypeId {
std::any::TypeId::of::<#type_path #ty_generics>()
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn apply(&mut self, val: &dyn lyra_engine::reflect::Reflect) {
let val = val.as_any().downcast_ref::<Self>()
.expect("The type of `val` is not the same as `self`");
*self = val.clone();
}
fn clone_inner(&self) -> Box<dyn lyra_engine::reflect::Reflect> {
Box::new(self.clone())
}
fn reflect_ref(&self) -> lyra_engine::reflect::ReflectRef {
lyra_engine::reflect::ReflectRef::Value(self)
}
fn reflect_mut(&mut self) -> lyra_engine::reflect::ReflectMut {
lyra_engine::reflect::ReflectMut::Value(self)
}
fn reflect_val(&self) -> &dyn lyra_engine::reflect::Reflect {
self
}
fn reflect_val_mut(&mut self) -> &mut dyn lyra_engine::reflect::Reflect {
self
}
}
impl #impl_generics lyra_engine::reflect::Struct for #type_path #ty_generics #where_clause {
fn field(&self, name: &str) -> Option<&dyn Reflect> {
#field_fn
}
fn field_mut(&mut self, name: &str) -> Option<&mut dyn Reflect> {
#field_mut_fn
}
fn fields_len(&self) -> usize {
#field_count
}
fn field_at(&self, idx: usize) -> Option<&dyn Reflect> {
#field_at_fn
}
fn field_at_mut(&mut self, idx: usize) -> Option<&mut dyn Reflect> {
#field_at_mut_fn
}
fn field_name_at(&self, idx: usize) -> Option<&str> {
#field_name_at_fn
}
fn set_field(&mut self, name: &str, val: &dyn Reflect) -> bool {
let any_val = val.as_any();
#set_field_fn
true
}
fn set_field_at(&mut self, idx: usize, val: &dyn Reflect) -> bool {
let any_val = val.as_any();
#set_field_at_fn
true
}
fn field_type(&self, name: &str) -> Option<&'static str> {
#field_type_fn
}
fn field_type_at(&self, idx: usize) -> Option<&'static str> {
#field_type_at_fn
}
fn method(&self, name: &str) -> Option<&Method> {
unimplemented!()
}
fn method_at(&self, idx: usize) -> Option<&Method> {
unimplemented!()
}
fn methods_len(&self) -> usize {
unimplemented!()
}
}
})
}

View File

@ -0,0 +1,74 @@
use std::{any::{Any, TypeId}, cell::{Ref, RefMut}};
use lyra_ecs::{Component, ComponentInfo, World, Entity, DynamicBundle};
use crate::{Reflect, FromType};
#[derive(Clone)]
pub struct ReflectedComponent {
pub type_id: TypeId,
pub info: ComponentInfo,
//value: Value,
//from_world:
//from_world: for<'a> fn (world: &'a mut World) -> Box<dyn Reflect>,
/// Inserts component into entity in the world
fn_insert: for<'a> fn (world: &'a mut World, entity: Entity, component: Box<dyn Reflect>),
/// Inserts component into a bundle
fn_bundle_insert: for<'a> fn (dynamic_bundle: &'a mut DynamicBundle, component: Box<dyn Reflect>),
fn_reflect: for<'a> fn (world: &'a World, entity: Entity) -> Option<Ref<'a, dyn Reflect>>,
fn_reflect_mut: for<'a> fn (world: &'a mut World, entity: Entity) -> Option<RefMut<'a, dyn Reflect>>,
}
impl ReflectedComponent {
/// Insert the reflected component into an entity.
pub fn insert(&self, world: &mut World, entity: Entity, component: Box<dyn Reflect>) {
(self.fn_insert)(world, entity, component);
}
/// Insert this component into a DynamicBundle
pub fn bundle_insert(&self, dynamic_bundle: &mut DynamicBundle, component: Box<dyn Reflect>) {
(self.fn_bundle_insert)(dynamic_bundle, component)
}
/// Retrieves a reflected component from an entity.
pub fn reflect<'a>(&'a self, world: &'a World, entity: Entity) -> Option<Ref<'a, dyn Reflect>> {
(self.fn_reflect)(world, entity)
}
/// Retrieves a reflected component from an entity.
pub fn reflect_mut<'a>(&'a mut self, world: &'a mut World, entity: Entity) -> Option<RefMut<'a, dyn Reflect>> {
(self.fn_reflect_mut)(world, entity)
}
}
impl<C: Component + Reflect> FromType<C> for ReflectedComponent {
fn from_type() -> Self {
ReflectedComponent {
type_id: TypeId::of::<C>(),
info: ComponentInfo::new::<C>(),
fn_insert: |world: &mut World, entity: Entity, component: Box<dyn Reflect>| {
let c = component as Box<dyn Any>;
let c = c.downcast::<C>()
.expect("Provided a non-matching type to ReflectedComponent insert method!");
let c = *c;
world.insert(entity, (c,));
},
fn_bundle_insert: |bundle: &mut DynamicBundle, component: Box<dyn Reflect>| {
let c = component as Box<dyn Any>;
let c = c.downcast::<C>()
.expect("Provided a non-matching type to ReflectedComponent insert method!");
let c = *c;
bundle.push(c);
},
fn_reflect: |world: &World, entity: Entity| {
world.view_one::<&C>(entity)
.get().map(|c| c as Ref<dyn Reflect>)
},
fn_reflect_mut: |world: &mut World, entity: Entity| {
world.view_one::<&mut C>(entity)
.get().map(|c| c as RefMut<dyn Reflect>)
},
}
}
}

View File

@ -0,0 +1,19 @@
use lyra_reflect_derive::{impl_reflect_simple_struct, impl_reflect_trait_value};
use crate::{lyra_engine, Method, Reflect};
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));
impl_reflect_simple_struct!(lyra_math::Vec4, fields(x = f32, y = f32, z = f32, w = f32));
impl_reflect_simple_struct!(lyra_math::Quat, fields(x = f32, y = f32, z = f32, w = f32));
impl_reflect_simple_struct!(
lyra_math::Transform,
fields(
translation = lyra_math::Vec3,
rotation = lyra_math::Quat,
scale = lyra_math::Vec3
)
);
impl_reflect_trait_value!(lyra_math::Mat4);

View File

@ -4,7 +4,7 @@ use crate::List;
use crate::lyra_engine; use crate::lyra_engine;
use super::{Reflect, ReflectRef, ReflectMut, util}; use crate::{Reflect, ReflectRef, ReflectMut, util};
impl_reflect_trait_value!(bool); impl_reflect_trait_value!(bool);
impl_reflect_trait_value!(char); impl_reflect_trait_value!(char);

View File

@ -0,0 +1,4 @@
pub mod impl_std;
#[cfg(feature="math")]
pub mod impl_math;

View File

@ -6,6 +6,8 @@ use lyra_ecs::{world::World, DynamicBundle, Component, Entity, ComponentInfo};
extern crate self as lyra_reflect; extern crate self as lyra_reflect;
pub use lyra_reflect_derive::*;
#[allow(unused_imports)] #[allow(unused_imports)]
pub(crate) mod lyra_engine { pub(crate) mod lyra_engine {
pub(crate) mod reflect { pub(crate) mod reflect {
@ -34,6 +36,12 @@ pub use dynamic_tuple::*;
pub mod reflected_field; pub mod reflected_field;
pub use reflected_field::*; pub use reflected_field::*;
pub mod component;
pub use component::*;
pub mod resource;
pub use resource::*;
pub mod util; pub mod util;
pub mod field; pub mod field;
pub use field::*; pub use field::*;
@ -42,7 +50,7 @@ pub use method::*;
pub mod registry; pub mod registry;
pub use registry::*; pub use registry::*;
pub mod impl_std; pub mod impls;
pub trait Reflect: Any { pub trait Reflect: Any {
fn name(&self) -> String; fn name(&self) -> String;
@ -235,67 +243,48 @@ pub trait FromType<T> {
fn from_type() -> Self; fn from_type() -> Self;
} }
#[derive(Clone)] pub trait ReflectWorldExt {
pub struct ReflectedComponent { /// Retrieves the type registry from the world.
pub type_id: TypeId, fn get_type_registry(&self) -> Ref<TypeRegistry>;
pub info: ComponentInfo, /// Retrieves the type registry mutably from the world.
//value: Value, fn get_type_registry_mut(&self) -> RefMut<TypeRegistry>;
//from_world: /// Get a registered type from the type registry. Returns `None` if the type is not registered
fn get_type<T>(&self, type_id: TypeId) -> Option<Ref<RegisteredType>>;
//from_world: for<'a> fn (world: &'a mut World) -> Box<dyn Reflect>, /// Get a mutable registered type from the type registry in the world. returns `None` if the type is not registered.
/// Inserts component into entity in the world fn get_type_mut<T>(&self, type_id: TypeId) -> Option<RefMut<RegisteredType>>;
fn_insert: for<'a> fn (world: &'a mut World, entity: Entity, component: &dyn Reflect), /// Get a registered type, or register a new type and return it.
/// Inserts component into a bundle fn get_type_or_default<T>(&self, type_id: TypeId) -> RefMut<RegisteredType>;
fn_bundle_insert: for<'a> fn (dynamic_bundle: &'a mut DynamicBundle, component: &dyn Reflect),
fn_reflect: for<'a> fn (world: &'a World, entity: Entity) -> Option<Ref<'a, dyn Reflect>>,
fn_reflect_mut: for<'a> fn (world: &'a mut World, entity: Entity) -> Option<RefMut<'a, dyn Reflect>>,
} }
impl ReflectedComponent { impl ReflectWorldExt for World {
/// Insert the reflected component into an entity. fn get_type_registry(&self) -> Ref<TypeRegistry> {
pub fn insert(&self, world: &mut World, entity: Entity, component: &dyn Reflect) { self.get_resource::<TypeRegistry>()
(self.fn_insert)(world, entity, component);
} }
/// Insert this component into a DynamicBundle fn get_type_registry_mut(&self) -> RefMut<TypeRegistry> {
pub fn bundle_insert(&self, dynamic_bundle: &mut DynamicBundle, component: &dyn Reflect) { self.get_resource_mut::<TypeRegistry>()
(self.fn_bundle_insert)(dynamic_bundle, component)
} }
/// Retrieves a reflected component from an entity. fn get_type<T>(&self, type_id: TypeId) -> Option<Ref<RegisteredType>> {
pub fn reflect<'a>(&'a self, world: &'a World, entity: Entity) -> Option<Ref<'a, dyn Reflect>> { let r = self.get_resource::<TypeRegistry>();
(self.fn_reflect)(world, entity) if r.has_type(type_id) {
} Some(Ref::map(r, |tr| tr.get_type(type_id).unwrap()))
} else {
/// Retrieves a reflected component from an entity. None
pub fn reflect_mut<'a>(&'a mut self, world: &'a mut World, entity: Entity) -> Option<RefMut<'a, dyn Reflect>> {
(self.fn_reflect_mut)(world, entity)
}
}
impl<C: Component + Reflect + Default> FromType<C> for ReflectedComponent {
fn from_type() -> Self {
ReflectedComponent {
type_id: TypeId::of::<C>(),
info: ComponentInfo::new::<C>(),
fn_insert: |world: &mut World, entity: Entity, component: &dyn Reflect| {
let mut c = C::default();
c.apply(component);
world.insert(entity, (c,));
},
fn_bundle_insert: |bundle: &mut DynamicBundle, component: &dyn Reflect| {
let mut c = C::default();
c.apply(component);
bundle.push(c);
},
fn_reflect: |world: &World, entity: Entity| {
world.view_one::<&C>(entity)
.get().map(|c| c as Ref<dyn Reflect>)
},
fn_reflect_mut: |world: &mut World, entity: Entity| {
world.view_one::<&mut C>(entity)
.get().map(|c| c as RefMut<dyn Reflect>)
},
} }
} }
fn get_type_mut<T>(&self, type_id: TypeId) -> Option<RefMut<RegisteredType>> {
let r = self.get_resource_mut::<TypeRegistry>();
if r.has_type(type_id) {
Some(RefMut::map(r, |tr| tr.get_type_mut(type_id).unwrap()))
} else {
None
}
}
fn get_type_or_default<T>(&self, type_id: TypeId) -> RefMut<RegisteredType> {
let r = self.get_resource_mut::<TypeRegistry>();
RefMut::map(r, |tr| tr.get_type_or_default(type_id))
}
} }

View File

@ -52,7 +52,6 @@ pub trait Enum: Reflect {
#[allow(unused_variables)] #[allow(unused_variables)]
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use lyra_reflect_derive::Reflect;
use super::EnumType; use super::EnumType;
use crate::{lyra_engine, Reflect, ReflectRef}; use crate::{lyra_engine, Reflect, ReflectRef};

View File

@ -41,7 +41,6 @@ pub trait Struct: Reflect {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use lyra_reflect_derive::Reflect;
use crate::{Reflect, ReflectRef, ReflectMut}; use crate::{Reflect, ReflectRef, ReflectMut};
use crate::lyra_engine; use crate::lyra_engine;

View File

@ -21,6 +21,12 @@ impl TypeRegistry {
self.inner.get_mut(&type_id) self.inner.get_mut(&type_id)
} }
/// Get a registered type, or register a new type and return it.
pub fn get_type_or_default(&mut self, type_id: TypeId) -> &mut RegisteredType {
self.inner.entry(type_id)
.or_insert(RegisteredType::default())
}
pub fn register_type<T>(&mut self) pub fn register_type<T>(&mut self)
where where
T: AsRegisteredType + 'static T: AsRegisteredType + 'static

View File

@ -0,0 +1,63 @@
use std::{any::{Any, TypeId}, cell::{Ref, RefMut}, ptr::NonNull};
use lyra_ecs::{World, ResourceObject};
use crate::{Reflect, FromType};
#[derive(Clone)]
pub struct ReflectedResource {
pub type_id: TypeId,
fn_reflect: for<'a> fn (world: &'a World) -> Option<Ref<'a, dyn Reflect>>,
fn_reflect_mut: for<'a> fn (world: &'a mut World) -> Option<RefMut<'a, dyn Reflect>>,
fn_reflect_ptr: fn (world: &mut World) -> Option<NonNull<u8>>,
fn_refl_insert: fn (world: &mut World, this: Box<dyn Reflect>),
}
impl ReflectedResource {
/// Retrieves the reflected resource from the world.
pub fn reflect<'a>(&self, world: &'a World) -> Option<Ref<'a, dyn Reflect>> {
(self.fn_reflect)(world)
}
/// Retrieves a mutable reflected resource from the world.
pub fn reflect_mut<'a>(&self, world: &'a mut World) -> Option<RefMut<'a, dyn Reflect>> {
(self.fn_reflect_mut)(world)
}
pub fn reflect_ptr(&self, world: &mut World) -> Option<NonNull<u8>> {
(self.fn_reflect_ptr)(world)
}
/// Insert the resource into the world.
pub fn insert(&self, world: &mut World, this: Box<dyn Reflect>) {
(self.fn_refl_insert)(world, this)
}
}
impl<T: ResourceObject + Reflect> FromType<T> for ReflectedResource {
fn from_type() -> Self {
Self {
type_id: TypeId::of::<T>(),
fn_reflect: |world: &World| {
world.try_get_resource::<T>()
.map(|r| r as Ref<dyn Reflect>)
},
fn_reflect_mut: |world: &mut World| {
world.try_get_resource_mut::<T>()
.map(|r| r as RefMut<dyn Reflect>)
},
fn_reflect_ptr: |world: &mut World| unsafe {
world.try_get_resource_ptr::<T>()
.map(|ptr| ptr.cast::<u8>())
},
fn_refl_insert: |world: &mut World, this: Box<dyn Reflect>| {
let res = this as Box<dyn Any>;
let res = res.downcast::<T>()
.expect("Provided a non-matching type to ReflectedResource insert method!");
let res = *res;
world.add_resource(res);
}
}
}
}

View File

@ -6,15 +6,19 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
lyra-ecs = { path = "../lyra-ecs" }
anyhow = "1.0.75" anyhow = "1.0.75"
base64 = "0.21.4" base64 = "0.21.4"
edict = "0.5.0" crossbeam = { version = "0.8.4", features = [ "crossbeam-channel" ] }
glam = "0.24.1" glam = "0.24.1"
gltf = { version = "1.3.0", features = ["KHR_materials_pbrSpecularGlossiness", "KHR_materials_specular"] } gltf = { version = "1.3.0", features = ["KHR_materials_pbrSpecularGlossiness", "KHR_materials_specular"] }
image = "0.24.7" image = "0.24.7"
# not using custom matcher, or file type from file path # not using custom matcher, or file type from file path
infer = { version = "0.15.0", default-features = false } infer = { version = "0.15.0", default-features = false }
mime = "0.3.17" mime = "0.3.17"
notify = "6.1.1"
notify-debouncer-full = "0.3.1"
#notify = { version = "6.1.1", default-features = false, features = [ "fsevent-sys", "macos_fsevent" ]} # disables crossbeam-channel
percent-encoding = "2.3.0" percent-encoding = "2.3.0"
thiserror = "1.0.48" thiserror = "1.0.48"
tracing = "0.1.37" tracing = "0.1.37"

View File

@ -16,4 +16,17 @@ pub use model::*;
pub mod material; pub mod material;
pub use material::*; pub use material::*;
pub(crate) mod util; pub mod world_ext;
pub use world_ext::*;
pub(crate) mod util;
pub use crossbeam::channel as channel;
pub use notify;
#[allow(unused_imports)]
pub(crate) mod lyra_engine {
pub(crate) mod ecs {
pub use lyra_ecs::*;
}
}

View File

@ -2,7 +2,7 @@ use std::{fs::File, sync::Arc, io::Read};
use image::ImageError; use image::ImageError;
use crate::{resource_manager::ResourceStorage, texture::Texture, resource::Resource, ResourceManager}; use crate::{resource_manager::ResourceStorage, texture::Texture, resource::ResHandle, ResourceManager};
use super::{LoaderError, ResourceLoader}; use super::{LoaderError, ResourceLoader};
@ -62,7 +62,7 @@ impl ResourceLoader for ImageLoader {
let texture = Texture { let texture = Texture {
image, image,
}; };
let res = Resource::with_data(path, texture); let res = ResHandle::with_data(path, texture);
Ok(Arc::new(res)) Ok(Arc::new(res))
} }
@ -76,7 +76,7 @@ impl ResourceLoader for ImageLoader {
let texture = Texture { let texture = Texture {
image, image,
}; };
let res = Resource::with_data(&uuid::Uuid::new_v4().to_string(), texture); let res = ResHandle::with_data(&uuid::Uuid::new_v4().to_string(), texture);
Ok(Arc::new(res)) Ok(Arc::new(res))
} }

View File

@ -30,7 +30,7 @@ impl From<io::Error> for LoaderError {
} }
pub trait ResourceLoader { pub trait ResourceLoader {
/// Returns the extensions that this loader supports. /// Returns the extensions that this loader supports. Does not include the `.`
fn extensions(&self) -> &[&str]; fn extensions(&self) -> &[&str];
/// Returns the mime types that this loader supports. /// Returns the mime types that this loader supports.
fn mime_types(&self) -> &[&str]; fn mime_types(&self) -> &[&str];

View File

@ -2,7 +2,7 @@ use std::{sync::Arc, path::PathBuf};
use thiserror::Error; use thiserror::Error;
use crate::{ResourceLoader, LoaderError, Mesh, Model, MeshVertexAttribute, VertexAttributeData, Resource, Material, MeshIndices, ResourceManager, util}; use crate::{ResourceLoader, LoaderError, Mesh, Model, MeshVertexAttribute, VertexAttributeData, ResHandle, Material, MeshIndices, ResourceManager, util};
use tracing::debug; use tracing::debug;
@ -189,7 +189,7 @@ impl ResourceLoader for ModelLoader {
.collect(); .collect();
debug!("Loaded {} meshes, and {} materials from '{}'", meshes.len(), materials.len(), path); debug!("Loaded {} meshes, and {} materials from '{}'", meshes.len(), materials.len(), path);
Ok(Arc::new(Resource::with_data(path, Model::new(meshes)))) Ok(Arc::new(ResHandle::with_data(path, Model::new(meshes))))
} }
#[allow(unused_variables)] #[allow(unused_variables)]
@ -216,8 +216,8 @@ mod tests {
let mut manager = ResourceManager::new(); let mut manager = ResourceManager::new();
let loader = ModelLoader::default(); let loader = ModelLoader::default();
let model = loader.load(&mut manager, &path).unwrap(); let model = loader.load(&mut manager, &path).unwrap();
let model = Arc::downcast::<Resource<Model>>(model.as_arc_any()).unwrap(); let model = Arc::downcast::<ResHandle<Model>>(model.as_arc_any()).unwrap();
let model = model.data.as_ref().unwrap(); let model = model.data_ref();
assert_eq!(model.meshes.len(), 1); // There should only be 1 mesh assert_eq!(model.meshes.len(), 1); // There should only be 1 mesh
let mesh = &model.meshes[0]; let mesh = &model.meshes[0];
assert!(mesh.position().unwrap().len() > 0); assert!(mesh.position().unwrap().len() > 0);

View File

@ -1,6 +1,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::Material; use crate::Material;
use crate::lyra_engine;
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
@ -81,7 +82,7 @@ pub enum MeshVertexAttribute {
Other(String), Other(String),
} }
#[derive(Clone, edict::Component)] #[derive(Clone, lyra_ecs::Component)]
pub struct Mesh { pub struct Mesh {
pub uuid: uuid::Uuid, pub uuid: uuid::Uuid,
pub attributes: HashMap<MeshVertexAttribute, VertexAttributeData>, pub attributes: HashMap<MeshVertexAttribute, VertexAttributeData>,

View File

@ -1,32 +1,174 @@
use std::sync::Arc; use std::{any::Any, sync::{Arc, RwLock}};
use uuid::Uuid; use uuid::Uuid;
use crate::ResourceStorage;
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum ResourceState { pub enum ResourceState {
Loading, Loading,
Ready, Ready,
} }
#[derive(Clone)] pub struct ResourceDataRef<'a, T> {
pub struct Resource<T> { guard: std::sync::RwLockReadGuard<'a, Resource<T>>,
pub path: String,
pub data: Option<Arc<T>>,
pub uuid: Uuid,
pub state: ResourceState,
} }
/// A helper type to make it easier to use resources impl<'a, T> std::ops::Deref for ResourceDataRef<'a, T> {
pub type ResHandle<T> = Arc<Resource<T>>; type Target = T;
impl<T> Resource<T> { fn deref(&self) -> &Self::Target {
// safety: this struct must only be created if the resource is loaded
self.guard.data.as_ref().unwrap()
}
}
pub struct Resource<T> {
path: String,
pub(crate) data: Option<T>,
pub(crate) version: usize,
pub(crate) state: ResourceState,
uuid: Uuid,
pub(crate) is_watched: bool,
}
/// A handle to a resource.
///
/// # Note
/// This struct has an inner [`RwLock`] to the resource data, so most methods may be blocking.
/// However, the only times it will be blocking is if another thread is reloading the resource
/// and has a write lock on the data. This means that most of the time, it is not blocking.
pub struct ResHandle<T> {
pub(crate) data: Arc<RwLock<Resource<T>>>,
}
impl<T> Clone for ResHandle<T> {
fn clone(&self) -> Self {
Self { data: self.data.clone() }
}
}
impl<T> ResHandle<T> {
/// Create the resource with data, its assumed the state is `Ready` /// Create the resource with data, its assumed the state is `Ready`
pub fn with_data(path: &str, data: T) -> Self { pub fn with_data(path: &str, data: T) -> Self {
Self { let res_version = Resource {
path: path.to_string(), path: path.to_string(),
data: Some(Arc::new(data)), data: Some(data),
uuid: Uuid::new_v4(), version: 0,
state: ResourceState::Ready, state: ResourceState::Ready,
uuid: Uuid::new_v4(),
is_watched: false,
};
Self {
data: Arc::new(RwLock::new(res_version)),
} }
} }
/// Returns a boolean indicating if this resource's path is being watched.
pub fn is_watched(&self) -> bool {
let d = self.data.read().expect("Resource mutex was poisoned!");
d.is_watched
}
/// Returns a boolean indicating if this resource is loaded
pub fn is_loaded(&self) -> bool {
let d = self.data.read().expect("Resource mutex was poisoned!");
d.state == ResourceState::Ready
}
/// Returns the current state of the resource.
pub fn state(&self) -> ResourceState {
let d = self.data.read().expect("Resource mutex was poisoned!");
d.state
}
/// Returns the path that the resource was loaded from.
pub fn path(&self) -> String {
let d = self.data.read().expect("Resource mutex was poisoned!");
d.path.to_string()
}
/// Returns the uuid of the resource.
pub fn uuid(&self) -> Uuid {
let d = self.data.read().expect("Resource mutex was poisoned!");
d.uuid
}
/// Retrieves the current version of the resource. This gets incremented when the resource
/// is reloaded.
pub fn version(&self) -> usize {
let d = self.data.read().expect("Resource mutex was poisoned!");
d.version
}
/// Get a reference to the data in the resource
///
/// # Panics
/// Panics if the resource was not loaded yet.
pub fn data_ref<'a>(&'a self) -> ResourceDataRef<'a, T> {
let d = self.data.read().expect("Resource mutex was poisoned!");
ResourceDataRef {
guard: d
}
}
/// Attempt to get a borrow to the resource data. Returns `None` if the resource is not loaded.
pub fn try_data_ref<'a>(&'a self) -> Option<ResourceDataRef<'a, T>> {
if self.is_loaded() {
let d = self.data.read().expect("Resource mutex was poisoned!");
Some(ResourceDataRef {
guard: d
})
} else {
None
}
}
}
impl<T: Send + Sync + 'static> ResourceStorage for ResHandle<T> {
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn as_arc_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync> {
self.clone()
}
fn as_box_any(self: Box<Self>) -> Box<dyn Any + Send + Sync> {
self
}
fn set_watched(&self, watched: bool) {
let mut w = self.data.write().unwrap();
w.is_watched = watched;
}
fn path(&self) -> String {
self.path()
}
fn version(&self) -> usize {
self.version()
}
fn state(&self) -> ResourceState {
self.state()
}
fn uuid(&self) -> Uuid {
self.uuid()
}
fn is_watched(&self) -> bool {
self.is_watched()
}
fn is_loaded(&self) -> bool {
self.is_loaded()
}
} }

View File

@ -1,28 +1,30 @@
use std::{sync::Arc, collections::HashMap, any::Any}; use std::{sync::{Arc, RwLock}, collections::HashMap, any::Any, path::Path, time::Duration};
use crossbeam::channel::Receiver;
use notify::{Watcher, RecommendedWatcher};
use notify_debouncer_full::{DebouncedEvent, FileIdMap};
use thiserror::Error; use thiserror::Error;
use uuid::Uuid;
use crate::{resource::Resource, loader::{ResourceLoader, LoaderError, image::ImageLoader, model::ModelLoader}}; use crate::{loader::{image::ImageLoader, model::ModelLoader, LoaderError, ResourceLoader}, resource::ResHandle, ResourceState};
/// A trait for type erased storage of a resource.
/// Implemented for [`ResHandle<T>`]
pub trait ResourceStorage: Send + Sync + Any + 'static { pub trait ResourceStorage: Send + Sync + Any + 'static {
fn as_any(&self) -> &dyn Any; fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any; fn as_any_mut(&mut self) -> &mut dyn Any;
fn as_arc_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync>; fn as_arc_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync>;
} fn as_box_any(self: Box<Self>) -> Box<dyn Any + Send + Sync>;
/// Do not set a resource to watched if it is not actually watched.
/// This is used internally.
fn set_watched(&self, watched: bool);
/// Implements this trait for anything that fits the type bounds fn path(&self) -> String;
impl<T: Send + Sync + 'static> ResourceStorage for T { fn version(&self) -> usize;
fn as_any(&self) -> &dyn Any { fn state(&self) -> ResourceState;
self fn uuid(&self) -> Uuid;
} fn is_watched(&self) -> bool;
fn is_loaded(&self) -> bool;
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn as_arc_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync> {
self.clone()
}
} }
#[derive(Error, Debug)] #[derive(Error, Debug)]
@ -46,9 +48,16 @@ impl From<LoaderError> for RequestError {
/// A struct that stores all Manager data. This is requried for sending /// A struct that stores all Manager data. This is requried for sending
//struct ManagerStorage //struct ManagerStorage
/// A struct that
pub struct ResourceWatcher {
debouncer: Arc<RwLock<notify_debouncer_full::Debouncer<RecommendedWatcher, FileIdMap>>>,
events_recv: Receiver<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>,
}
pub struct ResourceManager { pub struct ResourceManager {
resources: HashMap<String, Arc<dyn ResourceStorage>>, resources: HashMap<String, Arc<dyn ResourceStorage>>,
loaders: Vec<Arc<dyn ResourceLoader>>, loaders: Vec<Arc<dyn ResourceLoader>>,
watchers: HashMap<String, ResourceWatcher>,
} }
impl Default for ResourceManager { impl Default for ResourceManager {
@ -62,6 +71,7 @@ impl ResourceManager {
Self { Self {
resources: HashMap::new(), resources: HashMap::new(),
loaders: vec![ Arc::new(ImageLoader), Arc::new(ModelLoader) ], loaders: vec![ Arc::new(ImageLoader), Arc::new(ModelLoader) ],
watchers: HashMap::new(),
} }
} }
@ -73,11 +83,15 @@ impl ResourceManager {
self.loaders.push(Arc::new(L::default())); self.loaders.push(Arc::new(L::default()));
} }
pub fn request<T: Send + Sync + Any + 'static>(&mut self, path: &str) -> Result<Arc<Resource<T>>, RequestError> { pub fn request<T>(&mut self, path: &str) -> Result<ResHandle<T>, RequestError>
where
T: Send + Sync + Any + 'static
{
match self.resources.get(&path.to_string()) { match self.resources.get(&path.to_string()) {
Some(res) => { Some(res) => {
let res = res.clone().as_arc_any(); let res = res.clone().as_arc_any();
let res = res.downcast::<Resource<T>>().expect("Failure to downcast resource"); let res: Arc<ResHandle<T>> = res.downcast::<ResHandle<T>>().expect("Failure to downcast resource");
let res = ResHandle::<T>::clone(&res);
Ok(res) Ok(res)
}, },
@ -88,12 +102,43 @@ impl ResourceManager {
// Load the resource and store it // Load the resource and store it
let loader = Arc::clone(loader); // stop borrowing from self let loader = Arc::clone(loader); // stop borrowing from self
let res = loader.load(self, path)?; let res = loader.load(self, path)?;
let res: Arc<dyn ResourceStorage> = Arc::from(res);
self.resources.insert(path.to_string(), res.clone()); self.resources.insert(path.to_string(), res.clone());
// cast Arc<dyn ResourceStorage> to Arc<Resource<T> // cast Arc<dyn ResourceStorage> to Arc<Resource<T>
let res = res.as_arc_any(); let res = res.as_arc_any();
let res = res.downcast::<Resource<T>>() let res = res.downcast::<ResHandle<T>>()
.expect("Failure to downcast resource! Does the loader return an `Arc<Resource<T>>`?"); .expect("Failure to downcast resource! Does the loader return an `Arc<Resource<T>>`?");
let res = ResHandle::<T>::clone(&res);
Ok(res)
} else {
Err(RequestError::UnsupportedFileExtension(path.to_string()))
}
}
}
}
/// Request a resource without downcasting to a ResHandle<T>.
/// Whenever you're ready to downcast, you can do so like this:
/// ```compile_fail
/// let arc_any = res_arc.as_arc_any();
/// let res: Arc<ResHandle<T>> = res.downcast::<ResHandle<T>>().expect("Failure to downcast resource");
/// ```
pub fn request_raw(&mut self, path: &str) -> Result<Arc<dyn ResourceStorage>, RequestError> {
match self.resources.get(&path.to_string()) {
Some(res) => {
Ok(res.clone())
},
None => {
if let Some(loader) = self.loaders.iter()
.find(|l| l.does_support_file(path)) {
// Load the resource and store it
let loader = Arc::clone(loader); // stop borrowing from self
let res = loader.load(self, path)?;
let res: Arc<dyn ResourceStorage> = Arc::from(res);
self.resources.insert(path.to_string(), res.clone());
Ok(res) Ok(res)
} else { } else {
@ -112,18 +157,23 @@ impl ResourceManager {
/// * `bytes` - The bytes to store. /// * `bytes` - The bytes to store.
/// ///
/// Returns: The `Arc` to the now stored resource /// Returns: The `Arc` to the now stored resource
pub fn load_bytes<T: Send + Sync + Any + 'static>(&mut self, ident: &str, mime_type: &str, bytes: Vec<u8>, offset: usize, length: usize) -> Result<Arc<Resource<T>>, RequestError> { pub fn load_bytes<T>(&mut self, ident: &str, mime_type: &str, bytes: Vec<u8>, offset: usize, length: usize) -> Result<ResHandle<T>, RequestError>
where
T: Send + Sync + Any + 'static
{
if let Some(loader) = self.loaders.iter() if let Some(loader) = self.loaders.iter()
.find(|l| l.does_support_mime(mime_type)) { .find(|l| l.does_support_mime(mime_type)) {
let loader = loader.clone(); let loader = loader.clone();
let res = loader.load_bytes(self, bytes, offset, length)?; let res = loader.load_bytes(self, bytes, offset, length)?;
let res: Arc<dyn ResourceStorage> = Arc::from(res);
self.resources.insert(ident.to_string(), res.clone()); self.resources.insert(ident.to_string(), res.clone());
// code here... // code here...
// cast Arc<dyn ResourceStorage> to Arc<Resource<T> // cast Arc<dyn ResourceStorage> to Arc<Resource<T>
let res = res.as_arc_any(); let res = res.as_arc_any();
let res = res.downcast::<Resource<T>>() let res = res.downcast::<ResHandle<T>>()
.expect("Failure to downcast resource! Does the loader return an `Arc<Resource<T>>`?"); .expect("Failure to downcast resource! Does the loader return an `Arc<Resource<T>>`?");
let res = ResHandle::<T>::clone(&res);
Ok(res) Ok(res)
} else { } else {
@ -132,11 +182,14 @@ impl ResourceManager {
} }
/// Requests bytes from the manager. /// Requests bytes from the manager.
pub fn request_loaded_bytes<T: Send + Sync + Any + 'static>(&mut self, ident: &str) -> Result<Arc<Resource<T>>, RequestError> { pub fn request_loaded_bytes<T>(&mut self, ident: &str) -> Result<Arc<ResHandle<T>>, RequestError>
where
T: Send + Sync + Any + 'static
{
match self.resources.get(&ident.to_string()) { match self.resources.get(&ident.to_string()) {
Some(res) => { Some(res) => {
let res = res.clone().as_arc_any(); let res = res.clone().as_arc_any();
let res = res.downcast::<Resource<T>>().expect("Failure to downcast resource"); let res = res.downcast::<ResHandle<T>>().expect("Failure to downcast resource");
Ok(res) Ok(res)
}, },
@ -145,6 +198,91 @@ impl ResourceManager {
} }
} }
} }
/// Start watching a path for changes. Returns a mspc channel that will send events
pub fn watch(&mut self, path: &str, recursive: bool) -> notify::Result<Receiver<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>> {
let (send, recv) = crossbeam::channel::bounded(15);
let mut watcher = notify_debouncer_full::new_debouncer(Duration::from_millis(1000), None, send)?;
let recurse_mode = match recursive {
true => notify::RecursiveMode::Recursive,
false => notify::RecursiveMode::NonRecursive,
};
watcher.watcher().watch(path.as_ref(), recurse_mode)?;
let watcher = Arc::new(RwLock::new(watcher));
let watcher = ResourceWatcher {
debouncer: watcher,
events_recv: recv.clone(),
};
self.watchers.insert(path.to_string(), watcher);
let res = self.resources.get(&path.to_string())
.expect("The path that was watched has not been loaded as a resource yet");
res.set_watched(true);
Ok(recv)
}
/// Stops watching a path
pub fn stop_watching(&mut self, path: &str) -> notify::Result<()> {
if let Some(watcher) = self.watchers.get(path) {
let mut watcher = watcher.debouncer.write().unwrap();
watcher.watcher().unwatch(Path::new(path))?;
// unwrap is safe since only loaded resources can be watched
let res = self.resources.get(&path.to_string()).unwrap();
res.set_watched(false);
}
Ok(())
}
/// Returns a mspc receiver for watcher events of a specific path. The path must already
/// be watched with [`ResourceManager::watch`] for this to return `Some`.
pub fn watcher_event_recv(&self, path: &str) -> Option<Receiver<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>> {
self.watchers.get(&path.to_string())
.map(|w| w.events_recv.clone())
}
/// Reloads a resource. The data inside the resource will be updated, the state may
pub fn reload<T>(&mut self, resource: ResHandle<T>) -> Result<(), RequestError>
where
T: Send + Sync + Any + 'static
{
let path = resource.path();
if let Some(loader) = self.loaders.iter()
.find(|l| l.does_support_file(&path)) {
println!("got loader");
let loader = Arc::clone(loader); // stop borrowing from self
let loaded = loader.load(self, &path)?;
let loaded = loaded.as_arc_any();
let loaded = loaded.downcast::<ResHandle<T>>()
.unwrap();
let loaded = match Arc::try_unwrap(loaded) {
Ok(v) => v,
Err(_) => panic!("Seems impossible that this would happen, the resource was just loaded!"),
};
let loaded = loaded.data;
let loaded = match Arc::try_unwrap(loaded) {
Ok(v) => v,
Err(_) => panic!("Seems impossible that this would happen, the resource was just loaded!"),
};
let loaded = loaded.into_inner().unwrap();
let res_lock = &resource.data;
let mut res_lock = res_lock.write().unwrap();
let version = res_lock.version;
res_lock.data = loaded.data;
res_lock.state = loaded.state;
res_lock.version = version + 1;
}
Ok(())
}
} }
#[cfg(test)] #[cfg(test)]
@ -165,20 +303,20 @@ mod tests {
fn load_image() { fn load_image() {
let mut man = ResourceManager::new(); let mut man = ResourceManager::new();
let res = man.request::<Texture>(&get_image("squiggles.png")).unwrap(); let res = man.request::<Texture>(&get_image("squiggles.png")).unwrap();
assert_eq!(res.state, ResourceState::Ready); assert_eq!(res.state(), ResourceState::Ready);
let img = res.data.as_ref(); let img = res.try_data_ref();
img.unwrap(); img.unwrap();
} }
/// Ensures that only one copy of the same thing made /// Ensures that only one copy of the same data was made
#[test] #[test]
fn ensure_single() { fn ensure_single() {
let mut man = ResourceManager::new(); let mut man = ResourceManager::new();
let res = man.request::<Texture>(&get_image("squiggles.png")).unwrap(); let res = man.request::<Texture>(&get_image("squiggles.png")).unwrap();
assert_eq!(Arc::strong_count(&res), 2); assert_eq!(Arc::strong_count(&res.data), 2);
let resagain = man.request::<Texture>(&get_image("squiggles.png")).unwrap(); let resagain = man.request::<Texture>(&get_image("squiggles.png")).unwrap();
assert_eq!(Arc::strong_count(&resagain), 3); assert_eq!(Arc::strong_count(&resagain.data), 3);
} }
/// Ensures that an error is returned when a file that doesn't exist is requested /// Ensures that an error is returned when a file that doesn't exist is requested
@ -196,4 +334,42 @@ mod tests {
} }
); );
} }
#[test]
fn reload_image() {
let mut man = ResourceManager::new();
let res = man.request::<Texture>(&get_image("squiggles.png")).unwrap();
assert_eq!(res.state(), ResourceState::Ready);
let img = res.try_data_ref();
img.unwrap();
println!("Path = {}", res.path());
man.reload(res.clone()).unwrap();
assert_eq!(res.version(), 1);
man.reload(res.clone()).unwrap();
assert_eq!(res.version(), 2);
}
#[test]
fn watch_image() {
let orig_path = get_image("squiggles.png");
let image_path = get_image("squiggles_test.png");
std::fs::copy(orig_path, &image_path).unwrap();
let mut man = ResourceManager::new();
let res = man.request::<Texture>(&image_path).unwrap();
assert_eq!(res.state(), ResourceState::Ready);
let img = res.try_data_ref();
img.unwrap();
let recv = man.watch(&image_path, false).unwrap();
std::fs::remove_file(&image_path).unwrap();
let event = recv.recv().unwrap();
let event = event.unwrap();
assert!(event.iter().any(|ev| ev.kind.is_remove() || ev.kind.is_modify()));
}
} }

View File

@ -0,0 +1,75 @@
use std::any::Any;
use crossbeam::channel::Receiver;
use lyra_ecs::World;
use notify_debouncer_full::DebouncedEvent;
use crate::{RequestError, ResHandle, ResourceLoader, ResourceManager};
pub trait WorldAssetExt {
/// Register a resource loader with the resource manager.
fn register_res_loader<L>(&mut self)
where
L: ResourceLoader + Default + 'static;
/// Request a resource from the resource manager.
fn request_res<T>(&mut self, path: &str) -> Result<ResHandle<T>, RequestError>
where
T: Send + Sync + Any + 'static;
/// Start watching a resource for changes. Returns a crossbeam channel that can be used to listen for events.
fn watch_res(&mut self, path: &str, recursive: bool) -> notify::Result<Receiver<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>>;
/// Stop watching a resource for changes.
fn stop_watching_res(&mut self, path: &str) -> notify::Result<()>;
/// Try to retrieve a crossbeam channel for a path that is currently watched. Returns None if the path is not watched
fn res_watcher_recv(&self, path: &str) -> Option<Receiver<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>>;
/// Reload a resource. The data will be updated in the handle. This is not
/// automatically triggered if the resource is being watched.
fn reload_res<T>(&mut self, resource: ResHandle<T>) -> Result<(), RequestError>
where
T: Send + Sync + Any + 'static;
}
impl WorldAssetExt for World {
fn register_res_loader<L>(&mut self)
where
L: ResourceLoader + Default + 'static
{
let mut man = self.get_resource_or_default::<ResourceManager>();
man.register_loader::<L>();
}
fn request_res<T>(&mut self, path: &str) -> Result<ResHandle<T>, RequestError>
where
T: Send + Sync + Any + 'static
{
let mut man = self.get_resource_or_default::<ResourceManager>();
man.request(path)
}
fn watch_res(&mut self, path: &str, recursive: bool) -> notify::Result<Receiver<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>> {
let mut man = self.get_resource_or_default::<ResourceManager>();
man.watch(path, recursive)
}
fn stop_watching_res(&mut self, path: &str) -> notify::Result<()> {
let mut man = self.get_resource_or_default::<ResourceManager>();
man.stop_watching(path)
}
fn res_watcher_recv(&self, path: &str) -> Option<Receiver<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>> {
let man = self.get_resource::<ResourceManager>();
man.watcher_event_recv(path)
}
fn reload_res<T>(&mut self, resource: ResHandle<T>) -> Result<(), RequestError>
where
T: Send + Sync + Any + 'static
{
let mut man = self.get_resource_or_default::<ResourceManager>();
man.reload(resource)
}
}

View File

@ -7,11 +7,13 @@ edition = "2021"
[features] [features]
default = ["lua"] default = ["lua"]
lua = ["dep:mlua"] lua = ["dep:elua"]
teal = ["lua", "elua/teal"]
[dependencies] [dependencies]
lyra-ecs = { path = "../lyra-ecs" } lyra-scripting-derive = { path = "lyra-scripting-derive" }
lyra-reflect = { path = "../lyra-reflect" } lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] }
lyra-reflect = { path = "../lyra-reflect", features = [ "math" ] }
lyra-resource = { path = "../lyra-resource" } lyra-resource = { path = "../lyra-resource" }
lyra-game = { path = "../lyra-game" } lyra-game = { path = "../lyra-game" }
thiserror = "1.0.50" thiserror = "1.0.50"
@ -19,8 +21,10 @@ anyhow = "1.0.77"
tracing = "0.1.37" tracing = "0.1.37"
# enabled with lua feature # enabled with lua feature
mlua = { version = "0.9.2", features = ["lua54"], optional = true } # luajit maybe? #mlua = { version = "0.9.2", features = ["lua54"], optional = true } # luajit maybe?
elua = { path = "./elua", optional = true }
itertools = "0.12.0"
[dev-dependencies] [dev-dependencies]
tracing-subscriber = { version = "0.3.16", features = [ "tracing-log" ] } tracing-subscriber = { version = "0.3.16", features = [ "tracing-log" ] }

1
lyra-scripting/elua Submodule

@ -0,0 +1 @@
Subproject commit 54c9926a04cdef657289fd67730c0b85d1bdda3e

View File

@ -0,0 +1,14 @@
[package]
name = "lyra-scripting-derive"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
proc-macro = true
[dependencies]
proc-macro2 = "1.0.70"
quote = "1.0.33"
syn = "2.0.41"

View File

@ -0,0 +1,723 @@
use proc_macro2::{Ident, Span};
use quote::quote;
use syn::{parse_macro_input, Path, Token, token, parenthesized, punctuated::Punctuated, braced};
mod mat_wrapper;
use mat_wrapper::MatWrapper;
const FN_NAME_INTERNAL_REFLECT_TYPE: &str = "__lyra_internal_reflect_type";
const FN_NAME_INTERNAL_REFLECT: &str = "__lyra_internal_reflect";
pub(crate) struct MetaMethod {
pub ident: Ident,
pub mods: Vec<Ident>,
}
impl syn::parse::Parse for MetaMethod {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let ident: Ident = input.parse()?;
let mods = if input.peek(token::Paren) {
let content;
let _parens: token::Paren = parenthesized!(content in input);
content.parse_terminated(Ident::parse, Token![,])?
.into_iter().collect()
} else { vec![] };
Ok(Self {
ident,
mods,
})
}
}
impl MetaMethod {
/// Returns a boolean if an identifier is a lua wrapper, and therefore also userdata
fn is_arg_wrapper(ident: &Ident) -> bool {
let s = ident.to_string();
s.starts_with("Lua")
}
/// Returns a boolean indiciating if the metamethod has takes in any arguments
fn does_metamethod_have_arg(metamethod: &Ident) -> bool {
let mm_str = metamethod.to_string();
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
},
_ => 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 {
let mm_str = metamethod.to_string();
let mm_str = mm_str.as_str();
match mm_str {
"Add" | "Sub" | "Div" | "Mul" | "Mod" => {
let symbol = match mm_str {
"Add" => quote!(+),
"Sub" => quote!(-),
"Div" => quote!(/),
"Mul" => quote!(*),
"Mod" => quote!(%),
_ => unreachable!(), // the string was just checked to be one of these
};
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))
}
}
"Shr" => {
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
};
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(),
}
}
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! {
#arg_param.0
}
} else {
quote! {
#arg_param
}
};
Self::get_method_body(&mt_ident, other)
}
pub fn to_tokens(&self, wrapper_ident: &Ident) -> proc_macro2::TokenStream {
let wrapped_str = &wrapper_ident.to_string()[3..]; // removes starting 'Lua' from name
let mt_ident = &self.ident;
let mt_lua_name = mt_ident.to_string().to_lowercase();
if self.mods.is_empty() {
let other = quote! {
v.0
};
let body = Self::get_method_body(&self.ident, other);
if Self::does_metamethod_have_arg(&self.ident) {
quote! {
builder.meta_method(elua::MetaMethod::#mt_ident, |_, this, (v,): (#wrapper_ident,)| {
#body
});
}
} else {
quote! {
builder.meta_method(elua::MetaMethod::#mt_ident, |_, this, ()| {
#body
});
}
}
} else if self.mods.len() == 1 {
let first = self.mods.iter().next().unwrap();
let body = Self::get_body_for_arg(&self.ident, first, quote!(v));
quote! {
builder.meta_method(elua::MetaMethod::#mt_ident, |_, this, (v,): (#first,)| {
#body
});
}
} else {
// an optional match arm that matches elua::Value:Number
let number_arm = {
let num_ident = self.mods.iter().find(|i| {
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,
_ => false,
}
});
if let Some(num_ident) = num_ident {
let body = Self::get_body_for_arg(&self.ident, num_ident, quote!(n as #num_ident));
quote! {
elua::Value::Number(n) => {
#body
},
}
} else { quote!() }
};
let userdata_arm = {
let wrappers: Vec<&Ident> = self.mods.iter()
.filter(|i| Self::is_arg_wrapper(i))
.collect();
let if_statements = wrappers.iter().map(|i| {
let body = Self::get_method_body(&self.ident, quote!(other.0));
quote! {
if let Ok(other) = ud.as_ref::<#i>() {
#body
}
}
});
quote! {
elua::Value::Userdata(ud) => {
#(#if_statements else)*
// this is the body of the else statement
{
// try to get the name of the userdata for the error message
if let Ok(mt) = ud.get_metatable() {
if let Ok(name) = mt.get::<_, String>("__name") {
return Err(elua::Error::BadArgument {
func: Some(format!("{}.__{}", #wrapped_str, #mt_lua_name)),
arg_index: 2,
arg_name: Some("rhs".to_string()),
error: Arc::new(elua::Error::Runtime(
format!("cannot multiply with unknown userdata named {}", name)
))
});
}
}
Err(elua::Error::BadArgument {
func: Some(format!("{}.__{}", #wrapped_str, #mt_lua_name)),
arg_index: 2,
arg_name: Some("rhs".to_string()),
error: Arc::new(
elua::Error::runtime("cannot multiply with unknown userdata")
)
})
}
},
}
};
quote! {
builder.meta_method(elua::MetaMethod::#mt_ident, |_, this, (v,): (elua::Value,)| {
match v {
#number_arm
#userdata_arm
_ => Err(elua::Error::BadArgument {
func: Some(format!("{}.__{}", #wrapped_str, #mt_lua_name)),
arg_index: 2,
arg_name: Some("rhs".to_string()),
error: Arc::new(
elua::Error::Runtime(format!("cannot multiply with {}", v.type_name()))
)
})
}
});
}
}
}
}
pub(crate) struct VecWrapper {
}
impl VecWrapper {
fn vec_size(&self, wrapper_ident: &Ident) -> usize {
let name = wrapper_ident.to_string();
name[name.len() - 1..].parse::<usize>()
.or_else(|_| name[name.len() - 2.. name.len() - 1].parse::<usize>())
.expect("Failure to grab Vec size from ident name")
}
/// Returns the token stream of the type of the axis of the vec (Vec2 vs IVec2 vs I64Vec2, etc.)
fn vec_axis_type(&self, wrapper_ident: &Ident) -> &'static str {
let name = wrapper_ident.to_string();
let start = name.find("Vec").unwrap();
let before = &name[start - 1.. start];
match before {
"D" => return "f64",
"I" => return "i32",
"U" => return "u32",
"B" => return "bool",
_ => {},
}
//println!("before is {before}");
let three_before = &name[start - 3.. start];
match three_before {
"I64" => return "i64",
"U64" => return "u64",
_ => {},
}
//println!("three before is {three_before}");
"f32"
}
pub fn to_field_tokens(&self, wrapped_path: &Path, wrapper_ident: &Ident) -> proc_macro2::TokenStream {
let mut consts = vec![quote!(ZERO), quote!(ONE), quote!(X),
quote!(Y), ]; // , quote!(AXES)
let vec_size = self.vec_size(wrapper_ident);
let axis_type_name = self.vec_axis_type(wrapper_ident);
if axis_type_name.contains("b") {
return quote! {
builder.field("FALSE", #wrapper_ident(#wrapped_path::FALSE));
builder.field("TRUE", #wrapper_ident(#wrapped_path::TRUE));
};
}
if vec_size >= 3 {
consts.push(quote!(Z));
// no negative numbers for unsigned vecs
if !axis_type_name.contains("u") {
consts.push(quote!(NEG_Z));
}
}
if vec_size == 4 {
consts.push(quote!(W));
// no negative numbers for unsigned vecs
if !axis_type_name.contains("u") {
consts.push(quote!(NEG_W));
}
}
// no negative numbers for unsigned vecs
if !axis_type_name.contains("u") {
consts.push(quote!(NEG_X));
consts.push(quote!(NEG_Y));
consts.push(quote!(NEG_ONE));
}
if axis_type_name.contains("f") {
consts.push(quote!(NAN))
}
let const_tokens = consts.iter().map(|cnst| {
let const_name = cnst.to_string();
quote! {
builder.field(#const_name, #wrapper_ident(#wrapped_path::#cnst));
}
});
quote! {
#(#const_tokens)*
}
}
pub fn to_method_tokens(&self, wrapped_path: &Path, wrapper_ident: &Ident) -> proc_macro2::TokenStream {
let vec_size = self.vec_size(wrapper_ident);
let axis_type_name = self.vec_axis_type(wrapper_ident);
// methods that only some vecs have
let mut optional_methods = vec![];
// boolean vectors dont have much :(
if axis_type_name.contains("b") {
return quote!(); // TODO: all, any, bitmask, splat
}
if axis_type_name.contains("f") {
let type_id = Ident::new(axis_type_name, Span::call_site());
optional_methods.push(
quote! {
builder.method("clamp_length",
|_, this, (min, max): (#type_id, #type_id)| {
Ok(#wrapper_ident(this.clamp_length(min, max)))
});
builder.method("abs_diff_eq",
|_, this, (rhs, max_abs_diff): (#wrapper_ident, #type_id)| {
Ok(this.abs_diff_eq(rhs.0, max_abs_diff))
});
builder.method("ceil",
|_, this, (): ()| {
Ok(#wrapper_ident(this.ceil()))
});
}
);
if vec_size != 4 {
optional_methods.push(
quote! {
builder.method("angle_between",
|_, this, (rhs,): (#wrapper_ident,)| {
Ok(this.angle_between(rhs.0))
});
}
)
}
}
if !axis_type_name.contains("u") {
optional_methods.push(
quote! {
builder.method("abs",
|_, this, (): ()| {
Ok(#wrapper_ident(this.abs()))
});
}
)
}
let optional_methods_iter = optional_methods.iter();
quote! {
builder.method("clamp",
|_, this, (min, max): (#wrapper_ident, #wrapper_ident)| {
Ok(#wrapper_ident(this.clamp(min.0, max.0)))
});
// TODO: Not all Vecs have this
/* builder.method("clamp_length",
|_, this, (min, max): (f32, f32)| {
Ok(#wrapper_ident(this.clamp_length(min, max)))
}); */
builder.method("to_array",
|_, this, (): ()| {
Ok(this.to_array())
});
#(#optional_methods_iter)*
}
}
}
pub(crate) struct WrapUsage {
pub type_path: Path,
/// The extra derives of the type.
pub derive_idents: Punctuated<Ident, Token![,]>,
/// The field idents of the type that will be exposed with gets and sets
pub field_idents: Punctuated<Ident, Token![,]>,
pub skip_new_fn: bool,
/// The identifiers that are taken as parameters in the types 'new' function
pub new_fn_idents: Punctuated<Ident, Token![,]>,
pub meta_method_idents: Punctuated<MetaMethod, Token![,]>,
pub matrix: Option<MatWrapper>,
pub vec: Option<VecWrapper>,
pub custom_methods: Option<syn::Block>,
pub custom_fields: Option<syn::Block>,
}
impl syn::parse::Parse for WrapUsage {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let type_path: Path = input.parse()?;
let mut s = Self {
type_path,
derive_idents: Punctuated::default(),
field_idents: Punctuated::default(),
skip_new_fn: false,
new_fn_idents: Punctuated::default(),
meta_method_idents: Punctuated::default(),
matrix: None,
vec: None,
custom_methods: None,
custom_fields: None,
};
/* let mut derive_idents = None;
let mut field_idents = None;
let mut new_fn_idents = None; */
while input.peek(Token![,]) {
let _: Token![,] = input.parse()?;
//println!("Peeked a , ({:?})", input);
if input.peek(syn::Ident) {
let ident: Ident = input.parse()?;
let ident_str = ident.to_string();
let ident_str = ident_str.as_str();
match ident_str {
"derives" => {
if input.peek(token::Paren) {
let content;
let _parens: token::Paren = parenthesized!(content in input);
let derives: Punctuated<Ident, Token![,]> = content.parse_terminated(Ident::parse, Token![,])?;
s.derive_idents = derives;
//println!("read derives: {:?}", s.derive_idents);
}
},
"fields" => {
if input.peek(token::Paren) {
let content;
let _parens: token::Paren = parenthesized!(content in input);
let fields: Punctuated<Ident, Token![,]> = content.parse_terminated(Ident::parse, Token![,])?;
s.field_idents = fields;
//println!("read fields: {:?}", s.field_idents);
}
},
"new" => {
if input.peek(token::Paren) {
let content;
let _parens: token::Paren = parenthesized!(content in input);
let fields: Punctuated<Ident, Token![,]> = content.parse_terminated(Ident::parse, Token![,])?;
s.new_fn_idents = fields;
//println!("read fields: {:?}", s.new_fn_idents);
}
},
"no_new" => {
s.skip_new_fn = true;
},
"matrix" => {
if input.peek(token::Brace) {
let content;
let _braces = braced!(content in input);
s.matrix = Some(content.parse()?);
}
},
"metamethods" => {
if input.peek(token::Paren) {
let content;
let _bracket: token::Paren = parenthesized!(content in input);
let meta_methods: Punctuated<MetaMethod, Token![,]> = content.parse_terminated(MetaMethod::parse, Token![,])?;
s.meta_method_idents = meta_methods;
}
},
"custom_methods" => {
let methods_block = input.parse()?;
s.custom_methods = Some(methods_block);
},
"custom_fields" => {
let block = input.parse()?;
s.custom_fields = Some(block);
}
_ => {
return Err(syn::Error::new_spanned(ident, "unknown wrapper command"));
}
}
}
}
Ok(s)
}
}
/// Creates a wrapper type for a VecN from the engine math library.
#[proc_macro]
pub fn wrap_math_vec_copy(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as WrapUsage);
let path: Path = input.type_path;
let type_name = &path.segments.last()
.expect("Failure to find typename in macro usage!")
.ident;
let wrapper_typename = Ident::new(&format!("Lua{}", type_name), Span::call_site());
let vec_wrapper = {
let name_str = type_name.to_string();
if name_str.contains("Vec") {
Some(VecWrapper {})
} else {
None
}
};
// TODO: fix this so it doesn't cause a stack overflow
/* let vec_wrapper_fields = vec_wrapper.as_ref().map(|vec|
vec.to_field_tokens(&path, &wrapper_typename)); */
let vec_wrapper_fields: Option<proc_macro2::TokenStream> = None;
let vec_wrapper_methods = vec_wrapper.as_ref().map(|vec|
vec.to_method_tokens(&path, &wrapper_typename));
let derive_idents_iter = input.derive_idents.iter();
let custom_methods = input.custom_methods;
let custom_fields = input.custom_fields;
let field_get_set_pairs = input.field_idents.iter().map(|i| {
let is = i.to_string();
quote! {
builder.field_getter(#is, |_, this| {
Ok(this.#i)
});
builder.field_setter(#is, |_, this, #i| {
this.#i = #i;
Ok(())
});
}
});
let new_fn_idents = {
let idents = if input.new_fn_idents.is_empty() {
input.field_idents.iter()
} else {
input.new_fn_idents.iter()
};
let idents_c = idents.clone();
if !input.skip_new_fn {
quote! {
builder.function("new", |_, ( #(#idents_c),* )| {
Ok(#wrapper_typename(#path::new( #(#idents),* )))
});
}
} else { quote!() }
};
let matrix_wrapper_methods = input.matrix.as_ref().map(|m|
m.to_method_tokens(&path, &wrapper_typename));
let matrix_wrapper_fields = input.matrix.as_ref().map(|m|
m.to_field_tokens(&path, &wrapper_typename));
let meta_method_idents = {
let idents = input.meta_method_idents.iter().map(|metamethod| {
metamethod.to_tokens(&wrapper_typename)
});
quote! {
#(#idents)*
}
};
proc_macro::TokenStream::from(quote! {
#[derive(Clone, Copy, lyra_reflect::Reflect, #(#derive_idents_iter),*)]
pub struct #wrapper_typename(#[reflect(skip)] pub(crate) #path);
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
}
}
impl<'lua> elua::FromLua<'lua> for #wrapper_typename {
fn from_lua(_lua: &'lua elua::State, value: elua::Value<'lua>) -> elua::Result<Self> {
match value {
elua::Value::Userdata(ud) => Ok(*ud.as_ref::<Self>()?),
_ => panic!("Attempt to get {} from a {} value", stringify!(#wrapper_typename), value.type_name()),
}
}
}
impl elua::Userdata for #wrapper_typename {
fn name() -> String {
stringify!(#type_name).to_string()
}
fn build<'a>(builder: &mut elua::UserdataBuilder<'a, Self>) {
#(#field_get_set_pairs)*
#matrix_wrapper_fields
#vec_wrapper_fields
#custom_fields
#new_fn_idents
builder.method(#FN_NAME_INTERNAL_REFLECT, |_, this, ()| {
Ok(crate::ScriptBorrow::from_component::<#path>(Some(this.0.clone())))
});
builder.function(#FN_NAME_INTERNAL_REFLECT_TYPE, |_, ()| {
Ok(crate::ScriptBorrow::from_component::<#path>(None))
});
#meta_method_idents
#matrix_wrapper_methods
#vec_wrapper_methods
#custom_methods
}
}
impl lyra_scripting::lua::LuaWrapper for #wrapper_typename {
fn wrapped_type_id() -> std::any::TypeId {
let t = std::any::TypeId::of::<#path>();
println!("Got id of {}, it is {:?}", stringify!(#path), t);
t
}
}
impl<'a> elua::FromLuaVec<'a> for #wrapper_typename {
fn from_lua_value_vec(state: &'a elua::State, mut values: elua::ValueVec<'a>) -> elua::Result<Self> {
use elua::FromLua;
if let Some(val) = values.pop_front() {
#wrapper_typename::from_lua(state, val)
} else {
Err(elua::Error::IncorrectArgCount {
arg_expected: 1,
arg_count: values.len() as i32,
})
}
}
}
})
}

View File

@ -0,0 +1,142 @@
use proc_macro2::Ident;
use quote::quote;
use syn::{Path, Token};
pub(crate) struct MatWrapper {
pub column_type: Ident,
}
impl MatWrapper {
pub fn to_field_tokens(&self, wrapped_path: &Path, wrapper_ident: &Ident) -> proc_macro2::TokenStream {
quote! {
builder.field("ZERO", #wrapper_ident(#wrapped_path::ZERO));
builder.field("IDENTITY", #wrapper_ident(#wrapped_path::IDENTITY));
builder.field("NAN", #wrapper_ident(#wrapped_path::NAN));
}
}
pub fn to_method_tokens(&self, wrapped_path: &Path, wrapper_ident: &Ident) -> proc_macro2::TokenStream {
let column_type = &self.column_type;
let column_size = {
let ty_str = column_type.to_string();
ty_str[ty_str.len() - 1..].parse::<usize>()
.expect("Failure to parse number from token type")
};
let column_size_xtwo = column_size * 2;
let element_ty = quote!(f32);
quote! {
builder.function("from_cols",
|_, (x_axis, y_axis): (#column_type, #column_type)| {
Ok(#wrapper_ident(#wrapped_path::from_cols(x_axis.0, y_axis.0)))
});
builder.function("from_cols_array",
|_, (arr,): ([#element_ty; #column_size_xtwo],)| {
Ok(#wrapper_ident(#wrapped_path::from_cols_array(&arr)))
});
builder.function("from_cols_array_2d",
|_, (arr,): ([[#element_ty; #column_size]; #column_size],)| {
Ok(#wrapper_ident(#wrapped_path::from_cols_array_2d(&arr)))
});
builder.function("from_diagonal",
|_, (diag,): (#column_type,)| {
Ok(#wrapper_ident(#wrapped_path::from_diagonal(diag.0)))
});
builder.method("col",
|_, this, (idx,): (usize,)| {
Ok(#column_type(this.col(idx)))
});
builder.method("row",
|_, this, (idx,): (usize,)| {
Ok(#column_type(this.row(idx)))
});
builder.method_mut("set_col",
|_, this, (idx, newc): (usize, #column_type)| {
let col = this.col_mut(idx);
*col = newc.0;
Ok(())
});
builder.method("is_finite",
|_, this, (): ()| {
Ok(this.is_finite())
});
builder.method("is_nan",
|_, this, (): ()| {
Ok(this.is_nan())
});
builder.method("transpose",
|_, this, (): ()| {
Ok(#wrapper_ident(this.0.transpose()))
});
builder.method("determinant",
|_, this, (): ()| {
Ok(this.determinant())
});
builder.method("inverse",
|_, this, (): ()| {
Ok(#wrapper_ident(this.inverse()))
});
builder.method("abs_diff_eq",
|_, this, (rhs, max_abs_diff): (#wrapper_ident, f32)| {
Ok(this.abs_diff_eq(rhs.0, max_abs_diff))
});
// TODO: After all DMat's are implemented
/* builder.method("as_dmat",
|_, this, (rhs, max_abs_diff): (#wrapper_ident, f32)| {
Ok(D#wrapper_ident(this.as_dmat))
}); */
}
}
}
impl syn::parse::Parse for MatWrapper {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let mut column_type = None;
// cba to remove the use of this bool
let mut first = true;
while input.peek(Token![,]) || first {
if !first {
let _: Token![,] = input.parse()?;
}
if input.peek(syn::Ident) {
let ident: Ident = input.parse()?;
let ident_str = ident.to_string();
let ident_str = ident_str.as_str();
match ident_str {
"col_type" => {
let _eq: Token![=] = input.parse()?;
column_type = Some(input.parse()?);
},
_ => return Err(syn::Error::new_spanned(ident, "unknown matrix wrapper command")),
}
}
first = false;
}
Ok(Self {
column_type: column_type.ok_or_else(|| syn::Error::new(input.span(),
"expected `col_type`"))?,
})
}
}

View File

View File

@ -0,0 +1,182 @@
---@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

View File

@ -0,0 +1,95 @@
---@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

View File

@ -0,0 +1,187 @@
---@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

View File

@ -1,6 +1,6 @@
use std::collections::HashMap; use std::collections::HashMap;
use lyra_ecs::ResourceObject; use lyra_ecs::{ResourceObject, Entity, World};
use crate::ScriptWorldPtr; use crate::ScriptWorldPtr;
@ -8,15 +8,15 @@ use crate::ScriptWorldPtr;
pub enum ScriptError { pub enum ScriptError {
#[error("{0}")] #[error("{0}")]
#[cfg(feature = "lua")] #[cfg(feature = "lua")]
MluaError(mlua::Error), MluaError(elua::Error),
#[error("{0}")] #[error("{0}")]
Other(anyhow::Error), Other(anyhow::Error),
} }
#[cfg(feature = "lua")] #[cfg(feature = "lua")]
impl From<mlua::Error> for ScriptError { impl From<elua::Error> for ScriptError {
fn from(value: mlua::Error) -> Self { fn from(value: elua::Error) -> Self {
ScriptError::MluaError(value) ScriptError::MluaError(value)
} }
} }
@ -29,8 +29,12 @@ impl From<anyhow::Error> for ScriptError {
#[derive(Clone, Hash, PartialEq, Eq)] #[derive(Clone, Hash, PartialEq, Eq)]
pub struct ScriptData { pub struct ScriptData {
/// The script id
pub script_id: u64, pub script_id: u64,
/// The name of the script
pub name: String, pub name: String,
/// The entity that this script exists on
pub entity: Entity,
} }
/// Provides an API to a scripting context. /// Provides an API to a scripting context.
@ -38,12 +42,15 @@ pub trait ScriptApiProvider {
/// The type used as the script's context. /// The type used as the script's context.
type ScriptContext; type ScriptContext;
/// Exposes an API in the provided script context. /// Prepare the ECS world for this api. Things like registering types with the type registry happen here.
fn expose_api(&mut self, ctx: &mut Self::ScriptContext) -> Result<(), ScriptError>; fn prepare_world(&mut self, world: &mut World) {
let _ = world; // remove compiler warning
}
/// Create a script in the script host. /// Exposes an API in the provided script context.
/// fn expose_api(&mut self, data: &ScriptData, ctx: &mut Self::ScriptContext) -> Result<(), ScriptError>;
/// This only creates the script for the host, it does not setup the script for execution. See [`ScriptHostProvider::setup_script`].
/// Setup a script right before its 'init' method is called.
fn setup_script(&mut self, data: &ScriptData, ctx: &mut Self::ScriptContext) -> Result<(), ScriptError>; fn setup_script(&mut self, data: &ScriptData, ctx: &mut Self::ScriptContext) -> Result<(), ScriptError>;
/// A function that is used to update a script's environment. /// A function that is used to update a script's environment.
@ -74,13 +81,18 @@ pub trait ScriptHost: Default + ResourceObject {
/// Loads a script and creates a context for it. /// Loads a script and creates a context for it.
/// ///
/// Before the script is executed, the API providers are exposed to the script. /// Before the script is executed, the API providers are exposed to the script.
fn load_script(&mut self, script: &[u8], script_data: &ScriptData, providers: &mut crate::ScriptApiProviders<Self>) -> Result<Self::ScriptContext, ScriptError>; fn load_script(&mut self, script: &[u8], script_data: &ScriptData,
providers: &mut crate::ScriptApiProviders<Self>)
-> Result<Self::ScriptContext, ScriptError>;
/// Setup a script for execution. /// Setup a script for execution.
fn setup_script(&mut self, script_data: &ScriptData, ctx: &mut Self::ScriptContext, providers: &mut ScriptApiProviders<Self>) -> Result<(), ScriptError>; fn setup_script(&mut self, script_data: &ScriptData, ctx: &mut Self::ScriptContext,
providers: &mut ScriptApiProviders<Self>) -> Result<(), ScriptError>;
/// Executes the update step for the script. /// Calls a event function in the script.
fn update_script(&mut self, world: ScriptWorldPtr, script_data: &crate::ScriptData, ctx: &mut Self::ScriptContext, providers: &mut crate::ScriptApiProviders<Self>) -> Result<(), ScriptError>; fn call_script(&mut self, world: ScriptWorldPtr, script_data: &crate::ScriptData,
ctx: &mut Self::ScriptContext, providers: &mut crate::ScriptApiProviders<Self>,
event_fn_name: &str) -> Result<(), ScriptError>;
} }
#[derive(Default)] #[derive(Default)]
@ -110,4 +122,12 @@ impl<T> ScriptContexts<T> {
pub fn has_context(&self, script_id: u64) -> bool { pub fn has_context(&self, script_id: u64) -> bool {
self.contexts.contains_key(&script_id) self.contexts.contains_key(&script_id)
} }
pub fn remove_context(&mut self, script_id: u64) -> Option<T> {
self.contexts.remove(&script_id)
}
pub fn len(&self) -> usize {
self.contexts.len()
}
} }

View File

@ -2,6 +2,9 @@
pub mod lua; pub mod lua;
pub mod world; pub mod world;
use std::any::TypeId;
use lyra_ecs::{Component, ResourceObject};
pub use world::*; pub use world::*;
pub mod wrap; pub mod wrap;
@ -15,16 +18,19 @@ pub use script::*;
use lyra_game::game::Game; use lyra_game::game::Game;
// required for some proc macros :(
#[allow(unused_imports)] #[allow(unused_imports)]
pub(crate) mod lyra_engine { pub(crate) mod lyra_engine {
pub use lyra_ecs as ecs; pub use lyra_ecs as ecs;
pub use lyra_reflect as reflect;
} }
use lyra_reflect::{ReflectedComponent, Reflect}; use lyra_reflect::{ReflectedComponent, Reflect, FromType, ReflectedResource};
#[derive(Clone)] #[derive(Clone)]
pub enum ReflectBranch { pub enum ReflectBranch {
Component(ReflectedComponent), Component(ReflectedComponent),
Resource(ReflectedResource),
} }
impl ReflectBranch { impl ReflectBranch {
@ -35,18 +41,51 @@ impl ReflectBranch {
pub fn as_component_unchecked(&self) -> &ReflectedComponent { pub fn as_component_unchecked(&self) -> &ReflectedComponent {
match self { match self {
ReflectBranch::Component(c) => c, ReflectBranch::Component(c) => c,
//_ => panic!("`self` is not an instance of `ReflectBranch::Component`") _ => panic!("`self` is not an instance of `ReflectBranch::Component`")
} }
} }
/// Returns a boolean indicating if `self` is a reflection of a Component.
pub fn is_component(&self) -> bool { pub fn is_component(&self) -> bool {
matches!(self, ReflectBranch::Component(_)) matches!(self, ReflectBranch::Component(_))
} }
/// Gets self as a [`ReflectedResource`].
///
/// # Panics
/// If `self` is not a variant of [`ReflectBranch::Resource`].
pub fn as_resource_unchecked(&self) -> &ReflectedResource {
match self {
ReflectBranch::Resource(v) => v,
_ => panic!("`self` is not an instance of `ReflectBranch::Component`")
}
}
/// Gets self as a [`ReflectedResource`], returning `None` if self is not an instance of it.
pub fn as_resource(&self) -> Option<&ReflectedResource> {
match self {
ReflectBranch::Resource(v) => Some(v),
_ => None
}
}
/// Returns a boolean indicating if `self` is a reflection of a Resource.
pub fn is_resource(&self) -> bool {
matches!(self, ReflectBranch::Resource(_))
}
/// Returns the type id of the reflected thing
pub fn reflect_type_id(&self) -> TypeId {
match self {
ReflectBranch::Component(c) => c.type_id,
ReflectBranch::Resource(r) => r.type_id,
}
}
} }
pub struct ScriptBorrow { pub struct ScriptBorrow {
reflect_branch: ReflectBranch, pub(crate) reflect_branch: ReflectBranch,
data: Option<Box<dyn Reflect>>, pub(crate) data: Option<Box<dyn Reflect>>,
} }
impl Clone for ScriptBorrow { impl Clone for ScriptBorrow {
@ -60,8 +99,37 @@ impl Clone for ScriptBorrow {
} }
} }
impl ScriptBorrow {
/// Creates a ScriptBorrow from a Component
pub fn from_component<T>(data: Option<T>) -> Self
where
T: Reflect + Component + 'static
{
let data = data.map(|d| Box::new(d) as Box<(dyn Reflect + 'static)>);
Self {
reflect_branch: ReflectBranch::Component(<ReflectedComponent as FromType<T>>::from_type()),
data,
}
}
/// Creates a ScriptBorrow from a Resource.
pub fn from_resource<T>(data: Option<T>) -> Self
where
T: Reflect + ResourceObject + 'static
{
let data = data.map(|d| Box::new(d) as Box<(dyn Reflect + 'static)>);
Self {
reflect_branch: ReflectBranch::Resource(<ReflectedResource as FromType<T>>::from_type()),
data,
}
}
}
/// An extension trait that adds some helpful methods that makes it easier to do scripting things /// An extension trait that adds some helpful methods that makes it easier to do scripting things
pub trait GameScriptExt { pub trait GameScriptExt {
/// A helper method for adding a ScriptApiProvider into the world.
fn add_script_api_provider<T, P>(&mut self, provider: P) fn add_script_api_provider<T, P>(&mut self, provider: P)
where where
T: ScriptHost, T: ScriptHost,
@ -69,12 +137,13 @@ pub trait GameScriptExt {
} }
impl GameScriptExt for Game { impl GameScriptExt for Game {
fn add_script_api_provider<T, P>(&mut self, provider: P) fn add_script_api_provider<T, P>(&mut self, mut provider: P)
where where
T: ScriptHost, T: ScriptHost,
P: ScriptApiProvider<ScriptContext = T::ScriptContext> + 'static P: ScriptApiProvider<ScriptContext = T::ScriptContext> + 'static
{ {
let world = self.world(); let world = self.world_mut();
provider.prepare_world(world);
let mut providers = world.get_resource_mut::<ScriptApiProviders<T>>(); let mut providers = world.get_resource_mut::<ScriptApiProviders<T>>();
providers.add_provider(provider); providers.add_provider(provider);
} }

View File

@ -1,6 +1,6 @@
use std::{ptr::NonNull, ops::{Range, Deref}}; use std::{ptr::NonNull, ops::{Range, Deref}};
use lyra_ecs::{ComponentColumn, ComponentInfo, Archetype, ArchetypeId, ArchetypeEntityId, query::dynamic::{DynamicType, QueryDynamicType}, query::Fetch}; use lyra_ecs::{ComponentColumn, ComponentInfo, Archetype, ArchetypeId, ArchetypeEntityId, query::dynamic::{DynamicType, QueryDynamicType}, query::Fetch, Entity};
use lyra_reflect::TypeRegistry; use lyra_reflect::TypeRegistry;
#[cfg(feature = "lua")] #[cfg(feature = "lua")]
@ -45,6 +45,11 @@ impl<'a> From<lyra_ecs::query::dynamic::FetchDynamicType<'a>> for FetchDynamicTy
} }
} }
pub struct DynamicViewRow {
entity: Entity,
item: Vec<DynamicType>,
}
#[derive(Clone)] #[derive(Clone)]
pub struct DynamicViewIter { pub struct DynamicViewIter {
world_ptr: ScriptWorldPtr, world_ptr: ScriptWorldPtr,
@ -69,15 +74,15 @@ impl<'a> From<lyra_ecs::query::dynamic::DynamicViewIter<'a>> for DynamicViewIter
} }
impl Iterator for DynamicViewIter { impl Iterator for DynamicViewIter {
type Item = Vec<DynamicType>; type Item = DynamicViewRow;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
loop { loop {
if let Some(entity_index) = self.component_indices.next() { if let Some(entity_index) = self.component_indices.next() {
let mut fetch_res = vec![]; let mut fetch_res = vec![];
let entity_index = ArchetypeEntityId(entity_index);
for fetcher in self.fetchers.iter_mut() { for fetcher in self.fetchers.iter_mut() {
let entity_index = ArchetypeEntityId(entity_index);
if !fetcher.can_visit_item(entity_index) { if !fetcher.can_visit_item(entity_index) {
break; break;
} else { } else {
@ -90,7 +95,14 @@ impl Iterator for DynamicViewIter {
continue; continue;
} }
return Some(fetch_res); let arch = unsafe { self.archetypes.get_unchecked(self.next_archetype - 1).as_ref() };
let entity = arch.entity_of_index(entity_index).unwrap();
let row = DynamicViewRow {
entity,
item: fetch_res,
};
return Some(row);
} else { } else {
if self.next_archetype >= self.archetypes.len() { if self.next_archetype >= self.archetypes.len() {
return None; // ran out of archetypes to go through return None; // ran out of archetypes to go through
@ -108,7 +120,6 @@ impl Iterator for DynamicViewIter {
continue; continue;
} }
//let world = unsafe { self.world_ptr.as_ref() };
let world = self.world_ptr.as_ref(); let world = self.world_ptr.as_ref();
self.fetchers = self.queries.iter() self.fetchers = self.queries.iter()
@ -121,6 +132,19 @@ impl Iterator for DynamicViewIter {
} }
} }
#[cfg(feature = "lua")]
pub struct ReflectedItem<'a> {
//pub proxy: &'a ReflectLuaProxy,
pub comp_ptr: NonNull<u8>,
pub comp_val: elua::Value<'a>,
}
#[cfg(feature = "lua")]
pub struct ReflectedRow<'a> {
pub entity: Entity,
pub row: Vec<ReflectedItem<'a>>,
}
pub struct ReflectedIterator { pub struct ReflectedIterator {
pub world: ScriptWorldPtr, pub world: ScriptWorldPtr,
pub dyn_view: DynamicViewIter, pub dyn_view: DynamicViewIter,
@ -128,10 +152,9 @@ pub struct ReflectedIterator {
} }
impl ReflectedIterator { impl ReflectedIterator {
#[cfg(feature = "lua")] #[cfg(feature = "lua")]
pub fn next_lua<'a>(&mut self, lua: &'a mlua::Lua) -> Option<Vec<( (&'a ReflectLuaProxy, NonNull<u8>), mlua::AnyUserData<'a>)>> { pub fn next_lua<'a>(&mut self, lua: &'a elua::State) -> Option<ReflectedRow<'a>> {
use elua::AsLua;
let n = self.dyn_view.next(); let n = self.dyn_view.next();
@ -142,23 +165,32 @@ impl ReflectedIterator {
.map(|r| NonNull::from(r.deref())); .map(|r| NonNull::from(r.deref()));
} }
let mut dynamic_row = Vec::new(); let mut dynamic_row = vec![];
for d in row.iter() { for d in row.item.iter() {
let id = d.info.type_id.as_rust(); let id = d.info.type_id.as_rust();
let reflected_components = let reflected_components =
unsafe { self.reflected_components.as_ref().unwrap().as_ref() }; unsafe { self.reflected_components.as_ref().unwrap().as_ref() };
let reg_type = reflected_components.get_type(id) let reg_type = reflected_components.get_type(id)
.expect("Could not find type for dynamic view!"); .expect("Requested type was not found in TypeRegistry");
let proxy = reg_type.get_data::<ReflectLuaProxy>() let proxy = reg_type.get_data::<ReflectLuaProxy>()
// TODO: properly handle this error
.expect("Type does not have ReflectLuaProxy as a TypeData"); .expect("Type does not have ReflectLuaProxy as a TypeData");
let value = (proxy.fn_as_lua)(lua, d.ptr.cast()).unwrap()
let userdata = (proxy.fn_as_uservalue)(lua, d.ptr).unwrap(); .as_lua(lua).unwrap();
dynamic_row.push(( (proxy, d.ptr), userdata)); dynamic_row.push(ReflectedItem {
comp_ptr: d.ptr,
comp_val: value
});
} }
Some(dynamic_row) let row = ReflectedRow {
entity: row.entity,
row: dynamic_row
};
Some(row)
} else { } else {
None None
} }

View File

@ -1,6 +1,6 @@
use std::sync::Arc; use std::sync::Arc;
use lyra_resource::{ResourceLoader, Resource}; use lyra_resource::{ResourceLoader, ResHandle};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct LuaScript { pub struct LuaScript {
@ -13,7 +13,7 @@ pub struct LuaLoader;
impl ResourceLoader for LuaLoader { impl ResourceLoader for LuaLoader {
fn extensions(&self) -> &[&str] { fn extensions(&self) -> &[&str] {
&[".lua"] &["lua"]
} }
fn mime_types(&self) -> &[&str] { fn mime_types(&self) -> &[&str] {
@ -23,7 +23,7 @@ impl ResourceLoader for LuaLoader {
fn load(&self, _resource_manager: &mut lyra_resource::ResourceManager, path: &str) -> Result<std::sync::Arc<dyn lyra_resource::ResourceStorage>, lyra_resource::LoaderError> { fn load(&self, _resource_manager: &mut lyra_resource::ResourceManager, path: &str) -> Result<std::sync::Arc<dyn lyra_resource::ResourceStorage>, lyra_resource::LoaderError> {
let bytes = std::fs::read(path)?; let bytes = std::fs::read(path)?;
let s = Resource::with_data(path, LuaScript { let s = ResHandle::with_data(path, LuaScript {
bytes bytes
}); });
@ -34,7 +34,7 @@ impl ResourceLoader for LuaLoader {
let end = offset + length; let end = offset + length;
let bytes = bytes[offset..end].to_vec(); let bytes = bytes[offset..end].to_vec();
let s = Resource::with_data("from bytes", LuaScript { let s = ResHandle::with_data("from bytes", LuaScript {
bytes bytes
}); });

View File

@ -2,8 +2,7 @@ pub mod dynamic_iter;
pub use dynamic_iter::*; pub use dynamic_iter::*;
pub mod world; pub mod world;
use lyra_game::plugin::Plugin; use elua::FromLua;
use lyra_resource::ResourceManager;
pub use world::*; pub use world::*;
pub mod script; pub mod script;
@ -12,120 +11,174 @@ pub use script::*;
pub mod loader; pub mod loader;
pub use loader::*; pub use loader::*;
#[cfg(test)] pub mod providers;
mod test; pub mod wrappers;
use std::{ptr::NonNull, sync::Mutex}; pub mod proxy;
pub use proxy::*;
use lyra_ecs::DynamicBundle; pub mod system;
use lyra_reflect::{Reflect, RegisteredType, FromType, AsRegisteredType}; pub use system::*;
use mlua::{Lua, AnyUserDataExt}; use std::{any::TypeId, sync::Mutex};
use lyra_ecs::{
Component, ComponentInfo, World
};
use lyra_reflect::{Reflect, TypeRegistry};
use crate::ScriptBorrow;
pub type LuaContext = Mutex<elua::State>;
/// Name of a Lua function that is used to Reflect the Userdata, but without a value.
///
/// This is used for reflecting the userdata as an ECS Component or Resource. This **function**
/// returns a [`ScriptBorrow`] with data as `None`.
pub const FN_NAME_INTERNAL_REFLECT_TYPE: &str = "__lyra_internal_reflect_type"; pub const FN_NAME_INTERNAL_REFLECT_TYPE: &str = "__lyra_internal_reflect_type";
/// Name of a Lua function that is used to Reflect the Userdata.
///
/// This is used for reflecting the userdata as an ECS Component or Resource. This **method**
/// returns a [`ScriptBorrow`] with data as `Some`. **Anything that calls this expects the
/// method to return data**.
pub const FN_NAME_INTERNAL_REFLECT: &str = "__lyra_internal_reflect"; pub const FN_NAME_INTERNAL_REFLECT: &str = "__lyra_internal_reflect";
use crate::{ScriptBorrow, ScriptDynamicBundle, ScriptApiProviders, ScriptContexts}; /// Name of a Lua function implemented for Userdata types that can be made into components.
///
/// This is used for types that can be converted into components. When implementing this function,
/// you must return a [`ScriptBorrow`] that contains the component for this userdata.
/// You can return [`elua::Value::Nil`] if for some reason the type could not be converted
/// into a component.
///
/// A good example of this is `LuaResHandle`. The resource handle is requested from the
/// world, and could be a 3d model. The 3d model could then be manually wrapped as
/// [`LuaModelComponent`] with its `new` function. But for quality of life, this internal
/// function was created so that the userdata can be converted into its component
/// type without having to wrap it.
///
/// Without implementing this function:
/// ```lua
/// local cube = world:request_res("assets/cube-texture-embedded.gltf")
/// local cube_comp = ModelComponent.new(cube) -- annoying to write
///
/// local pos = Transform.from_translation(Vec3.new(0, 0, -8.0))
/// world:spawn(pos, cube_comp)
/// ```
///
/// With this function:
/// ```lua
/// local cube = world:request_res("assets/cube-texture-embedded.gltf")
/// local pos = Transform.from_translation(Vec3.new(0, 0, -8.0))
/// world:spawn(pos, cube)
/// ```
pub const FN_NAME_INTERNAL_AS_COMPONENT: &str = "__lyra_internal_refl_as_component";
impl<'lua> mlua::FromLua<'lua> for ScriptBorrow { /// A trait used for registering a Lua type with the world.
fn from_lua(value: mlua::Value<'lua>, _lua: &'lua Lua) -> mlua::Result<Self> { pub trait RegisterLuaType {
/// Register a type to Lua that **is not wrapped**.
fn register_lua_type<'a, T>(&mut self)
where
T: Reflect + LuaProxy + Clone + elua::FromLua<'a> + elua::Userdata;
/// Registers a type to Lua that is wrapped another type.
/// This would be used for something like `UserdataRef<T>`.
fn register_lua_wrapper<'a, W>(&mut self)
where
W: Reflect + LuaProxy + LuaWrapper + Clone + elua::FromLua<'a> + elua::Userdata;
/// Registers a type to Lua that can be converted into and from Lua types.
fn register_lua_convert<T>(&mut self)
where
T: Clone + for<'a> elua::FromLua<'a> + for<'a> elua::AsLua<'a> + LuaWrapper + 'static;
/// Registers a type to Lua that implements [`elua::TableProxy`]
fn register_lua_table_proxy<'a, T, W>(&mut self)
where
T: elua::TableProxy + 'static,
W: Component;
}
impl RegisterLuaType for World {
fn register_lua_type<'a, T>(&mut self)
where
T: Reflect + LuaProxy + Clone + elua::FromLua<'a> + elua::Userdata
{
let mut registry = self.get_resource_mut::<TypeRegistry>();
let type_id = TypeId::of::<T>();
let reg_type = registry.get_type_or_default(type_id);
reg_type.add_data(ReflectLuaProxy::from_lua_proxy::<T>());
}
fn register_lua_wrapper<'a, W>(&mut self)
where
W: Reflect + LuaProxy + LuaWrapper + Clone + elua::FromLua<'a> + elua::Userdata
{
let mut registry = self.get_resource_mut::<TypeRegistry>();
let reg_type = registry.get_type_or_default(W::wrapped_type_id());
reg_type.add_data(ReflectLuaProxy::from_lua_proxy::<W>());
}
fn register_lua_convert<T>(&mut self)
where
T: Clone + for<'a> elua::FromLua<'a> + for<'a> elua::AsLua<'a> + LuaWrapper + 'static,
{
let mut registry = self.get_resource_mut::<TypeRegistry>();
let reg_type = registry.get_type_or_default(T::wrapped_type_id());
reg_type.add_data(ReflectLuaProxy::from_as_and_from_lua::<T>());
}
fn register_lua_table_proxy<'a, T, C>(&mut self)
where
T: elua::TableProxy + 'static,
C: Component
{
let mut registry = self.get_resource_mut::<TypeRegistry>();
let reg_type = registry.get_type_or_default(TypeId::of::<C>());
reg_type.add_data(ReflectLuaProxy::from_table_proxy::<T>());
drop(registry);
let mut lookup = self.get_resource_or_else::<LuaTableProxyLookup, _>(LuaTableProxyLookup::default);
lookup.typeid_from_name.insert(T::table_name(), TypeId::of::<C>());
let info = ComponentInfo::new::<C>();
lookup.comp_info_from_name.insert(T::table_name(), info);
}
}
impl<'lua> elua::FromLua<'lua> for ScriptBorrow {
fn from_lua(_: &'lua elua::State, value: elua::Value<'lua>) -> elua::Result<Self> {
match value { match value {
mlua::Value::UserData(ud) => Ok(ud.borrow::<Self>()?.clone()), elua::Value::Userdata(ud) => Ok(ud.as_ref::<Self>()?.clone()),
_ => unreachable!(), _ => unreachable!(),
} }
} }
} }
impl mlua::UserData for ScriptBorrow {} impl<'lua> elua::FromLuaVec<'lua> for ScriptBorrow {
fn from_lua_value_vec(state: &'lua elua::State, mut values: elua::ValueVec<'lua>) -> elua::Result<Self> {
pub fn reflect_user_data(ud: &mlua::AnyUserData) -> ScriptBorrow { if let Some(v) = values.pop_front() {
ud.call_method::<_, ScriptBorrow>(FN_NAME_INTERNAL_REFLECT, ()) ScriptBorrow::from_lua(state, v)
.expect("Type does not implement '__internal_reflect' properly") } else {
} Err(elua::Error::Nil)
pub trait LuaProxy {
fn as_lua_value<'lua>(lua: &'lua mlua::Lua, this: &dyn Reflect) -> mlua::Result<mlua::AnyUserData<'lua>>;
fn apply(lua: &mlua::Lua, this: &mut dyn Reflect, apply: &mlua::AnyUserData) -> mlua::Result<()>;
}
impl<'a, T: Reflect + Clone + mlua::FromLua<'a> + mlua::UserData> LuaProxy for T {
fn as_lua_value<'lua>(lua: &'lua mlua::Lua, this: &dyn Reflect) -> mlua::Result<mlua::AnyUserData<'lua>> {
let this = this.as_any().downcast_ref::<T>().unwrap();
lua.create_userdata(this.clone())
}
fn apply(_lua: &mlua::Lua, this: &mut dyn Reflect, apply: &mlua::AnyUserData) -> mlua::Result<()> {
let this = this.as_any_mut().downcast_mut::<T>().unwrap();
let apply = apply.borrow::<T>()?;
*this = apply.clone();
Ok(())
}
}
#[derive(Clone)]
pub struct ReflectLuaProxy {
fn_as_uservalue: for<'a> fn(lua: &'a Lua, this_ptr: NonNull<u8>) -> mlua::Result<mlua::AnyUserData<'a>>,
fn_apply: for<'a> fn(lua: &'a Lua, this_ptr: NonNull<u8>, apply: &'a mlua::AnyUserData<'a>) -> mlua::Result<()>,
}
impl<'a, T: Reflect + LuaProxy + Clone + mlua::FromLua<'a> + mlua::UserData> FromType<T> for ReflectLuaProxy {
fn from_type() -> Self {
Self {
fn_as_uservalue: |lua, this| -> mlua::Result<mlua::AnyUserData> {
let this = unsafe { this.cast::<T>().as_ref() };
<T as LuaProxy>::as_lua_value(lua, this)
},
fn_apply: |lua, ptr, apply| {
let this = unsafe { ptr.cast::<T>().as_mut() };
<T as LuaProxy>::apply(lua, this, apply)
}
} }
} }
} }
impl<'lua> mlua::FromLua<'lua> for ScriptDynamicBundle { impl elua::Userdata for ScriptBorrow {
fn from_lua(value: mlua::Value<'lua>, _lua: &'lua Lua) -> mlua::Result<Self> { fn name() -> String {
match value { "ScriptBorrow".to_string()
mlua::Value::UserData(ud) => Ok(ud.borrow::<Self>()?.clone()),
mlua::Value::Nil => Err(mlua::Error::FromLuaConversionError { from: "Nil", to: "DynamicBundle", message: Some("Value was nil".to_string()) }),
_ => panic!(),
}
} }
fn build<'a>(_: &mut elua::UserdataBuilder<'a, Self>) { }
} }
impl mlua::UserData for ScriptDynamicBundle { /// Helper function used for reflecting userdata as a ScriptBorrow
fn add_methods<'lua, M: mlua::prelude::LuaUserDataMethods<'lua, Self>>(methods: &mut M) { pub fn reflect_user_data(ud: &elua::AnyUserdata) -> ScriptBorrow {
methods.add_function("new", |_, ()| { ud.execute_method::<_, ScriptBorrow>(FN_NAME_INTERNAL_REFLECT, ())
Ok(ScriptDynamicBundle(DynamicBundle::new())) .expect("Type does not implement internal reflect method properly")
});
methods.add_method_mut("push", |_, this, (comp,): (mlua::AnyUserData,)| {
let script_brw = comp.call_method::<_, ScriptBorrow>(FN_NAME_INTERNAL_REFLECT, ())?;
let reflect = script_brw.reflect_branch.as_component_unchecked();
let refl_data = script_brw.data.unwrap();
let refl_data = refl_data.as_ref();
reflect.bundle_insert(&mut this.0, refl_data);
Ok(())
});
}
}
#[derive(Default)]
pub struct LuaScriptingPlugin;
impl Plugin for LuaScriptingPlugin {
fn setup(&self, game: &mut lyra_game::game::Game) {
let world = game.world();
world.add_resource_default::<LuaHost>();
world.add_resource_default::<ScriptApiProviders<LuaHost>>();
world.add_resource_default::<ScriptContexts<Mutex<mlua::Lua>>>();
let mut loader = world.get_resource_or_else(ResourceManager::default);
loader.register_loader::<LuaLoader>();
}
} }

View File

@ -0,0 +1,49 @@
use lyra_ecs::ResourceObject;
use lyra_reflect::Reflect;
use crate::{lua::{wrappers::{LuaActionHandler, LuaDeltaTime, LuaModelComponent}, LuaContext, RegisterLuaType, FN_NAME_INTERNAL_REFLECT_TYPE}, ScriptApiProvider, ScriptBorrow, ScriptData, ScriptDynamicBundle, ScriptWorldPtr};
#[derive(Default)]
pub struct LyraEcsApiProvider;
impl ScriptApiProvider for LyraEcsApiProvider {
type ScriptContext = LuaContext;
fn prepare_world(&mut self, world: &mut lyra_ecs::World) {
world.register_lua_convert::<LuaDeltaTime>();
world.register_lua_wrapper::<LuaModelComponent>();
world.register_lua_wrapper::<LuaActionHandler>();
}
fn expose_api(&mut self, _: &ScriptData, ctx: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> {
let ctx = ctx.lock().unwrap();
let globals = ctx.globals()?;
globals.set("World", ctx.create_proxy::<ScriptWorldPtr>()?)?;
globals.set("DynamicBundle", ctx.create_proxy::<ScriptDynamicBundle>()?)?;
globals.set("ModelComponent", ctx.create_proxy::<LuaModelComponent>()?)?;
globals.set("ActionHandler", ctx.create_proxy::<LuaActionHandler>()?)?;
let dt_table = create_reflect_table::<lyra_game::DeltaTime>(&ctx)?;
globals.set("DeltaTime", dt_table)?;
Ok(())
}
fn setup_script(&mut self, _: &crate::ScriptData, _: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> {
Ok(())
}
fn update_script_environment(&mut self, _: crate::ScriptWorldPtr, _: &crate::ScriptData, _: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> {
Ok(())
}
}
fn create_reflect_table<T: Reflect + ResourceObject + Default + 'static>(lua: &elua::State) -> elua::Result<elua::Table> {
let table = lua.create_table()?;
table.set(FN_NAME_INTERNAL_REFLECT_TYPE, lua.create_function(|_, ()| {
Ok(ScriptBorrow::from_resource::<T>(None))
})?)?;
Ok(table)
}

View File

@ -0,0 +1,41 @@
use lyra_ecs::World;
use crate::lua::wrappers::{LuaQuat, LuaTransform, LuaVec3};
use crate::ScriptData;
use crate::lua::RegisterLuaType;
use crate::{ScriptApiProvider, lua::LuaContext};
#[derive(Default)]
pub struct LyraMathApiProvider;
impl ScriptApiProvider for LyraMathApiProvider {
type ScriptContext = LuaContext;
fn prepare_world(&mut self, world: &mut World) {
world.register_lua_wrapper::<LuaVec3>();
world.register_lua_wrapper::<LuaQuat>();
world.register_lua_wrapper::<LuaTransform>();
}
fn expose_api(&mut self, _data: &ScriptData, ctx: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> {
let ctx = ctx.lock().unwrap();
/* let bytes = include_bytes!("../../../scripts/lua/math/transform.lua");
ctx.load("lyra/math/transform.lua", bytes.as_slice())?.execute(())?; */
let globals = ctx.globals()?;
globals.set("Vec3", ctx.create_proxy::<LuaVec3>()?)?;
globals.set("Quat", ctx.create_proxy::<LuaQuat>()?)?;
globals.set("Transform", ctx.create_proxy::<LuaTransform>()?)?;
Ok(())
}
fn setup_script(&mut self, _: &crate::ScriptData, _: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> {
Ok(())
}
fn update_script_environment(&mut self, _: crate::ScriptWorldPtr, _: &crate::ScriptData, _: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> {
Ok(())
}
}

View File

@ -0,0 +1,8 @@
pub mod util;
pub use util::*;
pub mod math;
pub use math::*;
pub mod ecs;
pub use ecs::*;

View File

@ -0,0 +1,134 @@
use std::sync::{Mutex, Arc};
use tracing::{debug_span, debug};
use crate::{ScriptApiProvider, ScriptData};
/// This Api provider provides some nice utility functions.
///
/// Functions:
/// ```lua
/// ---@param str (string) A format string.
/// ---@param ... (any varargs) The variables to format into the string. These values must be
/// primitives, or if UserData, have the '__tostring' meta method
/// function printf(str, ...)
/// ```
#[derive(Default)]
pub struct UtilityApiProvider;
impl ScriptApiProvider for UtilityApiProvider {
type ScriptContext = Mutex<elua::State>;
fn expose_api(&mut self, data: &ScriptData, ctx: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> {
let ctx = ctx.lock().unwrap();
//fn printf(lua: &elua::State, (mut text, formats): (String, elua::Variadic<elua::Value>)) -> elua::Result<()> {
let printf = |lua: &elua::State, (mut text, formats): (String, elua::Variadic<elua::Value>)| {
let mut formatted = String::new();
let mut arg_num = 0;
while let Some(start) = text.find("{}") {
let val_str = match formats.get(arg_num) {
Some(v) => match v {
elua::Value::Nil => "nil".to_string(),
elua::Value::Boolean(b) => b.to_string(),
elua::Value::Number(n) => n.to_string(),
elua::Value::String(s) => s.clone(),
elua::Value::Table(_) => {
return Err(elua::Error::runtime("unable to get string representation of Table"));
},
elua::Value::Function(_) => {
return Err(elua::Error::runtime("unable to get string representation of Function"));
},
elua::Value::Thread(_) => {
return Err(elua::Error::runtime("unable to get string representation of Thread"));
},
elua::Value::Userdata(ud) => {
if let Ok(tos) = ud.get::<_, elua::Function>(elua::MetaMethod::ToString) {
tos.exec::<_, String>(())?
} else {
return Err(elua::Error::runtime("UserData does not implement MetaMethod '__tostring'"));
}
},
elua::Value::None => "None".to_string(),
elua::Value::Multi(_) => {
return Err(elua::Error::runtime("unable to get string representation of ValueVec"));
},
},
None => {
let got_args = arg_num;// - 1;
// continue searching for {} to get the number of format spots for the error message.
while let Some(start) = text.find("{}") {
text = text[start + 2..].to_string();
arg_num += 1;
}
return Err(elua::Error::BadArgument {
func: Some("printf".to_string()),
arg_index: 2,
arg_name: Some("fmt...".to_string()),
error: Arc::new(elua::Error::Runtime(format!(
"not enough args \
given for the amount of format areas in the string. Expected {}, \
got {}.", arg_num, got_args
)))
})
},
};
formatted = format!("{}{}{}", formatted, &text[0..start], val_str);
text = text[start + 2..].to_string();
arg_num += 1;
}
if arg_num < formats.len() {
return Err(elua::Error::BadArgument {
func: Some("printf".to_string()),
arg_index: 2,
arg_name: Some("fmt...".to_string()),
error: Arc::new(elua::Error::Runtime(format!(
"got more args \
than format areas in the string. Expected {}, got {}.", formats.len(), arg_num
)))
})
}
formatted = format!("{}{}", formatted, text);
lua.globals()?
.get::<_, elua::Function>("print")?
.exec::<_, ()>(formatted)?;
Ok(())
};
let script_name_reg = ctx.registry_insert(data.name.clone())?;
let printf_func = ctx.create_function(printf)?;
let print_func = ctx.create_function(move |lua, text: String| {
let name = lua.registry_get::<String>(script_name_reg)?;
let _span = debug_span!("lua", script = &name).entered();
debug!(target: "lyra_scripting::lua", "{}", text);
Ok(())
})?;
let globals = ctx.globals()?;
globals.set("printf", printf_func)?;
globals.set("print", print_func)?;
Ok(())
}
fn setup_script(&mut self, _data: &ScriptData, _ctx: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> {
Ok(())
}
fn update_script_environment(&mut self, _world: crate::ScriptWorldPtr, _data: &ScriptData, _ctx: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> {
Ok(())
}
}

View File

@ -0,0 +1,179 @@
use std::{any::TypeId, collections::HashMap, ptr::NonNull};
use elua::{FromLua, TableProxy, AsLua};
use lyra_ecs::{ComponentInfo, DynamicBundle};
use lyra_reflect::Reflect;
use crate::{ScriptBorrow, ScriptDynamicBundle};
use super::FN_NAME_INTERNAL_REFLECT;
pub trait LuaWrapper {
/// The type id of the wrapped type.
fn wrapped_type_id() -> TypeId;
}
/// A trait that used to convert something into lua, or to set something to a value from lua.
pub trait LuaProxy {
fn as_lua_value<'lua>(
lua: &'lua elua::State,
this: &dyn Reflect,
) -> elua::Result<elua::Value<'lua>>;
fn apply(
lua: &elua::State,
this: &mut dyn Reflect,
value: &elua::Value,
) -> elua::Result<()>;
}
impl<'a, T> LuaProxy for T
where
T: Reflect + Clone + elua::FromLua<'a> + elua::Userdata
{
fn as_lua_value<'lua>(
lua: &'lua elua::State,
this: &dyn Reflect,
) -> elua::Result<elua::Value<'lua>> {
let this = this.as_any().downcast_ref::<T>().unwrap();
lua.create_userdata(this.clone())
.and_then(|ud| ud.as_lua(lua))
}
fn apply(
_: &elua::State,
this: &mut dyn Reflect,
apply: &elua::Value,
) -> elua::Result<()> {
let this = this.as_any_mut().downcast_mut::<T>().unwrap();
let apply = apply.as_userdata()
.expect("Somehow a non-userdata Lua Value was provided to a LuaProxy")
.as_ref::<T>()?;
*this = apply.clone();
Ok(())
}
}
/// A struct that is used for retrieving rust type ids of types that implement `TableProxy`.
#[derive(Default)]
pub struct LuaTableProxyLookup {
pub(crate) typeid_from_name: HashMap<String, TypeId>,
pub(crate) comp_info_from_name: HashMap<String, ComponentInfo>,
}
/// A struct used for Proxying types to and from Lua.
#[derive(Clone)]
pub struct ReflectLuaProxy {
pub fn_as_lua:
for<'a> fn(lua: &'a elua::State, this_ptr: NonNull<()>) -> elua::Result<elua::Value<'a>>,
pub fn_apply: for<'a> fn(
lua: &'a elua::State,
this_ptr: NonNull<()>,
value: &'a elua::Value<'a>,
) -> elua::Result<()>,
}
impl ReflectLuaProxy {
/// Create from a type that implements LuaProxy (among some other required traits)
pub fn from_lua_proxy<'a, T>() -> Self
where
T: Reflect + LuaProxy
{
Self {
fn_as_lua: |lua, this| -> elua::Result<elua::Value> {
let this = unsafe { this.cast::<T>().as_ref() };
<T as LuaProxy>::as_lua_value(lua, this)
},
fn_apply: |lua, ptr, apply| {
let this = unsafe { ptr.cast::<T>().as_mut() };
<T as LuaProxy>::apply(lua, this, apply)
},
}
}
pub fn from_table_proxy<T>() -> Self
where
T: TableProxy
{
Self {
fn_as_lua: |lua, this| -> elua::Result<elua::Value> {
let this = unsafe { this.cast::<T>().as_ref() };
this.as_table(lua)
.and_then(|t| t.as_lua(lua))
},
fn_apply: |lua, ptr, value| {
let this = unsafe { ptr.cast::<T>().as_mut() };
let table = value.as_table()
.expect("Somehow a non-Table Lua Value was provided to a TableProxy");
let new_val = T::from_table(lua, table.clone())?;
*this = new_val;
Ok(())
},
}
}
/// Create from a type that implements FromLua and AsLua
pub fn from_as_and_from_lua<T>() -> Self
where
T: for<'a> elua::FromLua<'a> + for<'a> elua::AsLua<'a> + Clone
{
Self {
fn_as_lua: |lua, this| -> elua::Result<elua::Value> {
let this = unsafe { this.cast::<T>().as_ref() };
this.clone().as_lua(lua)
},
fn_apply: |lua, ptr, value| {
let this = unsafe { ptr.cast::<T>().as_mut() };
let new_val = T::from_lua(lua, value.clone())?;
*this = new_val;
Ok(())
},
}
}
}
impl<'lua> elua::FromLua<'lua> for ScriptDynamicBundle {
fn from_lua(_: &'lua elua::State, val: elua::Value<'lua>) -> elua::Result<Self> {
match val {
elua::Value::Userdata(ud) => Ok(ud.as_ref::<Self>()?.clone()),
elua::Value::Nil => Err(elua::Error::Nil),
_ => unreachable!(),
}
}
}
impl<'lua> elua::FromLuaVec<'lua> for ScriptDynamicBundle {
fn from_lua_value_vec(state: &'lua elua::State, mut values: elua::ValueVec<'lua>) -> elua::Result<Self> {
if let Some(v) = values.pop_front() {
Ok(ScriptDynamicBundle::from_lua(state, v)?)
} else {
Err(elua::Error::Nil)
}
}
}
impl elua::Userdata for ScriptDynamicBundle {
fn name() -> String {
"Bundle".to_string()
}
fn build<'a>(builder: &mut elua::UserdataBuilder<'a, Self>) {
builder
.function("new", |_, ()| Ok(ScriptDynamicBundle(DynamicBundle::new())))
.method_mut("push", |_, this, comp: elua::AnyUserdata| {
let script_brw = comp.execute_method::<_, ScriptBorrow>(FN_NAME_INTERNAL_REFLECT, ())?;
let reflect = script_brw.reflect_branch.as_component_unchecked();
let refl_data = script_brw.data.unwrap();
reflect.bundle_insert(&mut this.0, refl_data);
Ok(())
});
}
}

View File

@ -1,46 +1,41 @@
use std::sync::Mutex; use std::sync::Mutex;
use tracing::debug; use elua::{AsLua, StdLibraries};
use crate::{ScriptHost, ScriptError, ScriptWorldPtr}; use crate::{ScriptHost, ScriptError, ScriptWorldPtr, ScriptEntity};
#[derive(Default)] #[derive(Default)]
pub struct LuaHost; pub struct LuaHost;
fn try_call_lua_function(lua: &mlua::Lua, fn_name: &str) -> Result<(), ScriptError> { fn try_call_lua_function(lua: &elua::State, fn_name: &str) -> Result<(), ScriptError> {
let globals = lua.globals(); let globals = lua.globals()?;
match globals.get::<_, mlua::Function>(fn_name) { if globals.has_key(fn_name)? {
Ok(init_fn) => { let lua_fn = globals.get::<_, elua::Function>(fn_name)?;
init_fn.call(()) lua_fn.exec(())
.map_err(ScriptError::MluaError)?; .map_err(ScriptError::MluaError)?;
},
Err(mlua::Error::FromLuaConversionError { from: "nil", to: "function", message: None }) => {
debug!("Function '{}' was not found, ignoring...", fn_name)
// ignore
},
Err(e) => {
return Err(ScriptError::MluaError(e));
},
} }
Ok(()) Ok(())
} }
impl ScriptHost for LuaHost { impl ScriptHost for LuaHost {
type ScriptContext = Mutex<mlua::Lua>; type ScriptContext = Mutex<elua::State>;
fn load_script(&mut self, script: &[u8], script_data: &crate::ScriptData, providers: &mut crate::ScriptApiProviders<Self>) -> Result<Self::ScriptContext, crate::ScriptError> { fn load_script(&mut self, script: &[u8], script_data: &crate::ScriptData, providers: &mut crate::ScriptApiProviders<Self>) -> Result<Self::ScriptContext, crate::ScriptError> {
let mut ctx = Mutex::new(mlua::Lua::new()); let mut ctx = Mutex::new({
let s = elua::State::new();
s.expose_libraries(StdLibraries::all());
s
});
for provider in providers.apis.iter_mut() { for provider in providers.apis.iter_mut() {
provider.expose_api(&mut ctx)?; provider.expose_api(script_data, &mut ctx)?;
} }
let lua = ctx.lock().unwrap(); let lua = ctx.lock().unwrap();
lua.load(script) lua.load(&script_data.name, script)?
.set_name(&script_data.name) .execute(())
.exec()
.map_err(|e| ScriptError::MluaError(e))?; .map_err(|e| ScriptError::MluaError(e))?;
drop(lua); drop(lua);
@ -52,22 +47,26 @@ impl ScriptHost for LuaHost {
provider.setup_script(script_data, ctx)?; provider.setup_script(script_data, ctx)?;
} }
let ctx = ctx.lock().expect("Failure to get Lua ScriptContext");
try_call_lua_function(&ctx, "init")?;
Ok(()) Ok(())
} }
/// Runs the update step of the lua script. /// Runs the update step of the lua script.
/// ///
/// It looks for an `update` function with zero parameters in [`the ScriptContext`] and executes it. /// It looks for an `update` function with zero parameters in the [`ScriptContext`] and executes it.
fn update_script(&mut self, world: ScriptWorldPtr, script_data: &crate::ScriptData, ctx: &mut Self::ScriptContext, providers: &mut crate::ScriptApiProviders<Self>) -> Result<(), ScriptError> { fn call_script(&mut self, world: ScriptWorldPtr, script_data: &crate::ScriptData,
ctx: &mut Self::ScriptContext, providers: &mut crate::ScriptApiProviders<Self>,
function_name: &str) -> Result<(), ScriptError> {
for provider in providers.apis.iter_mut() { for provider in providers.apis.iter_mut() {
provider.update_script_environment(world.clone(), script_data, ctx)?; provider.update_script_environment(world.clone(), script_data, ctx)?;
} }
let ctx = ctx.lock().expect("Failure to get Lua ScriptContext"); let ctx = ctx.lock().expect("Failure to get Lua ScriptContext");
try_call_lua_function(&ctx, "update")?;
let globals = ctx.globals()?;
globals.set("world", world.as_lua(&ctx)?)?;
globals.set("entity", ScriptEntity(script_data.entity).as_lua(&ctx)?)?;
try_call_lua_function(&ctx, function_name)?;
Ok(()) Ok(())
} }

View File

@ -0,0 +1,258 @@
use anyhow::anyhow;
use lyra_ecs::{query::{Entities, ResMut, View}, World};
use lyra_game::{game::GameStages, plugin::Plugin};
use lyra_reflect::TypeRegistry;
use lyra_resource::ResourceManager;
use tracing::{debug, debug_span, error, trace};
use crate::{GameScriptExt, ScriptApiProviders, ScriptContexts, ScriptData, ScriptError, ScriptHost, ScriptList, ScriptWorldPtr};
use super::{providers::{LyraEcsApiProvider, LyraMathApiProvider, UtilityApiProvider}, LuaContext, LuaHost, LuaLoader, LuaScript};
/// A system that creates the script contexts in the world as new scripts are found
pub fn lua_scripts_create_contexts(
world: &mut World,
mut host: ResMut<LuaHost>,
mut contexts: ResMut<ScriptContexts<LuaContext>>,
mut providers: ResMut<ScriptApiProviders<LuaHost>>,
view: View<(Entities, &ScriptList<LuaScript>)>,
) -> anyhow::Result<()> {
for (en, scripts) in view.into_iter() {
for script in scripts.iter() {
if !contexts.has_context(script.id()) {
let script_data = ScriptData {
name: script.name().to_string(),
script_id: script.id(),
entity: en,
};
let script_name = script.name();
let _span = debug_span!("lua", script = script_name).entered();
if let Some(script_res) = &script.res_handle().try_data_ref() {
debug!("Loading script...");
let mut script_ctx =
host.load_script(&script_res.bytes, &script_data, &mut providers)?;
trace!("Finished loading script");
debug!("Setting up script...");
host.setup_script(&script_data, &mut script_ctx, &mut providers)?;
trace!("Finished setting up script");
// call on_init, handle the error
let world_ptr = ScriptWorldPtr::from_ref(&world);
match host.call_script(
world_ptr,
&script_data,
&mut script_ctx,
&mut providers,
"on_init",
) {
Ok(()) => {}
Err(e) => match e {
ScriptError::MluaError(m) => {
error!("Script '{}' ran into an error: {}", script.name(), m);
}
ScriptError::Other(_) => return Err(e.into()),
},
}
contexts.add_context(script.id(), script_ctx);
break;
} else {
trace!("Script is not loaded yet, skipping for now");
}
}
}
}
Ok(())
}
/// A system that triggers a reload of watched script resources.
///
/// Note: This only works if the script is watched. See [`lyra_resource::ResourceManager::watch`].
pub fn lua_scripts_reload_system(
mut contexts: ResMut<ScriptContexts<LuaContext>>,
mut resman: ResMut<ResourceManager>,
view: View<&ScriptList<LuaScript>>,
) -> anyhow::Result<()> {
for scripts in view.into_iter() {
for script in scripts.iter() {
let handle = script.res_handle();
if handle.is_watched() {
let handle_path = handle.path();
let watch_recv = resman.watcher_event_recv(&handle_path).unwrap();
match watch_recv.try_recv() {
Ok(ev) => {
let evs =
ev.map_err(|e| anyhow!("Script watcher ran into errors: {:?}", e))?;
if evs.iter().any(|ev| ev.event.kind.is_modify()) {
debug!(
"Detected change of '{}' script, triggering reload",
handle_path
);
contexts.remove_context(script.id()).unwrap();
resman.reload(handle)?;
}
}
Err(e) => match e {
lyra_resource::channel::TryRecvError::Empty => {}
lyra_resource::channel::TryRecvError::Disconnected => {
resman.stop_watching(&handle_path).unwrap();
}
},
}
}
}
}
Ok(())
}
fn lua_call_script_function(world: &mut World, stage_name: &str) -> anyhow::Result<()> {
let world_ptr = ScriptWorldPtr::from_ref(&world);
let mut host = world.get_resource_mut::<LuaHost>();
let mut contexts = world.get_resource_mut::<ScriptContexts<LuaContext>>();
let mut providers = world.get_resource_mut::<ScriptApiProviders<LuaHost>>();
for (en, scripts) in world.view_iter::<(Entities, &ScriptList<LuaScript>)>() {
for script in scripts.iter() {
let script_data = ScriptData {
name: script.name().to_string(),
script_id: script.id(),
entity: en,
};
if let Some(ctx) = contexts.get_context_mut(script.id()) {
trace!(
"Running '{}' function in script '{}'",
stage_name,
script.name()
);
match host.call_script(
world_ptr.clone(),
&script_data,
ctx,
&mut providers,
stage_name,
) {
Ok(()) => {}
Err(e) => match e {
ScriptError::MluaError(m) => {
error!("Script '{}' ran into an error: {}", script.name(), m);
}
ScriptError::Other(_) => return Err(e.into()),
},
}
}
}
}
Ok(())
}
/// This system executes the 'on_update' function of lua scripts in the world. It is meant to run
/// during the 'GameStages::Update' stage.
pub fn lua_script_update_stage_system(world: &mut World) -> anyhow::Result<()> {
lua_call_script_function(world, "on_update")
}
/// This system executes the 'on_pre_update' function of lua scripts in the world. It is meant to run
/// during the 'GameStages::PreUpdate' stage.
pub fn lua_script_pre_update_stage_system(world: &mut World) -> anyhow::Result<()> {
lua_call_script_function(world, "on_pre_update")
}
/// This system executes the 'on_post_update' function of lua scripts in the world. It is meant to run
/// during the 'GameStages::PostUpdate' stage.
pub fn lua_script_post_update_stage_system(world: &mut World) -> anyhow::Result<()> {
lua_call_script_function(world, "on_post_update")
}
/// This system executes the 'on_first' function of lua scripts in the world. It is meant to run
/// during the 'GameStages::First' stage.
pub fn lua_script_first_stage_system(world: &mut World) -> anyhow::Result<()> {
lua_call_script_function(world, "on_first")
}
/// This system executes the 'on_last' function of lua scripts in the world. It is meant to run
/// during the 'GameStages::Last' stage.
pub fn lua_script_last_stage_system(world: &mut World) -> anyhow::Result<()> {
lua_call_script_function(world, "on_last")
}
#[derive(Default)]
pub struct LuaScriptingPlugin;
impl Plugin for LuaScriptingPlugin {
fn setup(&self, game: &mut lyra_game::game::Game) {
let world = game.world_mut();
world.add_resource_default::<TypeRegistry>();
world.add_resource_default::<LuaHost>();
world.add_resource_default::<ScriptApiProviders<LuaHost>>();
world.add_resource_default::<ScriptContexts<LuaContext>>();
let mut loader = world
.try_get_resource_mut::<ResourceManager>()
.expect("Add 'ResourceManager' to the world before trying to add this plugin");
loader.register_loader::<LuaLoader>();
drop(loader);
game.add_script_api_provider::<LuaHost, _>(UtilityApiProvider);
game.add_script_api_provider::<LuaHost, _>(LyraEcsApiProvider);
game.add_script_api_provider::<LuaHost, _>(LyraMathApiProvider);
game.add_system_to_stage(
GameStages::First,
"lua_create_contexts",
lua_scripts_create_contexts,
&[],
)
.add_system_to_stage(
GameStages::First,
"lua_reload_scripts",
lua_scripts_reload_system,
&["lua_create_contexts"],
)
.add_system_to_stage(
GameStages::First,
"lua_first_stage",
lua_script_first_stage_system,
&["lua_reload_scripts"],
)
// cannot depend on 'lua_create_contexts' since it will cause a panic.
// the staged executor separates the executor of a single stage so this system
// cannot depend on the other one.
.add_system_to_stage(
GameStages::PreUpdate,
"lua_pre_update",
lua_script_pre_update_stage_system,
&[],
)
.add_system_to_stage(
GameStages::Update,
"lua_update",
lua_script_update_stage_system,
&[],
)
.add_system_to_stage(
GameStages::PostUpdate,
"lua_post_update",
lua_script_post_update_stage_system,
&[],
)
.add_system_to_stage(
GameStages::Last,
"lua_last_stage",
lua_script_last_stage_system,
&[],
);
}
}

View File

@ -1,219 +0,0 @@
use std::{sync::{Mutex, Arc}, alloc::Layout, ptr::NonNull};
use lyra_ecs::{World, query::{Res, View, Entities, ResMut}, Component, system::{GraphExecutor, IntoSystem}};
use lyra_resource::ResourceManager;
use mlua::{IntoLua, AnyUserDataExt};
use tracing::{debug, error};
use tracing_subscriber::{layer::SubscriberExt, fmt, filter, util::SubscriberInitExt};
use crate::{ScriptHost, ScriptData, ScriptApiProvider, ScriptApiProviders, ScriptError, ScriptWorldPtr, ScriptList, Script, ScriptContexts};
use super::{LuaHost, LuaLoader, LuaScript};
use crate::lyra_engine;
fn enable_tracing() {
tracing_subscriber::registry()
.with(fmt::layer().with_writer(std::io::stdout))
.init();
}
#[derive(Default)]
struct PrintfProvider;
impl ScriptApiProvider for PrintfProvider {
type ScriptContext = Mutex<mlua::Lua>;
fn expose_api(&mut self, ctx: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> {
let ctx = ctx.lock().unwrap();
fn printf(lua: &mlua::Lua, (mut text, formats): (String, mlua::Variadic<mlua::Value>)) -> mlua::Result<()> {
let mut formatted = String::new();
let mut arg_num = 0;
while let Some(start) = text.find("{}") {
let val_str = match formats.get(arg_num) {
Some(v) => match v {
mlua::Value::Nil => "nil".to_string(),
mlua::Value::Boolean(b) => b.to_string(),
mlua::Value::LightUserData(_) => {
return Err(mlua::Error::RuntimeError(format!("unable to get string representation of LightUserData")));
},
mlua::Value::Integer(i) => i.to_string(),
mlua::Value::Number(n) => n.to_string(),
mlua::Value::String(s) => s.to_str().unwrap().to_string(),
mlua::Value::Table(_) => {
return Err(mlua::Error::RuntimeError(format!("unable to get string representation of Table")));
},
mlua::Value::Function(_) => {
return Err(mlua::Error::RuntimeError(format!("unable to get string representation of Function")));
},
mlua::Value::Thread(_) => {
return Err(mlua::Error::RuntimeError(format!("unable to get string representation of Thread")));
},
mlua::Value::UserData(ud) => {
if let Ok(tos) = ud.get::<_, mlua::Function>(mlua::MetaMethod::ToString.to_string()) {
tos.call::<_, String>(())?
} else {
return Err(mlua::Error::RuntimeError(format!("UserData does not implement MetaMethod '__tostring'")));
}
},
mlua::Value::Error(e) => e.to_string(),
},
None => {
let got_args = arg_num;// - 1;
// continue searching for {} to get the number of format spots for the error message.
while let Some(start) = text.find("{}") {
text = text[start + 2..].to_string();
arg_num += 1;
}
return Err(mlua::Error::BadArgument {
to: Some("printf".to_string()),
pos: 2,
name: Some("...".to_string()),
cause: Arc::new(mlua::Error::RuntimeError(format!("not enough args \
given for the amount of format areas in the string. Expected {}, \
got {}.", arg_num, got_args)))
})
},
};
formatted = format!("{}{}{}", formatted, &text[0..start], val_str);
text = text[start + 2..].to_string();
arg_num += 1;
}
if arg_num < formats.len() {
return Err(mlua::Error::BadArgument {
to: Some("printf".to_string()),
pos: 2,
name: Some("...".to_string()),
cause: Arc::new(mlua::Error::RuntimeError(format!("got more args \
than format areas in the string. Expected {}, got {}.", formats.len(), arg_num)))
})
}
formatted = format!("{}{}", formatted, text);
lua.globals()
.get::<_, mlua::Function>("print")
.unwrap()
.call::<_, ()>(formatted)
.unwrap();
Ok(())
}
let printf_func = ctx.create_function(printf).unwrap();
let globals = ctx.globals();
globals.set("printf", printf_func).unwrap();
Ok(())
}
fn setup_script(&mut self, _data: &ScriptData, _ctx: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> {
Ok(())
}
fn update_script_environment(&mut self, world: crate::ScriptWorldPtr, _data: &ScriptData, ctx: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> {
let ctx = ctx.lock().unwrap();
let globals = ctx.globals();
let world_lua = world.into_lua(&ctx)
.map_err(ScriptError::MluaError)?;
globals.set("world", world_lua)
.map_err(ScriptError::MluaError)?;
Ok(())
}
}
/// Tests a simple lua script that just prints some test
#[test]
pub fn lua_print() {
enable_tracing();
let mut world = World::new();
let test_provider = PrintfProvider::default();
let mut providers = ScriptApiProviders::<LuaHost>::default();
providers.add_provider(test_provider);
let host = LuaHost::default();
world.add_resource(host);
world.add_resource(providers);
world.add_resource(ScriptContexts::<Mutex<mlua::Lua>>::default());
let mut res_loader = ResourceManager::new();
res_loader.register_loader::<LuaLoader>();
let script =
r#"
print("Hello World")
function update()
print("updated")
printf("I love to eat formatted {}!", "food")
--printf("World is {}", world)
end
"#;
let script = script.as_bytes();
let script = res_loader.load_bytes::<LuaScript>("test_script.lua",
"text/lua", script.to_vec(), 0, script.len()).unwrap();
let script = Script::new("text_script.lua", script);
let scripts = ScriptList::new(vec![script]);
world.spawn((scripts,));
let mut exec = GraphExecutor::new();
exec.insert_system("lua_update_scripts", lua_update_scripts.into_system(), &[]);
exec.execute(NonNull::from(&world), true).unwrap();
}
fn lua_update_scripts(world: &mut World) -> anyhow::Result<()> {
let world_ptr = ScriptWorldPtr::from_ref(&world);
let mut host = world.get_resource_mut::<LuaHost>();
let mut contexts = world.get_resource_mut::<ScriptContexts<Mutex<mlua::Lua>>>();
let mut providers = world.get_resource_mut::<ScriptApiProviders<LuaHost>>();
for scripts in world.view_iter::<&ScriptList<LuaScript>>() {
for script in scripts.iter() {
let script_data = ScriptData {
name: script.name().to_string(),
script_id: script.id(),
};
if !contexts.has_context(script.id()) {
if let Some(script_res) = &script.res_handle().data {
let mut script_ctx = host.load_script(&script_res.bytes, &script_data, &mut providers).unwrap();
host.setup_script(&script_data, &mut script_ctx, &mut providers).unwrap();
contexts.add_context(script.id(), script_ctx);
} else {
debug!("Script '{}' is not yet loaded, skipping", script.name());
}
}
let ctx = contexts.get_context_mut(script.id()).unwrap();
match host.update_script(world_ptr.clone(), &script_data, ctx, &mut providers) {
Ok(()) => {},
Err(e) => match e {
ScriptError::MluaError(m) => {
error!("Script '{}' ran into an error: {}", script.name(), m);
},
ScriptError::Other(_) => return Err(e.into()),
},
}
}
}
Ok(())
}

View File

@ -1,22 +1,36 @@
use lyra_ecs::query::dynamic::QueryDynamicType; use std::{ptr::NonNull, sync::Arc};
use lyra_reflect::TypeRegistry;
use mlua::{AnyUserDataExt, IntoLua, IntoLuaMulti};
use crate::{ScriptWorldPtr, ScriptEntity, ScriptDynamicBundle, ScriptBorrow}; use crate::{ScriptBorrow, ScriptEntity, ScriptWorldPtr};
use elua::AsLua;
use lyra_ecs::{query::dynamic::QueryDynamicType, CommandQueue, Commands, DynamicBundle, World};
use lyra_reflect::{ReflectWorldExt, RegisteredType, TypeRegistry};
use lyra_resource::ResourceManager;
use super::{ReflectedIterator, DynamicViewIter, FN_NAME_INTERNAL_REFLECT_TYPE, reflect_user_data, ReflectLuaProxy}; use super::{
reflect_user_data, wrappers::LuaResHandle, DynamicViewIter, LuaTableProxyLookup, ReflectLuaProxy, ReflectedIterator, FN_NAME_INTERNAL_AS_COMPONENT, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE
};
impl<'lua> mlua::FromLua<'lua> for ScriptEntity { impl<'lua> elua::FromLua<'lua> for ScriptEntity {
fn from_lua(value: mlua::Value<'lua>, _lua: &'lua mlua::Lua) -> mlua::Result<Self> { fn from_lua(_: &'lua elua::State, value: elua::Value<'lua>) -> elua::Result<Self> {
match value { match value {
mlua::Value::UserData(ud) => Ok(ud.borrow::<Self>()?.clone()), elua::Value::Userdata(ud) => Ok(ud.as_ref::<Self>()?.clone()),
mlua::Value::Nil => Err(mlua::Error::FromLuaConversionError { from: "Nil", to: "ScriptEntity", message: Some("Value was nil".to_string()) }), elua::Value::Nil => Err(elua::Error::type_mismatch("ScriptEntity", "Nil")),
_ => panic!(), _ => panic!(),
} }
} }
} }
impl mlua::UserData for ScriptEntity {} impl elua::Userdata for ScriptEntity {
fn name() -> String {
"Entity".to_string()
}
fn build<'a>(builder: &mut elua::userdata::UserdataBuilder<'a, Self>) {
builder.meta_method(elua::MetaMethod::ToString, |_, this, ()| {
Ok(format!("{:?}", this.0))
});
}
}
#[derive(thiserror::Error, Debug, Clone)] #[derive(thiserror::Error, Debug, Clone)]
pub enum WorldError { pub enum WorldError {
@ -24,108 +38,271 @@ pub enum WorldError {
LuaInvalidUsage(String), LuaInvalidUsage(String),
} }
impl mlua::UserData for ScriptWorldPtr { impl<'a> elua::FromLua<'a> for ScriptWorldPtr {
fn add_methods<'lua, M: mlua::prelude::LuaUserDataMethods<'lua, Self>>(methods: &mut M) { fn from_lua(_: &'a elua::State, val: elua::Value<'a>) -> elua::Result<Self> {
methods.add_method_mut("spawn", |_, this, (bundle,): (ScriptDynamicBundle,)| { match val {
let world = unsafe { this.inner.as_mut() }; elua::Value::Userdata(ud) => Ok(ud.as_ref::<Self>()?.clone()),
elua::Value::Nil => Err(elua::Error::type_mismatch("ScriptWorldPtr", "Nil")),
Ok(ScriptEntity(world.spawn(bundle.0))) _ => panic!(),
}); }
}
}
methods.add_method("view_iter", |lua, this, queries: mlua::Variadic<mlua::AnyUserData>| { impl elua::Userdata for ScriptWorldPtr {
let world = unsafe { this.inner.as_ref() }; fn name() -> String {
let mut view = world.dynamic_view(); "World".to_string()
}
for comp in queries.into_iter() { fn build<'a>(builder: &mut elua::UserdataBuilder<'a, Self>) {
let script_brw = comp.call_function::<_, ScriptBorrow>(FN_NAME_INTERNAL_REFLECT_TYPE, ()) builder
.expect("Type does not implement '__internal_reflect_type' properly"); .method_mut("spawn", |_, this, vals: elua::ValueVec| {
let refl_comp = script_brw.reflect_branch.as_component_unchecked(); let world = this.as_mut();
let dyn_type = QueryDynamicType::from_info(refl_comp.info); let mut bundle = DynamicBundle::new();
view.push(dyn_type);
}
let iter = view.into_iter(); //while let Some(val) = vals.pop_front() {
let mut reflected_iter = ReflectedIterator { for (i, val) in vals.into_iter().enumerate() {
world: this.clone(), let ud = val.as_userdata().ok_or(
dyn_view: DynamicViewIter::from(iter), elua::Error::bad_arg(
reflected_components: None, Some("World:spawn"),
}; 2 + i as i32, // i starts at 0
Some("components..."),
Arc::new(elua::Error::runtime("provided component is not userdata")),
))?;
let comp_borrow = {
if let Ok(as_comp) = ud.get::<_, elua::Function>(FN_NAME_INTERNAL_AS_COMPONENT) {
let ud = match as_comp.exec(ud.clone())? {
elua::Value::Userdata(ud) => ud,
elua::Value::Nil => ud.clone(),
_ => todo!(),
};
let f = lua.create_function_mut(move |lua, ()| { ud.execute_method::<_, ScriptBorrow>(FN_NAME_INTERNAL_REFLECT, ())?
if let Some(row) = reflected_iter.next_lua(lua) { } else {
let row = row.into_iter().map(|(_, ud)| ud.into_lua(lua)) ud.execute_method::<_, ScriptBorrow>(FN_NAME_INTERNAL_REFLECT, ())?
.collect::<mlua::Result<Vec<mlua::Value>>>()?; }
Ok(mlua::MultiValue::from_vec(row)) };
} else {
Ok(mlua::Value::Nil.into_lua_multi(lua)?) let reflect = comp_borrow.reflect_branch.as_component_unchecked();
let refl_data = comp_borrow.data.unwrap();
reflect.bundle_insert(&mut bundle, refl_data);
} }
})?;
Ok(f) // defer the entity spawn
}); // safety: Commands borrows Entities from World, the resource borrows from the world resouces,
// they are borrowing different parts of World.
let world_ptr: *mut World = world;
let mut commands_queue = world.get_resource_mut::<CommandQueue>();
let mut commands = Commands::new(&mut commands_queue, unsafe { &mut *world_ptr });
let entity = commands.spawn(bundle);
methods.add_method("view", |lua, this, (system, queries): (mlua::Function, mlua::Variadic<mlua::AnyUserData>)| { Ok(ScriptEntity(entity))
if queries.is_empty() { })
panic!("No components were provided!"); .method_mut(
} "view",
|lua, this, (system, queries): (elua::Function, elua::ValueVec)| {
let world = unsafe { this.inner.as_ref() }; if queries.is_empty() {
let mut view = world.dynamic_view(); return Err(elua::Error::BadArgument {
func: Some("World:view".to_string()),
arg_index: 2,
arg_name: Some("query...".to_string()),
error: Arc::new(elua::Error::other(WorldError::LuaInvalidUsage(
"no component types provided".to_string(),
))),
});
}
for comp in queries.into_iter() { let world = unsafe { this.inner.as_ref() };
let reflect = comp.call_function::<_, ScriptBorrow>(FN_NAME_INTERNAL_REFLECT_TYPE, ()) let mut view = world.dynamic_view();
.expect("Type does not implement 'reflect_type' properly");
let refl_comp = reflect.reflect_branch.as_component_unchecked();
let dyn_type = QueryDynamicType::from_info(refl_comp.info); for (idx, comp) in queries.into_iter().enumerate() {
view.push(dyn_type); match comp {
} elua::Value::Table(t) => {
let name: String = t.get(elua::MetaMethod::Name)?;
let iter = view.into_iter(); let lookup = world
let mut reflected_iter = ReflectedIterator { .try_get_resource::<LuaTableProxyLookup>()
world: this.clone(), .ok_or(elua::Error::runtime(
dyn_view: DynamicViewIter::from(iter), "Unable to lookup table proxy, none were ever registered!",
reflected_components: None, ))?;
}; let info = lookup.comp_info_from_name.get(&name).ok_or_else(
|| elua::Error::BadArgument {
func: Some("World:view".to_string()),
arg_index: 2 + idx as i32,
arg_name: Some("query...".to_string()),
error: Arc::new(elua::Error::Runtime(format!(
"the 'Table' with name {} is unknown to the engine!",
name
))),
},
)?;
let reg = this.as_ref().get_resource::<TypeRegistry>(); let dyn_type = QueryDynamicType::from_info(info.clone());
view.push(dyn_type);
while let Some(row) = reflected_iter.next_lua(lua) {
let (reflects, values): (Vec<(_, _)>, Vec<_>) = row.into_iter().unzip();
let value_row: Vec<_> = values.into_iter().map(|ud| ud.into_lua(lua)).collect::<mlua::Result<Vec<mlua::Value>>>()?;
let mult_val = mlua::MultiValue::from_vec(value_row);
let res: mlua::MultiValue = system.call(mult_val)?;
// if values were returned, find the type in the type registry, and apply the new values
if res.len() <= reflects.len() {
for (i, comp) in res.into_iter().enumerate() {
let (_proxy, ptr) = reflects[i];
match comp.as_userdata() {
Some(ud) => {
let lua_comp = reflect_user_data(ud);
let refl_comp = lua_comp.reflect_branch.as_component_unchecked();
let lua_typeid = refl_comp.info.type_id.as_rust();
let reg_type = reg.get_type(lua_typeid).unwrap();
let proxy = reg_type.get_data::<ReflectLuaProxy>().unwrap();
(proxy.fn_apply)(lua, ptr, ud)?
} }
None => { elua::Value::Userdata(ud) => {
panic!("A userdata value was not returned!"); let reflect = ud
.execute_function::<_, ScriptBorrow>(
FN_NAME_INTERNAL_REFLECT_TYPE,
(),
)
.expect("Type does not implement 'reflect_type' properly");
let refl_comp = reflect.reflect_branch.as_component_unchecked();
let dyn_type = QueryDynamicType::from_info(refl_comp.info);
view.push(dyn_type);
} }
_ => todo!(),
} }
} }
} else {
let msg = format!("Too many arguments were returned from the World view!
At most, the expected number of results is {}.", reflects.len());
return Err(mlua::Error::external(WorldError::LuaInvalidUsage(msg)));
}
}
Ok(()) let iter = view.into_iter();
}); let mut reflected_iter = ReflectedIterator {
world: this.clone(),
dyn_view: DynamicViewIter::from(iter),
reflected_components: None,
};
let mut current = world.current_tick();
let mut has_ticked = false;
while let Some(row) = reflected_iter.next_lua(lua) {
let r = row
.row
.into_iter()
.map(|r| (r.comp_val, r.comp_ptr.cast::<()>()))
.collect::<Vec<_>>();
let (values, ptrs) =
itertools::multiunzip::<(Vec<elua::Value>, Vec<NonNull<()>>), _>(r);
let mult_val = elua::ValueVec::from(values);
let res: elua::ValueVec = system.exec(mult_val)?;
// if values were returned, find the type in the type registry, and apply the new values
if res.len() <= ptrs.len() {
// we only want to tick one time per system
if !has_ticked {
current = world.tick();
has_ticked = true;
}
for (comp, ptr) in res.into_iter().zip(ptrs) {
let lua_typeid = match &comp {
elua::Value::Userdata(ud) => {
let lua_comp = reflect_user_data(ud);
let refl_comp =
lua_comp.reflect_branch.as_component_unchecked();
refl_comp.info.type_id.as_rust()
}
elua::Value::Table(tbl) => {
let name: String = tbl.get(elua::MetaMethod::Name)?;
let lookup = world.get_resource::<LuaTableProxyLookup>();
*lookup.typeid_from_name.get(&name).unwrap()
}
_ => {
panic!("A userdata or table value was not returned!");
// TODO: Handle properly
}
};
// update the component tick
let world = unsafe { this.inner.as_mut() };
let arch = world.entity_archetype_mut(row.entity).unwrap();
let idx = arch.entities().get(&row.entity).unwrap().clone();
let c = arch.get_column_mut(lua_typeid.into()).unwrap();
c.entity_ticks[idx.0 as usize] = current;
// apply the new component data
let reg = this.as_ref().get_resource::<TypeRegistry>();
let reg_type = reg.get_type(lua_typeid).unwrap();
let proxy = reg_type
.get_data::<ReflectLuaProxy>()
// this should actually be safe since the ReflectedIterator
// attempts to get the type data before it is tried here
.expect("Type does not have ReflectLuaProxy as a TypeData");
(proxy.fn_apply)(lua, ptr, &comp)?;
}
} else {
let msg = format!(
"Too many arguments were returned from the World view!
At most, the expected number of results is {}.",
ptrs.len()
);
return Err(elua::Error::Runtime(msg));
}
}
Ok(())
},
)
.method_mut("resource", |lua, this, (ty,): (elua::Value,)| {
let reflect = match ty {
elua::Value::Userdata(ud) => ud
.execute_function::<_, ScriptBorrow>(FN_NAME_INTERNAL_REFLECT_TYPE, ())
.expect("Type does not implement 'reflect_type' properly"),
elua::Value::Table(t) => {
let f: elua::Function = t.get(FN_NAME_INTERNAL_REFLECT_TYPE)?;
f.exec::<_, ScriptBorrow>(())
.expect("Type does not implement 'reflect_type' properly")
}
_ => {
panic!("how");
}
};
/* let reflect = ty
.execute_function::<_, ScriptBorrow>(FN_NAME_INTERNAL_REFLECT_TYPE, ())
.expect("Type does not implement 'reflect_type' properly"); */
let res = reflect.reflect_branch.as_resource_unchecked();
if let Some(res_ptr) = res.reflect_ptr(this.as_mut()) {
let reg_type = this
.as_ref()
.get_type::<RegisteredType>(reflect.reflect_branch.reflect_type_id())
.expect("Resource is not type registered!");
let proxy = reg_type
.get_data::<ReflectLuaProxy>()
.expect("Type does not have ReflectLuaProxy as a TypeData");
(proxy.fn_as_lua)(lua, res_ptr.cast()).and_then(|ud| ud.as_lua(lua))
} else {
// if the resource is not found in the world, return nil
Ok(elua::Value::Nil)
}
})
.method_mut("add_resource", |_, this, res: elua::Value| {
let reflect = match res {
elua::Value::Userdata(ud) => ud
.execute_method::<_, ScriptBorrow>(FN_NAME_INTERNAL_REFLECT, ())
.expect("Type does not implement 'reflect_type' properly"),
elua::Value::Table(t) => {
let f: elua::Function = t.get(FN_NAME_INTERNAL_REFLECT)?;
f.exec::<_, ScriptBorrow>(())
.expect("Type does not implement 'reflect_type' properly")
}
_ => {
panic!("how");
}
};
let data = reflect.data
.expect("Its expected that 'FN_NAME_INTERNAL_REFLECT' returns data in World:add_resource");
let res = reflect.reflect_branch.as_resource()
.ok_or(elua::Error::runtime("Provided type is not a resource!"))?;
let world = this.as_mut();
res.insert(world, data);
Ok(())
})
.method_mut("request_res", |_, this, path: String| {
let world = this.as_mut();
let mut man = world.get_resource_mut::<ResourceManager>();
let handle = man.request_raw(&path).unwrap();
Ok(LuaResHandle::from(handle))
});
} }
} }

View File

@ -0,0 +1,40 @@
use std::any::TypeId;
use lyra_game::DeltaTime;
use crate::lua::LuaWrapper;
#[derive(Clone, Default)]
pub struct LuaDeltaTime(pub(crate) DeltaTime);
impl std::ops::Deref for LuaDeltaTime {
type Target = DeltaTime;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::ops::DerefMut for LuaDeltaTime {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<'lua> elua::FromLua<'lua> for LuaDeltaTime {
fn from_lua(_: &'lua elua::State, _: elua::Value<'lua>) -> elua::Result<Self> {
todo!()
}
}
impl<'lua> elua::AsLua<'lua> for LuaDeltaTime {
fn as_lua(self, _: &'lua elua::State) -> elua::Result<elua::Value<'lua>> {
Ok(elua::Value::Number(*self.0 as f64))
}
}
impl LuaWrapper for LuaDeltaTime {
fn wrapped_type_id() -> std::any::TypeId {
TypeId::of::<DeltaTime>()
}
}

View File

@ -0,0 +1,185 @@
use lyra_game::input::{keycode_from_str, Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource, ActionState, LayoutId, MouseAxis, MouseInput};
use crate::lyra_engine;
use lyra_reflect::Reflect;
use crate::{lua::{LuaWrapper, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE}, ScriptBorrow};
#[derive(Clone, Reflect)]
pub struct LuaActionHandler {
handler: ActionHandler
}
impl elua::Userdata for LuaActionHandler {
fn name() -> String {
"ActionHandler".to_string()
}
fn build<'a>(builder: &mut elua::UserdataBuilder<'a, Self>) {
builder.function("new", |_, table: elua::Table| {
let mut handler = ActionHandler::new();
// create the layouts and add them to the handler
let layouts = table.get::<_, elua::Table>("layouts")
.map_err(|_| elua::Error::runtime("missing 'layouts' in ActionHandler table"))?;
for layout_id in layouts.sequence_iter::<u32>() {
let layout_id = layout_id?;
handler.add_layout(LayoutId(layout_id));
}
// add the actions to the handler
let actions = table.get::<_, elua::Table>("actions")
.map_err(|_| elua::Error::runtime("missing 'actions' in ActionHandler table"))?;
for pair in actions.pairs::<String, String>() {
let (action_lbl, action_type) = pair?;
let action_type = action_type.to_lowercase();
let action_type = match action_type.as_str() {
"axis" => ActionKind::Axis,
"button" => ActionKind::Button,
_ => todo!("Handle invalid axis type"),
};
handler.add_action(action_lbl, Action::new(action_type));
}
// find the mappings and start processing them
let mappings= table.get::<_, elua::Table>("mappings")
.map_err(|_| elua::Error::runtime("missing 'mappings' in ActionHandler table"))?;
for (map_id, tbl) in mappings.sequence_iter::<elua::Table>().enumerate() {
let tbl = tbl?;
let layout_id = tbl.get::<_, u32>("layout")?;
let mut mapping = ActionMapping::new(LayoutId(layout_id), ActionMappingId(map_id as u32));
// find the binds and start processing them
// the keys are used as the action names, and then the value is an array (lua table)
let binds_tbl = tbl.get::<_, elua::Table>("binds")
.map_err(|_| elua::Error::runtime("missing 'binds' in ActionHandler 'mappings' table"))?;
for pair in binds_tbl.pairs::<String, elua::Table>() {
let (action_lbl, input_binds) = pair?;
for input in input_binds.sequence_iter::<String>() {
let input = input?.to_lowercase();
let action = handler.action(&action_lbl)
.ok_or(elua::Error::Runtime(format!("Unknown action specified in mapping binds: {}", action_lbl)))?;
let mut binds = Vec::new();
let input_split: Vec<&str> = input.split(":").collect();
let input_name = input_split[0];
let button = input_split[1];
if action.kind == ActionKind::Axis {
if button == "axis" {
let axis_name = input_split[2];
let src = process_axis_string(input_name, axis_name)
.ok_or(elua::Error::Runtime(format!("invalid bind '{input_name}', unable to find device or axis for device")))?;
binds.push(src.into_binding());
} else {
// splits 'down=1' into 'down' and '1'
let (button, val_str) = button.split_once("=")
.ok_or(elua::Error::Runtime(format!("invalid bind string for Axis Action: '{input}' (expected '=' with float)")))?;
let val = val_str.parse::<f32>()
.map_err(|e| elua::Error::Runtime(format!("invalid bind string for Axis Action: '{input}' ({e})")))?;
let src = process_keyboard_string(button)
.ok_or(elua::Error::Runtime(format!("invalid key in bind: '{button}'")))?;
binds.push(src.into_binding_modifier(val));
}
} else {
todo!("Process bindings for Button Actions");
}
mapping.bind(action_lbl.clone(), &binds);
}
}
handler.add_mapping(mapping);
}
Ok(LuaActionHandler {
handler,
})
})
.method("get_axis", |_, this, action: String| {
Ok(this.handler.get_axis_modifier(action))
})
.method("is_pressed", |_, this, action: String| {
Ok(this.handler.is_action_pressed(action))
})
.method("was_just_pressed", |_, this, action: String| {
Ok(this.handler.was_action_just_pressed(action))
})
.method("was_just_released", |_, this, action: String| {
Ok(this.handler.was_action_just_released(action))
})
.method("get_just_pressed", |_, this, action: String| {
Ok(this.handler.get_just_pressed_modifier(action))
})
.method("get_action_state", |lua, this, action: String| {
let state = this.handler.get_action_state(action);
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)),
};
let mut multi = elua::ValueVec::new();
multi.push_val(lua, name)?;
multi.push_val(lua, val)?;
Ok(elua::Value::Multi(multi))
})
.method(FN_NAME_INTERNAL_REFLECT, |_, this, ()| {
Ok(ScriptBorrow::from_resource::<ActionHandler>(Some(this.handler.clone())))
})
.function(FN_NAME_INTERNAL_REFLECT_TYPE, |_, ()| {
Ok(ScriptBorrow::from_resource::<ActionHandler>(None))
});
}
}
impl<'a> elua::FromLua<'a> for LuaActionHandler {
fn from_lua(_: &'a elua::State, val: elua::Value<'a>) -> elua::Result<Self> {
let tyname = val.type_name();
let ud = val.as_userdata()
.ok_or(elua::Error::type_mismatch("ActionHandler", &tyname))?;
let handle = ud.as_ref::<LuaActionHandler>()?;
Ok(handle.clone())
}
}
impl LuaWrapper for LuaActionHandler {
fn wrapped_type_id() -> std::any::TypeId {
std::any::TypeId::of::<ActionHandler>()
}
}
fn process_keyboard_string(key_name: &str) -> Option<ActionSource> {
let key = keycode_from_str(key_name)?;
Some(ActionSource::Keyboard(key))
}
fn process_axis_string(device: &str, axis_name: &str) -> Option<ActionSource> {
match device {
"mouse" => match axis_name {
"y" => Some(ActionSource::Mouse(MouseInput::Axis(MouseAxis::Y))),
"x" => Some(ActionSource::Mouse(MouseInput::Axis(MouseAxis::X))),
"scroll" | "scrollwheel" => Some(ActionSource::Mouse(MouseInput::Axis(MouseAxis::ScrollWheel))),
_ => None,
},
_ => None
}
}

View File

@ -0,0 +1,649 @@
use std::sync::Arc;
use crate::lyra_engine;
use lyra_game::math;
use lyra_scripting_derive::wrap_math_vec_copy;
use crate as lyra_scripting;
// f32 types
wrap_math_vec_copy!(
math::Vec2,
derives(PartialEq),
fields(x, y),
metamethods(
Add(LuaVec2, f32),
Sub(LuaVec2, f32),
Div(LuaVec2, f32),
Mul(LuaVec2, f32),
Mod(LuaVec2, f32),
Eq, Unm, ToString
),
custom_methods {
builder.method_mut("move_by", |lua, this, vals: elua::ValueVec| {
let vals_clone = vals.clone();
if let Some((x, y)) = vals.try_into_vals::<(f32, f32)>(lua)? {
this.x += x;
this.y += y;
} else if let Some(v) = vals_clone.try_into_vals::<Self>(lua)? {
this.0 += v.0;
}
Ok(())
});
}
);
wrap_math_vec_copy!(
math::Vec3,
derives(PartialEq),
fields(x, y, z),
metamethods(
Add(LuaVec3, f32),
Sub(LuaVec3, f32),
Div(LuaVec3, f32),
Mul(LuaVec3, f32),
Mod(LuaVec3, f32),
Eq, Unm, ToString
),
custom_methods {
builder.method_mut("move_by", |lua, this, vals: elua::ValueVec| {
let vals_clone = vals.clone();
if let Some((x, y, z)) = vals.try_into_vals::<(f32, f32, f32)>(lua)? {
this.x += x;
this.y += y;
this.z += z;
} else if let Some(v) = vals_clone.try_into_vals::<Self>(lua)? {
this.0 += v.0;
}
Ok(())
});
}
);
wrap_math_vec_copy!(
math::Vec4,
derives(PartialEq),
fields(w, x, y, z),
metamethods(
Add(LuaVec4, f32),
Sub(LuaVec4, f32),
Div(LuaVec4, f32),
Mul(LuaVec4, f32),
Mod(LuaVec4, f32),
Eq, Unm
)
);
// =================================================
/* wrap_math_vec_copy!(
math::Vec3A,
derives(PartialEq),
fields(x, y, z),
metamethods(
Add(LuaVec3A, f32),
Sub(LuaVec3A, f32),
Div(LuaVec3A, f32),
Mul(LuaVec3A, f32),
Mod(LuaVec3A, f32),
Eq, Unm
)
);
// f64 types
wrap_math_vec_copy!(
math::DVec2,
derives(PartialEq),
fields(x, y),
metamethods(
Add(LuaDVec2, f64),
Sub(LuaDVec2, f64),
Div(LuaDVec2, f64),
Mul(LuaDVec2, f64),
Mod(LuaDVec2, f64),
Eq, Unm
)
);
wrap_math_vec_copy!(
math::DVec3,
derives(PartialEq),
fields(x, y, z),
metamethods(
Add(LuaDVec3, f64),
Sub(LuaDVec3, f64),
Div(LuaDVec3, f64),
Mul(LuaDVec3, f64),
Mod(LuaDVec3, f64),
Eq, Unm
)
);
wrap_math_vec_copy!(
math::DVec4,
derives(PartialEq),
fields(w, x, y, z),
metamethods(
Add(LuaDVec4, f64),
Sub(LuaDVec4, f64),
Div(LuaDVec4, f64),
Mul(LuaDVec4, f64),
Mod(LuaDVec4, f64),
Eq, Unm
)
);
// i32 types
wrap_math_vec_copy!(
math::IVec2,
derives(PartialEq, Eq, Hash),
fields(x, y),
metamethods(
Add(LuaIVec2, i32),
Sub(LuaIVec2, i32),
Div(LuaIVec2, i32),
Mul(LuaIVec2, i32),
Mod(LuaIVec2, i32),
Shl(LuaIVec2, LuaUVec2, i32),
Shr(LuaIVec2, LuaUVec2, i32),
BAnd(LuaIVec2, i32),
BOr(LuaIVec2, i32),
BXor(LuaIVec2, i32),
Eq, Unm, BNot
)
);
wrap_math_vec_copy!(
math::IVec3,
derives(PartialEq, Eq, Hash),
fields(x, y, z),
metamethods(
Add(LuaIVec3, i32),
Sub(LuaIVec3, i32),
Div(LuaIVec3, i32),
Mul(LuaIVec3, i32),
Mod(LuaIVec3, i32),
Shl(LuaIVec3, LuaUVec3, i32),
Shr(LuaIVec3, LuaUVec3, i32),
BAnd(LuaIVec3, i32),
BOr(LuaIVec3, i32),
BXor(LuaIVec3, i32),
Eq, Unm, BNot
)
);
wrap_math_vec_copy!(
math::IVec4,
derives(PartialEq, Eq, Hash),
fields(w, x, y, z),
metamethods(
Add(LuaIVec4, i32),
Sub(LuaIVec4, i32),
Div(LuaIVec4, i32),
Mul(LuaIVec4, i32),
Mod(LuaIVec4, i32),
Shl(LuaIVec4, LuaUVec4, i32),
Shr(LuaIVec4, LuaUVec4, i32),
BAnd(LuaIVec4, i32),
BOr(LuaIVec4, i32),
BXor(LuaIVec4, i32),
Eq, Unm, BNot
)
);
// u32 types
wrap_math_vec_copy!(
math::UVec2,
derives(PartialEq, Eq, Hash),
fields(x, y),
metamethods(
Add(LuaUVec2, u32),
Sub(LuaUVec2, u32),
Div(LuaUVec2, u32),
Mul(LuaUVec2, u32),
Mod(LuaUVec2, u32),
Shl(LuaUVec2, LuaIVec2, i32),
Shr(LuaUVec2, LuaIVec2, i32),
BAnd(LuaUVec2, u32),
BOr(LuaUVec2, u32),
BXor(LuaUVec2, u32),
Eq, BNot
)
);
wrap_math_vec_copy!(
math::UVec3,
derives(PartialEq, Eq, Hash),
fields(x, y, z),
metamethods(
Add(LuaUVec3, u32),
Sub(LuaUVec3, u32),
Div(LuaUVec3, u32),
Mul(LuaUVec3, u32),
Mod(LuaUVec3, u32),
Shl(LuaUVec3, LuaIVec3, i32),
Shr(LuaUVec3, LuaIVec3, i32),
BAnd(LuaUVec3, u32),
BOr(LuaUVec3, u32),
BXor(LuaUVec3, u32),
Eq, BNot
)
);
wrap_math_vec_copy!(
math::UVec4,
derives(PartialEq, Eq, Hash),
fields(w, x, y, z),
metamethods(
Add(LuaUVec4, u32),
Sub(LuaUVec4, u32),
Div(LuaUVec4, u32),
Mul(LuaUVec4, u32),
Mod(LuaUVec4, u32),
Shl(LuaUVec4, LuaIVec4, i32),
Shr(LuaUVec4, LuaIVec4, i32),
BAnd(LuaUVec4, u32),
BOr(LuaUVec4, u32),
BXor(LuaUVec4, u32),
Eq, BNot
)
);
// i64 types
wrap_math_vec_copy!(
math::I64Vec2,
derives(PartialEq, Eq, Hash),
fields(x, y),
metamethods(
Add(LuaI64Vec2, i64),
Sub(LuaI64Vec2, i64),
Div(LuaI64Vec2, i64),
Mul(LuaI64Vec2, i64),
Mod(LuaI64Vec2, i64),
Shl(i64),
Shr(i64),
BAnd(LuaI64Vec2, i64),
BOr(LuaI64Vec2, i64),
BXor(LuaI64Vec2, i64),
Eq, BNot
)
);
wrap_math_vec_copy!(
math::I64Vec3,
derives(PartialEq, Eq, Hash),
fields(x, y, z),
metamethods(
Add(LuaI64Vec3, i64),
Sub(LuaI64Vec3, i64),
Div(LuaI64Vec3, i64),
Mul(LuaI64Vec3, i64),
Mod(LuaI64Vec3, i64),
Shl(i64),
Shr(i64),
BAnd(LuaI64Vec3, i64),
BOr(LuaI64Vec3, i64),
BXor(LuaI64Vec3, i64),
Eq, BNot
)
);
wrap_math_vec_copy!(
math::I64Vec4,
derives(PartialEq, Eq, Hash),
fields(w, x, y, z),
metamethods(
Add(LuaI64Vec4, i64),
Sub(LuaI64Vec4, i64),
Div(LuaI64Vec4, i64),
Mul(LuaI64Vec4, i64),
Mod(LuaI64Vec4, i64),
Shl(i64),
Shr(i64),
BAnd(LuaI64Vec4, i64),
BOr(LuaI64Vec4, i64),
BXor(LuaI64Vec4, i64),
Eq, BNot
)
);
// u64 types
wrap_math_vec_copy!(
math::U64Vec2,
derives(PartialEq, Eq, Hash),
fields(x, y),
metamethods(
Add(LuaU64Vec2, u64),
Sub(LuaU64Vec2, u64),
Div(LuaU64Vec2, u64),
Mul(LuaU64Vec2, u64),
Mod(LuaU64Vec2, u64),
Shl(i64),
Shr(i64),
BAnd(LuaU64Vec2, u64),
BOr(LuaU64Vec2, u64),
BXor(LuaU64Vec2, u64),
Eq, BNot
)
);
wrap_math_vec_copy!(
math::U64Vec3,
derives(PartialEq, Eq, Hash),
fields(x, y, z),
metamethods(
Add(LuaU64Vec3, u64),
Sub(LuaU64Vec3, u64),
Div(LuaU64Vec3, u64),
Mul(LuaU64Vec3, u64),
Mod(LuaU64Vec3, u64),
Shl(i64),
Shr(i64),
BAnd(LuaU64Vec3, u64),
BOr(LuaU64Vec3, u64),
BXor(LuaU64Vec3, u64),
Eq, BNot
)
);
wrap_math_vec_copy!(
math::U64Vec4,
derives(PartialEq, Eq, Hash),
fields(w, x, y, z),
metamethods(
Add(LuaU64Vec4, u64),
Sub(LuaU64Vec4, u64),
Div(LuaU64Vec4, u64),
Mul(LuaU64Vec4, u64),
Mod(LuaU64Vec4, u64),
Shl(i64),
Shr(i64),
BAnd(LuaU64Vec4, u64),
BOr(LuaU64Vec4, u64),
BXor(LuaU64Vec4, u64),
Eq, BNot
)
);*/
// bool types
/* wrap_math_vec_copy!(
math::BVec2,
derives(PartialEq, Eq, Hash),
fields(x, y),
metamethods(Eq, BAnd, BOr, BXOr, BNot)
);
wrap_math_vec_copy!(
math::BVec3,
derives(PartialEq, Eq, Hash),
fields(x, y, z),
metamethods(Eq, BAnd, BOr, BXOr, BNot)
);
wrap_math_vec_copy!(
math::BVec4,
derives(PartialEq, Eq, Hash),
fields(w, x, y, z),
metamethods(Eq, BAnd, BOr, BXOr, BNot)
); */
// mat2
/* wrap_math_vec_copy!(
math::Mat2,
derives(PartialEq),
no_new,
matrix {
col_type = LuaVec2
},
metamethods(
Eq,
Add,
Sub,
Mul(LuaMat2, f32),
Unm
)
); */
// TODO
/* wrap_math_vec_copy!(
math::Mat4,
derives(PartialEq),
no_new,
matrix {
col_type = LuaVec4
},
metamethods(
Eq,
Add,
Sub,
Mul(LuaMat4, f32),
Unm
)
); */
// ============================================================
/// A macro that generates field getters and setters for lua wrapped types.
macro_rules! wrapped_field_getsetters {
($builder: ident, $name: literal, $field: ident, $type: ident) => {
$builder.field_getter($name, |_, this| {
Ok($type(this.$field))
});
$builder.field_setter($name, |_, this, v: $type| {
this.$field = *v;
Ok(())
});
};
}
/// A macro that generates field getters and setters for types that already implement As/FromLua.
macro_rules! field_getsetters {
($builder: ident, $name: literal, $field: ident, $type: ty) => {
$builder.field_getter($name, |_, this| {
Ok(this.$field)
});
$builder.field_setter($name, |_, this, v: $type| {
this.$field = v;
Ok(())
});
};
}
wrap_math_vec_copy!(
math::Quat,
derives(PartialEq),
no_new,
metamethods(
Eq,
// __mul for LuaVec3 is manually implemented below since it doesn't return Self
//Mul(LuaQuat, f32),
Add,
Sub,
Div(f32),
),
custom_fields {
field_getsetters!(builder, "x", x, f32);
field_getsetters!(builder, "y", y, f32);
field_getsetters!(builder, "z", z, f32);
field_getsetters!(builder, "w", w, f32);
},
custom_methods {
// manually implemented since Quat doesn't have a `new` function
builder.function("new", |_, (x, y, z, w)| {
Ok(Self(math::Quat::from_xyzw(x, y, z, w)))
});
builder.function("from_rotation_x", |_, (rad,)| {
let q = math::Quat::from_rotation_x(rad);
Ok(Self(q))
});
builder.function("from_rotation_y", |_, (rad,)| {
let q = math::Quat::from_rotation_y(rad);
Ok(Self(q))
});
builder.function("from_rotation_z", |_, (rad,)| {
let q = math::Quat::from_rotation_z(rad);
Ok(Self(q))
});
builder.method("dot", |_, this, (rhs,): (Self,)| {
Ok(this.dot(rhs.0))
});
builder.method("length", |_, this, ()| {
Ok(this.length())
});
builder.method("length_squared", |_, this, ()| {
Ok(this.length_squared())
});
builder.method_mut("normalize", |_, this, ()| {
this.0 = this.normalize();
Ok(())
});
builder.method_mut("mult_quat", |_, this, (rhs,): (Self,)| {
this.0 *= rhs.0;
Ok(())
});
builder.method("mult_vec3", |_, this, (rhs,): (LuaVec3,)| {
Ok(LuaVec3(this.0 * rhs.0))
});
// manually implemented here since multiplying may not return `Self`.
builder.meta_method(elua::MetaMethod::Mul, |lua, this, (val,): (elua::Value,)| {
use elua::AsLua;
match val {
elua::Value::Userdata(ud) => {
if ud.is::<LuaVec3>()? {
let v3 = ud.as_ref::<LuaVec3>()?;
LuaVec3(this.0 * v3.0)
.as_lua(lua)
} else {
let quat = ud.as_ref::<LuaQuat>()?;
LuaQuat(this.0 * quat.0)
.as_lua(lua)
}
},
elua::Value::Number(n) => {
LuaQuat(this.0 * (n as f32))
.as_lua(lua)
},
_ => {
todo!()
}
}
});
builder.method("lerp", |_, this, (rhs, alpha): (Self, f32)| {
Ok(Self(this.lerp(*rhs, alpha)))
});
}
);
wrap_math_vec_copy!(
math::Transform,
derives(PartialEq),
no_new,
metamethods(ToString, Eq),
custom_fields {
wrapped_field_getsetters!(builder, "translation", translation, LuaVec3);
wrapped_field_getsetters!(builder, "rotation", rotation, LuaQuat);
wrapped_field_getsetters!(builder, "scale", scale, LuaVec3);
},
custom_methods {
builder.function("default", |_, ()| {
Ok(Self(math::Transform::default()))
});
builder.function("new", |_, (pos, rot, scale): (LuaVec3, LuaQuat, LuaVec3)| {
Ok(Self(math::Transform::new(*pos, *rot, *scale)))
});
builder.function("from_translation", |_, (pos,): (LuaVec3,)| {
Ok(Self(math::Transform::from_translation(*pos)))
});
builder.function("from_xyz", |_, (x, y, z)| {
Ok(Self(math::Transform::from_xyz(x, y, z)))
});
builder.method("clone", |_, this, ()| {
Ok(this.clone())
});
builder.method("forward", |_, this, ()| {
Ok(LuaVec3(this.forward()))
});
builder.method("left", |_, this, ()| {
Ok(LuaVec3(this.left()))
});
builder.method("up", |_, this, ()| {
Ok(LuaVec3(this.up()))
});
builder.method_mut("rotate", |_, this, (quat,): (LuaQuat,)| {
this.rotate(*quat);
Ok(())
});
builder.method_mut("rotate_x", |_, this, (deg,): (f32,)| {
this.rotate_x(math::Angle::Degrees(deg));
Ok(())
});
builder.method_mut("rotate_y", |_, this, (deg,): (f32,)| {
this.rotate_y(math::Angle::Degrees(deg));
Ok(())
});
builder.method_mut("rotate_z", |_, this, (deg,): (f32,)| {
this.rotate_z(math::Angle::Degrees(deg));
Ok(())
});
builder.method_mut("rotate_x_rad", |_, this, (rad,): (f32,)| {
this.rotate_x(math::Angle::Radians(rad));
Ok(())
});
builder.method_mut("rotate_y_rad", |_, this, (rad,): (f32,)| {
this.rotate_y(math::Angle::Radians(rad));
Ok(())
});
builder.method_mut("rotate_z_rad", |_, this, (rad,): (f32,)| {
this.rotate_z(math::Angle::Radians(rad));
Ok(())
});
builder.method_mut("translate", |_, this, (x, y, z): (f32, f32, f32)| {
this.translate(x, y, z);
Ok(())
});
builder.method("lerp", |_, this, (rhs, alpha): (Self, f32)| {
Ok(Self(this.lerp(*rhs, alpha)))
});
// rotate a transform
builder.meta_method(elua::MetaMethod::Mul, |_, this, (quat,): (LuaQuat,)| {
let mut t = *this;
t.rotation *= *quat;
Ok(t)
});
// move a transform
builder.meta_method(elua::MetaMethod::Add, |_, this, (pos,): (LuaVec3,)| {
let mut t = *this;
t.translation += *pos;
Ok(t)
});
}
);

View File

@ -0,0 +1,14 @@
pub mod math;
pub use math::*;
pub mod delta_time;
pub use delta_time::*;
pub mod res_handle;
pub use res_handle::*;
pub mod model_comp;
pub use model_comp::*;
pub mod input_actions;
pub use input_actions::*;

View File

@ -0,0 +1,69 @@
use std::any::TypeId;
use std::{cell::Ref, sync::Arc};
use elua::FromLua;
use lyra_game::scene::ModelComponent;
use lyra_reflect::Reflect;
use lyra_resource::{Model, ResHandle};
use crate::lua::LuaWrapper;
use crate::lyra_engine;
use crate::{lua::{FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE}, ScriptBorrow};
use super::LuaResHandle;
#[derive(Clone, Reflect)]
pub struct LuaModelComponent(pub ModelComponent);
impl elua::Userdata for LuaModelComponent {
fn name() -> String {
"ModelComponent".to_string()
}
fn build<'a>(builder: &mut elua::UserdataBuilder<'a, Self>) {
builder
.function("new", |_, model: Ref<LuaResHandle>| {
let res = model.0.clone();
let res_any = res.as_arc_any();
match res_any.downcast::<ResHandle<Model>>() {
Ok(handle) => {
let res = ResHandle::<Model>::clone(&handle);
Ok(Self(ModelComponent(res)))
},
Err(_) => {
Err(elua::Error::BadArgument {
func: Some("ModelComponent:new".to_string()),
arg_index: 1,
arg_name: Some("model".to_string()),
error: Arc::new(
elua::Error::runtime("resource handle is not a handle to a Model")
)
})
}
}
})
.function(FN_NAME_INTERNAL_REFLECT_TYPE, |_, ()| {
Ok(ScriptBorrow::from_component::<ModelComponent>(None))
})
.method(FN_NAME_INTERNAL_REFLECT, |_, this, ()| {
Ok(ScriptBorrow::from_component(Some(this.0.clone())))
});
}
}
impl<'a> FromLua<'a> for LuaModelComponent {
fn from_lua(_: &'a elua::State, val: elua::Value<'a>) -> elua::Result<Self> {
let tyname = val.type_name();
let ud = val.as_userdata()
.ok_or(elua::Error::type_mismatch("Model", &tyname))?;
let ud = ud.as_ref::<LuaModelComponent>()?;
Ok(ud.clone())
}
}
impl LuaWrapper for LuaModelComponent {
fn wrapped_type_id() -> std::any::TypeId {
TypeId::of::<ModelComponent>()
}
}

View File

@ -0,0 +1,77 @@
use std::{ops::Deref, sync::Arc};
use elua::{AsLua, FromLua};
use lyra_game::scene::ModelComponent;
use lyra_resource::{Model, ResHandle, ResourceStorage};
use crate::lua::FN_NAME_INTERNAL_AS_COMPONENT;
use super::LuaModelComponent;
#[derive(Clone)]
pub struct LuaResHandle(pub Arc<dyn ResourceStorage>);
impl Deref for LuaResHandle {
type Target = dyn ResourceStorage;
fn deref(&self) -> &Self::Target {
self.0.deref()
}
}
impl From<Arc<dyn ResourceStorage>> for LuaResHandle {
fn from(value: Arc<dyn ResourceStorage>) -> Self {
LuaResHandle(value)
}
}
impl elua::Userdata for LuaResHandle {
fn name() -> String {
"Handle".to_string()
}
fn build<'a>(builder: &mut elua::UserdataBuilder<'a, Self>) {
builder.field_getter("path", |_, this| Ok(this.path()));
builder.field_getter("version", |_, this| Ok(this.version()));
builder.field_getter("uuid", |_, this| Ok(this.uuid().to_string()));
builder.field_getter("state", |_, this| {
let name = match this.state() {
lyra_resource::ResourceState::Loading => "loading",
lyra_resource::ResourceState::Ready => "ready",
};
Ok(name)
});
builder.method("is_watched", |_, this, ()| {
Ok(this.is_watched())
});
builder.method("is_loaded", |_, this, ()| {
Ok(this.is_loaded())
});
builder.method(FN_NAME_INTERNAL_AS_COMPONENT, |lua, this, ()| {
let any = this.0.as_any();
match any.downcast_ref::<ResHandle<Model>>() {
Some(model) => {
LuaModelComponent(ModelComponent(model.clone())).as_lua(lua)
},
None => {
Ok(elua::Value::Nil)
}
}
});
}
}
impl<'a> FromLua<'a> for LuaResHandle {
fn from_lua(_: &'a elua::State, val: elua::Value<'a>) -> elua::Result<Self> {
let tyname = val.type_name();
let ud = val.as_userdata()
.ok_or(elua::Error::type_mismatch("Handle", &tyname))?;
let handle = ud.as_ref::<LuaResHandle>()?;
Ok(handle.clone())
}
}

View File

@ -1,8 +1,12 @@
use std::sync::atomic::{AtomicU64, Ordering};
use lyra_ecs::Component; use lyra_ecs::Component;
use lyra_resource::ResHandle; use lyra_resource::ResHandle;
use crate::lyra_engine; use crate::lyra_engine;
static SCRIPT_ID_COUNTER: AtomicU64 = AtomicU64::new(0);
#[derive(Clone)] #[derive(Clone)]
pub struct Script<T> { pub struct Script<T> {
res: ResHandle<T>, res: ResHandle<T>,
@ -15,7 +19,7 @@ impl<T> Script<T> {
Self { Self {
res: script, res: script,
name: name.to_string(), name: name.to_string(),
id: 0 // TODO: make a counter id: SCRIPT_ID_COUNTER.fetch_add(1, Ordering::AcqRel)
} }
} }

View File

@ -5,6 +5,14 @@ use lyra_ecs::{world::World, Entity};
#[derive(Clone)] #[derive(Clone)]
pub struct ScriptEntity(pub Entity); pub struct ScriptEntity(pub Entity);
impl std::ops::Deref for ScriptEntity {
type Target = Entity;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Clone)] #[derive(Clone)]
pub struct ScriptWorldPtr { pub struct ScriptWorldPtr {
pub inner: NonNull<World>, pub inner: NonNull<World>,

View File

@ -12,9 +12,11 @@ mkShell rec {
heaptrack heaptrack
mold mold
udev udev
lua5_4_compat
]; ];
buildInputs = [ buildInputs = [
udev alsa-lib vulkan-loader udev alsa-lib libGL gcc
vulkan-loader vulkan-headers vulkan-tools
xorg.libX11 xorg.libXcursor xorg.libXi xorg.libXrandr # To use the x11 feature xorg.libX11 xorg.libXcursor xorg.libXi xorg.libXrandr # To use the x11 feature
libxkbcommon wayland # To use the wayland feature libxkbcommon wayland # To use the wayland feature
]; ];