Merge pull request 'Create an early scripting engine' (#2) from feature/early-scripting into main
Reviewed-on: #2
This commit is contained in:
commit
71693971c2
|
@ -0,0 +1,3 @@
|
|||
[submodule "lyra-scripting/elua"]
|
||||
path = lyra-scripting/elua
|
||||
url = git@git.seanomik.net:SeanOMik/elua.git
|
|
@ -72,7 +72,7 @@
|
|||
"--no-run",
|
||||
"--lib",
|
||||
"--package=lyra-ecs",
|
||||
"world::tests::view_change_tracking",
|
||||
"command::tests::deferred_commands",
|
||||
"--",
|
||||
"--exact --nocapture"
|
||||
],
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -10,11 +10,12 @@ members = [
|
|||
"lyra-ecs",
|
||||
"lyra-reflect",
|
||||
"lyra-scripting",
|
||||
"lyra-game"]
|
||||
"lyra-game", "lyra-math"]
|
||||
|
||||
[features]
|
||||
scripting = ["dep:lyra-scripting"]
|
||||
lua_scripting = ["scripting", "lyra-scripting/lua"]
|
||||
|
||||
[dependencies]
|
||||
lyra-game = { path = "lyra-game" }
|
||||
lyra-scripting = { path = "lyra-scripting", optional = true }
|
||||
lyra-scripting = { path = "lyra-scripting", optional = true }
|
||||
|
|
|
@ -6,7 +6,8 @@ edition = "2021"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[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"}
|
||||
anyhow = "1.0.75"
|
||||
async-std = "1.12.0"
|
||||
|
|
|
@ -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 ]]
|
|
@ -55,9 +55,9 @@ pub fn free_fly_camera_controller(delta_time: Res<DeltaTime>, handler: Res<Actio
|
|||
let left = cam.transform.left();
|
||||
let up = Vec3::Y;
|
||||
|
||||
let move_y = handler.get_axis_modifier(CommonActionLabel::MoveUpDown).unwrap_or(0.0);
|
||||
let move_x = handler.get_axis_modifier(CommonActionLabel::MoveLeftRight).unwrap_or(0.0);
|
||||
let move_z = handler.get_axis_modifier(CommonActionLabel::MoveForwardBackward).unwrap_or(0.0);
|
||||
let move_y = handler.get_axis_modifier("MoveUpDown").unwrap_or(0.0);
|
||||
let move_x = handler.get_axis_modifier("MoveLeftRight").unwrap_or(0.0);
|
||||
let move_z = handler.get_axis_modifier("MoveForwardBackward").unwrap_or(0.0);
|
||||
|
||||
let mut velocity = Vec3::ZERO;
|
||||
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
|
||||
}
|
||||
|
||||
let motion_x = handler.get_axis_modifier(CommonActionLabel::LookLeftRight).unwrap_or(0.0);
|
||||
let motion_y = handler.get_axis_modifier(CommonActionLabel::LookUpDown).unwrap_or(0.0);
|
||||
let motion_z = handler.get_axis_modifier(CommonActionLabel::LookRoll).unwrap_or(0.0);
|
||||
let motion_x = handler.get_axis_modifier("LookLeftRight").unwrap_or(0.0);
|
||||
let motion_y = handler.get_axis_modifier("LookUpDown").unwrap_or(0.0);
|
||||
let motion_z = handler.get_axis_modifier("LookRoll").unwrap_or(0.0);
|
||||
|
||||
let mut camera_rot = Vec3::ZERO;
|
||||
camera_rot.y -= motion_x * fly.mouse_sensitivity;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
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};
|
||||
|
||||
mod free_fly_camera;
|
||||
|
@ -77,29 +77,29 @@ struct CubeFlag;
|
|||
async fn main() {
|
||||
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_visible = false;
|
||||
window_options.cursor_visible = false; */
|
||||
}
|
||||
|
||||
let mut resman = world.get_resource_mut::<ResourceManager>();
|
||||
//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/texture-sep/texture-sep.gltf").unwrap();
|
||||
let crate_model = resman.request::<Model>("assets/crate/crate.gltf").unwrap();
|
||||
drop(resman);
|
||||
|
||||
world.spawn((
|
||||
/* world.spawn((
|
||||
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);
|
||||
//cube_tran.rotate_y(math::Angle::Degrees(180.0));
|
||||
world.spawn((
|
||||
TransformComponent::from(cube_tran),
|
||||
cube_tran,
|
||||
ModelComponent(crate_model.clone()),
|
||||
CubeFlag,
|
||||
));
|
||||
|
@ -117,7 +117,7 @@ async fn main() {
|
|||
diffuse: 1.0,
|
||||
specular: 1.3,
|
||||
},
|
||||
TransformComponent::from(light_tran),
|
||||
light_tran,
|
||||
ModelComponent(cube_model.clone()),
|
||||
));
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ async fn main() {
|
|||
diffuse: 7.0,
|
||||
specular: 1.0,
|
||||
},
|
||||
TransformComponent::from(light_tran),
|
||||
Transform::from(light_tran),
|
||||
ModelComponent(cube_model.clone()),
|
||||
));
|
||||
}
|
||||
|
@ -183,7 +183,7 @@ async fn main() {
|
|||
diffuse: 1.0,
|
||||
specular: 1.3,
|
||||
},
|
||||
TransformComponent::from(light_tran),
|
||||
Transform::from(light_tran),
|
||||
ModelComponent(cube_model),
|
||||
));
|
||||
}
|
||||
|
@ -206,7 +206,7 @@ async fn main() {
|
|||
Ok(())
|
||||
};
|
||||
let fps_plugin = move |game: &mut Game| {
|
||||
let world = game.world();
|
||||
let world = game.world_mut();
|
||||
world.add_resource(fps_counter::FPSCounter::new());
|
||||
};
|
||||
|
||||
|
@ -214,13 +214,13 @@ async fn main() {
|
|||
const SPEED: f32 = 4.0;
|
||||
let delta_time = **world.get_resource::<DeltaTime>();
|
||||
|
||||
for (mut transform, _) in world.view_iter::<(&mut TransformComponent, &CubeFlag)>() {
|
||||
let t = &mut transform.transform;
|
||||
for (mut transform, _) in world.view_iter::<(&mut Transform, &CubeFlag)>() {
|
||||
let t = &mut transform;
|
||||
t.rotate_y(math::Angle::Degrees(SPEED * delta_time));
|
||||
}
|
||||
|
||||
for (mut transform, _s) in world.view_iter::<(&mut TransformComponent, &mut SpotLight)>() {
|
||||
let t = &mut transform.transform;
|
||||
for (mut transform, _s) in world.view_iter::<(&mut Transform, &mut SpotLight)>() {
|
||||
let t = &mut transform;
|
||||
t.rotate_x(math::Angle::Degrees(SPEED * delta_time));
|
||||
}
|
||||
|
||||
|
@ -228,19 +228,19 @@ async fn main() {
|
|||
};
|
||||
|
||||
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();
|
||||
sys.with_criteria(FixedTimestep::new(45));
|
||||
sys.with_system(spin_system.into_system());
|
||||
//sys.with_system(fps_system);
|
||||
|
||||
game.with_system("fixed", sys, &[]);
|
||||
fps_plugin(game);
|
||||
//game.with_system("fixed", sys, &[]);
|
||||
//fps_plugin(game);
|
||||
};
|
||||
|
||||
let action_handler_plugin = |game: &mut Game| {
|
||||
let action_handler = ActionHandler::new()
|
||||
/* let action_handler = ActionHandler::builder()
|
||||
.add_layout(LayoutId::from(0))
|
||||
|
||||
.add_action(CommonActionLabel::MoveForwardBackward, Action::new(ActionKind::Axis))
|
||||
|
@ -250,7 +250,7 @@ async fn main() {
|
|||
.add_action(CommonActionLabel::LookUpDown, Action::new(ActionKind::Axis))
|
||||
.add_action(CommonActionLabel::LookRoll, Action::new(ActionKind::Axis))
|
||||
|
||||
.add_mapping(ActionMapping::new(LayoutId::from(0), ActionMappingId::from(0))
|
||||
.add_mapping(ActionMapping::builder(LayoutId::from(0), ActionMappingId::from(0))
|
||||
.bind(CommonActionLabel::MoveForwardBackward, &[
|
||||
ActionSource::Keyboard(KeyCode::W).into_binding_modifier(1.0),
|
||||
ActionSource::Keyboard(KeyCode::S).into_binding_modifier(-1.0)
|
||||
|
@ -282,26 +282,31 @@ async fn main() {
|
|||
.finish()
|
||||
);
|
||||
|
||||
/* #[allow(unused_variables)]
|
||||
let test_system = |world: &mut World| -> anyhow::Result<()> {
|
||||
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);
|
||||
let world = game.world_mut();
|
||||
world.add_resource(action_handler); */
|
||||
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
|
||||
.with_plugin(lyra_engine::DefaultPlugins)
|
||||
.with_startup_system(setup_sys.into_system())
|
||||
.with_plugin(action_handler_plugin)
|
||||
.with_plugin(script_test_plugin)
|
||||
//.with_plugin(fps_plugin)
|
||||
.with_plugin(jiggle_plugin)
|
||||
.with_plugin(FreeFlyCameraPlugin)
|
||||
|
|
|
@ -5,10 +5,15 @@ edition = "2021"
|
|||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
math = ["dep:lyra-math"]
|
||||
|
||||
[dependencies]
|
||||
lyra-ecs-derive = { path = "./lyra-ecs-derive" }
|
||||
lyra-math = { path = "../lyra-math", optional = true }
|
||||
anyhow = "1.0.75"
|
||||
thiserror = "1.0.50"
|
||||
paste = "1.0.14"
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.8.5" # used for tests
|
||||
|
|
|
@ -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 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)]
|
||||
pub struct ComponentColumn {
|
||||
|
@ -210,6 +210,8 @@ impl ArchetypeId {
|
|||
pub struct Archetype {
|
||||
pub id: ArchetypeId,
|
||||
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 capacity: usize,
|
||||
}
|
||||
|
@ -226,6 +228,7 @@ impl Archetype {
|
|||
Archetype {
|
||||
id: new_id,
|
||||
entities: HashMap::new(),
|
||||
ids_to_entity: HashMap::new(),
|
||||
columns,
|
||||
capacity: DEFAULT_CAPACITY,
|
||||
}
|
||||
|
@ -246,16 +249,17 @@ impl Archetype {
|
|||
self.capacity = new_cap;
|
||||
}
|
||||
|
||||
let entity_index = self.entities.len();
|
||||
self.entities.insert(entity, ArchetypeEntityId(entity_index as u64));
|
||||
let entity_index = ArchetypeEntityId(self.entities.len() as u64);
|
||||
self.entities.insert(entity, entity_index);
|
||||
self.ids_to_entity.insert(entity_index, entity);
|
||||
|
||||
bundle.take(|data, type_id, _size| {
|
||||
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;
|
||||
});
|
||||
|
||||
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.
|
||||
|
@ -286,6 +290,7 @@ impl Archetype {
|
|||
|
||||
// safe from the .expect at the start of this method.
|
||||
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.
|
||||
removed_entity.map(|(e, _a)| (e, entity_index))
|
||||
|
@ -351,14 +356,15 @@ impl Archetype {
|
|||
self.capacity = new_cap;
|
||||
}
|
||||
|
||||
let entity_index = self.entities.len();
|
||||
self.entities.insert(entity, ArchetypeEntityId(entity_index as u64));
|
||||
let entity_index = ArchetypeEntityId(self.entities.len() as u64);
|
||||
self.entities.insert(entity, entity_index);
|
||||
self.ids_to_entity.insert(entity_index, entity);
|
||||
|
||||
for col in self.columns.iter_mut() {
|
||||
col.len += 1;
|
||||
}
|
||||
|
||||
ArchetypeEntityId(entity_index as u64)
|
||||
entity_index
|
||||
}
|
||||
|
||||
/// Moves the entity from this archetype into another one.
|
||||
|
@ -401,6 +407,10 @@ impl Archetype {
|
|||
pub fn entities(&self) -> &HashMap<Entity, ArchetypeEntityId> {
|
||||
&self.entities
|
||||
}
|
||||
|
||||
pub fn entity_of_index(&self, id: ArchetypeEntityId) -> Option<Entity> {
|
||||
self.ids_to_entity.get(&id).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -409,7 +419,7 @@ mod tests {
|
|||
|
||||
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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -8,11 +8,19 @@ pub(crate) mod lyra_engine {
|
|||
}
|
||||
|
||||
pub mod archetype;
|
||||
use std::ops::BitOr;
|
||||
|
||||
pub use archetype::*;
|
||||
|
||||
pub mod entity;
|
||||
pub use entity::*;
|
||||
|
||||
pub mod world;
|
||||
pub use world::*;
|
||||
|
||||
pub mod command;
|
||||
pub use command::*;
|
||||
|
||||
pub mod bundle;
|
||||
pub use bundle::*;
|
||||
|
||||
|
@ -34,6 +42,10 @@ pub mod system;
|
|||
pub mod tick;
|
||||
pub use tick::*;
|
||||
|
||||
/// Implements Component for glam math types
|
||||
#[cfg(feature = "math")]
|
||||
pub mod math;
|
||||
|
||||
pub use lyra_ecs_derive::*;
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -44,4 +56,18 @@ pub enum Access {
|
|||
None,
|
||||
Read,
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
|
@ -221,7 +221,7 @@ impl<T: Component> AsQuery for &mut T {
|
|||
mod tests {
|
||||
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};
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{world::{Entity, World}, archetype::Archetype};
|
||||
use crate::{archetype::Archetype, world::World, Entity};
|
||||
|
||||
use super::{Fetch, Query, AsQuery};
|
||||
|
||||
|
|
|
@ -131,7 +131,7 @@ impl<'a, Q: Query> ViewOne<'a, Q> {
|
|||
}
|
||||
|
||||
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)
|
||||
.expect("An invalid record was specified for an entity");
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ impl<T: 'static> ResourceObject for T {}
|
|||
|
||||
/// A type erased storage for a Resource.
|
||||
pub struct ResourceData {
|
||||
data: Box<RefCell<dyn Any>>,
|
||||
pub(crate) data: Box<RefCell<dyn Any>>,
|
||||
type_id: TypeId,
|
||||
}
|
||||
|
||||
|
|
|
@ -79,6 +79,10 @@ impl System for BatchedSystem {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn execute_deferred(&mut self, _: std::ptr::NonNull<World>) -> anyhow::Result<()> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoSystem<()> for BatchedSystem {
|
||||
|
|
|
@ -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 super::{System, IntoSystem};
|
||||
|
||||
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 [`Access::None`] if you're only fetching components, or resources.
|
||||
|
@ -20,7 +24,10 @@ pub trait FnArgFetcher {
|
|||
/// # Safety
|
||||
/// The system executor must ensure that on execution of the system, it will be safe to
|
||||
/// 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 {
|
||||
|
@ -29,8 +36,10 @@ pub trait FnArg {
|
|||
|
||||
pub struct FnSystem<F, Args> {
|
||||
inner: F,
|
||||
#[allow(dead_code)]
|
||||
args: Args,
|
||||
//#[allow(dead_code)]
|
||||
//args: Args,
|
||||
arg_state: Option<Vec<Box<dyn Any>>>,
|
||||
_marker: PhantomData<Args>,
|
||||
}
|
||||
|
||||
macro_rules! impl_fn_system_tuple {
|
||||
|
@ -38,47 +47,65 @@ macro_rules! impl_fn_system_tuple {
|
|||
#[allow(non_snake_case)]
|
||||
impl<F, $($name: FnArgFetcher,)+> System for FnSystem<F, ($($name,)+)>
|
||||
where
|
||||
F: for<'a> FnMut($($name::Arg<'a>,)+) -> anyhow::Result<()>,
|
||||
F: for<'a> FnMut($($name::Arg<'a, '_>,)+) -> anyhow::Result<()>,
|
||||
{
|
||||
fn world_access(&self) -> Access {
|
||||
todo!()
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
/* impl<F, $($name: FnArg,)+> IntoSystem for F
|
||||
impl<F, $($name: FnArgFetcher,)+> IntoSystem<($($name,)+)> for F
|
||||
where
|
||||
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 {
|
||||
FnSystem {
|
||||
args: ($($name::Fetcher::new(),)+),
|
||||
inner: self
|
||||
}
|
||||
}
|
||||
} */
|
||||
|
||||
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
|
||||
inner: self,
|
||||
arg_state: None,
|
||||
_marker: PhantomData::<($($name,)+)>::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 }
|
||||
|
||||
/// An ArgFetcher implementation for query [`View`]s
|
||||
pub struct ViewArgFetcher<Q: AsQuery> {
|
||||
/* pub struct ViewArgFetcher<Q: AsQuery> {
|
||||
query: Q::Query
|
||||
}
|
||||
|
||||
impl<'a, Q: AsQuery> FnArg for View<'a, Q> {
|
||||
type Fetcher = ViewArgFetcher<Q>;
|
||||
}
|
||||
} */
|
||||
|
||||
impl<Q: AsQuery> FnArgFetcher for ViewArgFetcher<Q> {
|
||||
type Arg<'a> = View<'a, Q>;
|
||||
|
||||
fn new() -> Self {
|
||||
ViewArgFetcher {
|
||||
query: <Q::Query as Query>::new(),
|
||||
}
|
||||
}
|
||||
impl<'c, Q> FnArgFetcher for View<'c, Q>
|
||||
where
|
||||
Q: AsQuery,
|
||||
<Q as AsQuery>::Query: 'static
|
||||
{
|
||||
type State = Q::Query;
|
||||
type Arg<'a, 'state> = View<'a, Q>;
|
||||
|
||||
fn world_access(&self) -> Access {
|
||||
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 arch = world.archetypes.values().collect();
|
||||
let v = View::new(world, self.query, arch);
|
||||
let v = View::new(world, state.clone(), arch);
|
||||
|
||||
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`].
|
||||
pub struct WorldArgFetcher;
|
||||
/* pub struct WorldArgFetcher;
|
||||
|
||||
impl<'a> FnArg for &'a World {
|
||||
type Fetcher = WorldArgFetcher;
|
||||
}
|
||||
} */
|
||||
|
||||
impl FnArgFetcher for WorldArgFetcher {
|
||||
type Arg<'a> = &'a World;
|
||||
|
||||
fn new() -> Self {
|
||||
WorldArgFetcher
|
||||
}
|
||||
impl FnArgFetcher for &'_ World {
|
||||
type State = ();
|
||||
type Arg<'a, 'state> = &'a World;
|
||||
|
||||
fn world_access(&self) -> Access {
|
||||
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()
|
||||
}
|
||||
|
||||
fn apply_deferred(_: Self::State, _: NonNull<World>) { }
|
||||
|
||||
fn create_state(_: NonNull<World>) -> Self::State { () }
|
||||
}
|
||||
|
||||
/// An ArgFetcher implementation for mutably borrowing the [`World`].
|
||||
pub struct WorldMutArgFetcher;
|
||||
|
||||
impl<'a> FnArg for &'a mut World {
|
||||
type Fetcher = WorldMutArgFetcher;
|
||||
}
|
||||
|
||||
impl FnArgFetcher for WorldMutArgFetcher {
|
||||
type Arg<'a> = &'a mut World;
|
||||
|
||||
fn new() -> Self {
|
||||
WorldMutArgFetcher
|
||||
}
|
||||
impl FnArgFetcher for &'_ mut World {
|
||||
type State = ();
|
||||
type Arg<'a, 'state> = &'a mut World;
|
||||
|
||||
fn world_access(&self) -> Access {
|
||||
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()
|
||||
}
|
||||
|
||||
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>
|
||||
}
|
||||
|
||||
impl<'a, R: ResourceObject> FnArg for Res<'a, R> {
|
||||
type Fetcher = ResourceArgFetcher<R>;
|
||||
}
|
||||
} */
|
||||
|
||||
impl<R: ResourceObject> FnArgFetcher for ResourceArgFetcher<R> {
|
||||
type Arg<'a> = Res<'a, R>;
|
||||
impl<R: ResourceObject> FnArgFetcher for Res<'_, R> {
|
||||
type State = ();
|
||||
type Arg<'a, 'state> = Res<'a, R>;
|
||||
|
||||
fn new() -> Self {
|
||||
ResourceArgFetcher {
|
||||
phantom: PhantomData
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
let world = world.as_ref();
|
||||
Res(world.get_resource::<R>())
|
||||
}
|
||||
|
||||
fn apply_deferred(_: Self::State, _: NonNull<World>) { }
|
||||
|
||||
fn create_state(_: NonNull<World>) -> Self::State { () }
|
||||
}
|
||||
|
||||
pub struct ResourceMutArgFetcher<R: ResourceObject> {
|
||||
phantom: PhantomData<fn() -> R>
|
||||
}
|
||||
impl<R: ResourceObject> FnArgFetcher for ResMut<'_, R> {
|
||||
type State = ();
|
||||
type Arg<'a, 'state> = ResMut<'a, R>;
|
||||
|
||||
impl<'a, R: ResourceObject> FnArg for ResMut<'a, R> {
|
||||
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> {
|
||||
unsafe fn get<'a, 'state>(_: &'state mut Self::State, world: NonNull<World>) -> Self::Arg<'a, 'state> {
|
||||
let world = world.as_ref();
|
||||
ResMut(world.get_resource_mut::<R>())
|
||||
}
|
||||
|
||||
fn apply_deferred(_: Self::State, _: NonNull<World>) { }
|
||||
|
||||
fn create_state(_: NonNull<World>) -> Self::State { () }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -2,14 +2,16 @@ use std::{collections::{HashMap, VecDeque, HashSet}, ptr::NonNull};
|
|||
|
||||
use super::System;
|
||||
|
||||
use crate::world::World;
|
||||
use crate::{world::World, CommandQueue, Commands};
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum GraphExecutorError {
|
||||
#[error("could not find a system's dependency named `{0}`")]
|
||||
MissingSystem(String),
|
||||
#[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.
|
||||
|
@ -56,7 +58,7 @@ impl GraphExecutor {
|
|||
}
|
||||
|
||||
/// 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 visited = HashSet::new();
|
||||
|
||||
|
@ -69,7 +71,7 @@ impl GraphExecutor {
|
|||
while let Some(node) = stack.pop_front() {
|
||||
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)) {
|
||||
if stop_on_error {
|
||||
return Err(e);
|
||||
|
@ -78,6 +80,36 @@ impl GraphExecutor {
|
|||
possible_errors.push(e);
|
||||
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)
|
||||
|
|
|
@ -27,6 +27,8 @@ pub trait System {
|
|||
let _ = world;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn execute_deferred(&mut self, world: NonNull<World>) -> anyhow::Result<()>;
|
||||
}
|
||||
|
||||
pub trait IntoSystem<T> {
|
||||
|
|
|
@ -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};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct EntityId(pub u64);
|
||||
use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{dynamic::DynamicView, AsQuery, Query, View, ViewIter, ViewOne}, resource::ResourceData, ComponentInfo, DynTypeId, Entities, Entity, ResourceObject, Tick, TickTracker};
|
||||
|
||||
/// The id of the entity for the Archetype.
|
||||
/// The Archetype struct uses this as the index in the component columns
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
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)]
|
||||
pub struct Record {
|
||||
pub id: ArchetypeId,
|
||||
|
@ -25,11 +16,9 @@ pub struct Record {
|
|||
pub struct World {
|
||||
pub(crate) archetypes: HashMap<ArchetypeId, Archetype>,
|
||||
next_archetype_id: ArchetypeId,
|
||||
pub(crate) entity_index: HashMap<EntityId, Record>,
|
||||
dead_entities: VecDeque<Entity>,
|
||||
next_entity_id: EntityId,
|
||||
resources: HashMap<TypeId, ResourceData>,
|
||||
tracker: TickTracker,
|
||||
pub(crate) entities: Entities,
|
||||
}
|
||||
|
||||
impl Default for World {
|
||||
|
@ -37,11 +26,9 @@ impl Default for World {
|
|||
Self {
|
||||
archetypes: HashMap::new(),
|
||||
next_archetype_id: ArchetypeId(0),
|
||||
entity_index: HashMap::new(),
|
||||
dead_entities: VecDeque::new(),
|
||||
next_entity_id: EntityId(0),
|
||||
resources: HashMap::new(),
|
||||
tracker: TickTracker::new(),
|
||||
entities: Entities::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,32 +38,30 @@ impl World {
|
|||
Self::default()
|
||||
}
|
||||
|
||||
/// Gets a new Entity, will recycle dead entities and increment their generation.
|
||||
fn get_new_entity(&mut self) -> Entity {
|
||||
match self.dead_entities.pop_front() {
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Reserves an entity in the world
|
||||
pub fn reserve_entity(&mut self) -> Entity {
|
||||
self.entities.reserve()
|
||||
}
|
||||
|
||||
/// Spawns a new entity and inserts the component `bundle` into it.
|
||||
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
|
||||
B: Bundle
|
||||
{
|
||||
let bundle_types = bundle.type_ids();
|
||||
let new_entity = self.get_new_entity();
|
||||
|
||||
let tick = self.tick();
|
||||
|
||||
|
@ -86,7 +71,11 @@ impl World {
|
|||
.find(|a| a.is_archetype_for(&bundle_types));
|
||||
|
||||
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
|
||||
let record = Record {
|
||||
|
@ -94,14 +83,14 @@ impl World {
|
|||
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
|
||||
else {
|
||||
// create archetype
|
||||
let new_arch_id = self.next_archetype_id.increment();
|
||||
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
|
||||
self.archetypes.insert(new_arch_id, archetype);
|
||||
|
@ -113,27 +102,25 @@ impl World {
|
|||
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
|
||||
pub fn despawn(&mut self, entity: Entity) {
|
||||
// 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.
|
||||
let tick = if self.entity_index.contains_key(&entity.id) {
|
||||
let tick = if self.entities.arch_index.contains_key(&entity.id) {
|
||||
Some(self.tick())
|
||||
} 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 arch = self.archetypes.get_mut(&record.id).unwrap();
|
||||
|
||||
if let Some((moved, new_index)) = arch.remove_entity(entity, &tick) {
|
||||
// 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 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 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,
|
||||
index: res_index,
|
||||
};
|
||||
self.entity_index.insert(entity.id, new_record);
|
||||
self.entities.insert_entity_record(entity, new_record);
|
||||
} else {
|
||||
let new_arch_id = self.next_archetype_id.increment();
|
||||
let mut archetype = Archetype::from_bundle_info(new_arch_id, col_infos);
|
||||
|
@ -202,13 +189,23 @@ impl World {
|
|||
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();
|
||||
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.
|
||||
pub fn view_iter<T: 'static + AsQuery>(&self) -> ViewIter<T::Query> {
|
||||
let archetypes = self.archetypes.values().collect();
|
||||
|
@ -245,15 +242,29 @@ impl World {
|
|||
.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.
|
||||
///
|
||||
/// Will panic if the resource is not in the world. See [`try_get_resource`] for
|
||||
/// a function that returns an option.
|
||||
pub fn get_resource<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()
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// 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
|
||||
/// a function that returns an option.
|
||||
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()
|
||||
}
|
||||
|
||||
|
@ -296,6 +308,12 @@ impl World {
|
|||
pub fn tick_tracker(&self) -> &TickTracker {
|
||||
&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)]
|
||||
|
@ -348,7 +366,7 @@ mod tests {
|
|||
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,8 +5,9 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
lyra-resource = { path = "../lyra-resource" }
|
||||
lyra-ecs = { path = "../lyra-ecs" }
|
||||
lyra-reflect = { path = "../lyra-reflect" }
|
||||
lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] }
|
||||
lyra-reflect = { path = "../lyra-reflect", features = [ "math" ] }
|
||||
lyra-math = { path = "../lyra-math" }
|
||||
|
||||
winit = "0.28.1"
|
||||
tracing = "0.1.37"
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use instant::Instant;
|
||||
use lyra_ecs::{Component, world::World};
|
||||
use lyra_reflect::Reflect;
|
||||
|
||||
use crate::{plugin::Plugin, game::GameStages};
|
||||
|
||||
#[derive(Clone, Component)]
|
||||
pub struct DeltaTime(f32, Option<Instant>);
|
||||
#[derive(Clone, Component, Default, Reflect)]
|
||||
pub struct DeltaTime(f32, #[reflect(skip)] Option<Instant>);
|
||||
|
||||
impl std::ops::Deref for DeltaTime {
|
||||
type Target = f32;
|
||||
|
@ -36,7 +37,7 @@ pub struct DeltaTimePlugin;
|
|||
|
||||
impl Plugin for DeltaTimePlugin {
|
||||
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, &[]);
|
||||
}
|
||||
}
|
|
@ -78,6 +78,6 @@ pub struct EventsPlugin;
|
|||
|
||||
impl Plugin for EventsPlugin {
|
||||
fn setup(&self, game: &mut crate::game::Game) {
|
||||
game.world().add_resource(EventQueue::new());
|
||||
game.world_mut().add_resource(EventQueue::new());
|
||||
}
|
||||
}
|
|
@ -246,11 +246,17 @@ impl 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
|
||||
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
|
||||
pub fn with_system<S, A>(&mut self, name: &str, system: S, depends: &[&str]) -> &mut Self
|
||||
where
|
||||
|
@ -313,12 +319,14 @@ impl Game {
|
|||
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
|
||||
where
|
||||
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
|
||||
}
|
||||
|
@ -339,16 +347,12 @@ impl Game {
|
|||
tracing_subscriber::registry()
|
||||
.with(fmt::layer().with_writer(stdout_layer))
|
||||
.with(filter::Targets::new()
|
||||
.with_target("lyra_engine", Level::TRACE)
|
||||
.with_target("wgpu_core", Level::WARN)
|
||||
.with_default(Level::DEBUG))
|
||||
// done by prefix, so it includes all lyra subpackages
|
||||
.with_target("lyra", Level::DEBUG)
|
||||
.with_target("wgpu", Level::WARN)
|
||||
.with_default(Level::INFO))
|
||||
.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();
|
||||
|
||||
// run startup systems
|
||||
|
|
|
@ -2,6 +2,7 @@ use std::{collections::HashMap, ops::Deref, hash::{Hash, DefaultHasher, Hasher},
|
|||
|
||||
use glam::Vec2;
|
||||
use lyra_ecs::world::World;
|
||||
use lyra_reflect::Reflect;
|
||||
|
||||
use crate::{plugin::Plugin, game::GameStages, EventQueue};
|
||||
|
||||
|
@ -213,7 +214,7 @@ impl Action {
|
|||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct LayoutId(u32);
|
||||
pub struct LayoutId(pub u32);
|
||||
|
||||
impl From<u32> for LayoutId {
|
||||
fn from(value: u32) -> Self {
|
||||
|
@ -240,7 +241,7 @@ impl Layout {
|
|||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct ActionMappingId(u32);
|
||||
pub struct ActionMappingId(pub u32);
|
||||
|
||||
impl From<u32> for ActionMappingId {
|
||||
fn from(value: u32) -> Self {
|
||||
|
@ -264,6 +265,10 @@ impl ActionMapping {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn builder(layout: LayoutId, id: ActionMappingId) -> ActionMappingBuilder {
|
||||
ActionMappingBuilder::new(ActionMapping::new(layout, id))
|
||||
}
|
||||
|
||||
/// Creates a binding for the action.
|
||||
///
|
||||
/// If the action is not in this layout, this will panic!
|
||||
|
@ -271,7 +276,7 @@ impl ActionMapping {
|
|||
/// Parameters:
|
||||
/// * `action` - The label corresponding to the action in this Layout.
|
||||
/// * `bind` - The Binding to add to the Action.
|
||||
pub fn bind<L>(mut self, action: L, bindings: &[Binding]) -> Self
|
||||
pub fn bind<L>(&mut self, action: L, bindings: &[Binding]) -> &mut Self
|
||||
where
|
||||
L: ActionLabel
|
||||
{
|
||||
|
@ -283,32 +288,48 @@ impl ActionMapping {
|
|||
self
|
||||
}
|
||||
|
||||
/// Creates multiple binding for the action.
|
||||
///
|
||||
/// If the action is not in this layout, this will panic!
|
||||
///
|
||||
/// Parameters:
|
||||
/// * `action_label` - The label corresponding to the action in this Layout.
|
||||
/// * `bindings` - The list of Bindings to add to the Action.
|
||||
/* pub fn add_bindings(&mut self, action_label: String, bindings: &[Binding]) -> &mut Self {
|
||||
let mut bindings = bindings.to_vec();
|
||||
let action_binds = self.action_binds.entry(action_label)
|
||||
.or_insert_with(Vec::new);
|
||||
action_binds.append(&mut bindings);
|
||||
|
||||
self
|
||||
} */
|
||||
|
||||
pub fn finish(self) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct ActionMappingBuilder {
|
||||
mapping: ActionMapping,
|
||||
}
|
||||
|
||||
impl ActionMappingBuilder {
|
||||
fn new(mapping: ActionMapping) -> Self {
|
||||
Self {
|
||||
mapping,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bind<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 {
|
||||
#[reflect(skip)] // TODO: dont just skip all these
|
||||
pub actions: HashMap<u64, Action>,
|
||||
#[reflect(skip)]
|
||||
pub layouts: HashMap<LayoutId, Layout>,
|
||||
#[reflect(skip)]
|
||||
pub current_layout: LayoutId,
|
||||
#[reflect(skip)]
|
||||
pub current_mapping: ActionMappingId,
|
||||
}
|
||||
|
||||
|
@ -317,26 +338,31 @@ impl ActionHandler {
|
|||
Self::default()
|
||||
}
|
||||
|
||||
pub fn add_layout(mut self, id: LayoutId) -> Self {
|
||||
self.layouts.insert(id, Layout::new());
|
||||
|
||||
self
|
||||
pub fn builder() -> ActionHandlerBuilder {
|
||||
ActionHandlerBuilder::default()
|
||||
}
|
||||
|
||||
pub fn add_action<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
|
||||
L: ActionLabel
|
||||
{
|
||||
self.actions.insert(label.label_hash(), action);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_mapping(mut self, mapping: ActionMapping) -> Self {
|
||||
pub fn add_mapping(&mut self, mapping: ActionMapping) {
|
||||
let layout = self.layouts.get_mut(&mapping.layout).unwrap();
|
||||
layout.add_mapping(mapping);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns true if the action is pressed (or was just pressed).
|
||||
|
@ -443,6 +469,43 @@ impl ActionHandler {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ActionHandlerBuilder {
|
||||
handler: ActionHandler,
|
||||
}
|
||||
|
||||
impl ActionHandlerBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn add_layout(mut self, id: LayoutId) -> Self {
|
||||
self.handler.layouts.insert(id, Layout::new());
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_action<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<()> {
|
||||
let keys = world.try_get_resource::<InputButtons<KeyCode>>()
|
||||
.map(|r| r.deref().clone());
|
||||
|
|
|
@ -11,4 +11,182 @@ pub mod buttons;
|
|||
pub use buttons::*;
|
||||
|
||||
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
|
||||
}
|
||||
}
|
|
@ -8,8 +8,6 @@ use crate::{EventQueue, plugin::Plugin, game::GameStages};
|
|||
|
||||
use super::{events::*, InputButtons, InputEvent};
|
||||
|
||||
pub type KeyCode = winit::event::VirtualKeyCode;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct InputSystem;
|
||||
|
||||
|
@ -120,6 +118,10 @@ impl crate::ecs::system::System for InputSystem {
|
|||
fn world_access(&self) -> lyra_ecs::Access {
|
||||
lyra_ecs::Access::Write
|
||||
}
|
||||
|
||||
fn execute_deferred(&mut self, _: NonNull<World>) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoSystem<()> for InputSystem {
|
||||
|
|
|
@ -7,7 +7,6 @@ extern crate self as lyra_engine;
|
|||
pub mod game;
|
||||
pub mod render;
|
||||
pub mod resources;
|
||||
pub mod math;
|
||||
pub mod input;
|
||||
pub mod castable_any;
|
||||
pub mod plugin;
|
||||
|
@ -26,5 +25,10 @@ pub mod scene;
|
|||
|
||||
pub use lyra_resource as assets;
|
||||
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;
|
|
@ -1,3 +1,4 @@
|
|||
use lyra_ecs::CommandQueue;
|
||||
use lyra_resource::ResourceManager;
|
||||
|
||||
use crate::EventsPlugin;
|
||||
|
@ -98,7 +99,7 @@ pub struct ResourceManagerPlugin;
|
|||
|
||||
impl Plugin for ResourceManagerPlugin {
|
||||
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 {
|
||||
fn setup(&self, game: &mut Game) {
|
||||
CommandQueuePlugin.setup(game);
|
||||
EventsPlugin.setup(game);
|
||||
InputPlugin.setup(game);
|
||||
ResourceManagerPlugin.setup(game);
|
||||
WindowPlugin::default().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());
|
||||
}
|
||||
}
|
|
@ -8,11 +8,9 @@ pub use spotlight::*;
|
|||
|
||||
use std::{collections::{VecDeque, HashMap}, marker::PhantomData};
|
||||
|
||||
use tracing::debug;
|
||||
|
||||
use std::mem;
|
||||
|
||||
use crate::{math::Transform, scene::TransformComponent};
|
||||
use crate::math::Transform;
|
||||
|
||||
use self::directional::DirectionalLight;
|
||||
|
||||
|
@ -169,29 +167,29 @@ impl LightUniformBuffers {
|
|||
|
||||
pub fn update_lights(&mut self, queue: &wgpu::Queue, world_tick: Tick, world: &World) {
|
||||
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 {
|
||||
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);
|
||||
debug!("Updated point light");
|
||||
//debug!("Updated point light");
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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);
|
||||
//debug!("Updated spot light");
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -11,11 +11,11 @@ pub struct MaterialSpecular {
|
|||
|
||||
impl MaterialSpecular {
|
||||
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)
|
||||
.map(|i| RenderTexture::from_image(device, queue, bg_layout.clone(), i, None).unwrap());
|
||||
let tex = value.texture.as_ref().map(|t| t.data_ref())
|
||||
.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)
|
||||
.map(|i| RenderTexture::from_image(device, queue, bg_layout, i, None).unwrap());
|
||||
let color_tex = value.color_texture.as_ref().map(|t| t.data_ref())
|
||||
.map(|i| RenderTexture::from_image(device, queue, bg_layout, &i.image, None).unwrap());
|
||||
|
||||
Self {
|
||||
factor: value.factor,
|
||||
|
@ -39,8 +39,8 @@ pub struct Material {
|
|||
|
||||
impl Material {
|
||||
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)
|
||||
.map(|i| RenderTexture::from_image(device, queue, bg_layout.clone(), i, None).unwrap());
|
||||
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.image, None).unwrap());
|
||||
|
||||
let specular = value.specular.as_ref().map(|s| MaterialSpecular::from_resource(device, queue, bg_layout.clone(), s));
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ use winit::window::Window;
|
|||
use crate::math::Transform;
|
||||
use crate::render::material::MaterialUniform;
|
||||
use crate::render::render_buffer::BufferWrapperBuilder;
|
||||
use crate::scene::{ModelComponent, TransformComponent, CameraComponent};
|
||||
use crate::scene::{ModelComponent, CameraComponent};
|
||||
|
||||
use super::camera::{RenderCamera, CameraUniform};
|
||||
use super::desc_buf_lay::DescVertexBufferLayout;
|
||||
|
@ -395,13 +395,13 @@ impl Renderer for BasicRenderer {
|
|||
|
||||
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);
|
||||
|
||||
let cached = match self.entity_last_transforms.get_mut(&entity) {
|
||||
Some(last) if transform_epoch == last_epoch => {
|
||||
last.from_transform = last.to_transform;
|
||||
last.to_transform = transform.transform;
|
||||
last.to_transform = *transform;
|
||||
last.last_updated_at = Some(last.cached_at);
|
||||
last.cached_at = now_inst;
|
||||
|
||||
|
@ -412,8 +412,8 @@ impl Renderer for BasicRenderer {
|
|||
let cached = CachedTransform {
|
||||
last_updated_at: None,
|
||||
cached_at: now_inst,
|
||||
from_transform: transform.transform,
|
||||
to_transform: transform.transform,
|
||||
from_transform: *transform,
|
||||
to_transform: *transform,
|
||||
};
|
||||
self.entity_last_transforms.insert(entity, cached.clone());
|
||||
cached
|
||||
|
@ -430,7 +430,7 @@ impl Renderer for BasicRenderer {
|
|||
|
||||
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() {
|
||||
if !self.process_mesh(entity, transform_val, mesh) && model_epoch == last_epoch {
|
||||
self.update_mesh_buffers(entity, mesh);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use image::GenericImageView;
|
||||
use lyra_resource::{Resource, Texture};
|
||||
use lyra_resource::{ResHandle, Texture};
|
||||
|
||||
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>>) {
|
||||
let texture = &texture.data.as_ref().unwrap().image;
|
||||
pub fn update_texture(&mut self, _device: &wgpu::Device, queue: &wgpu::Queue, texture: &Arc<ResHandle<Texture>>) {
|
||||
let texture = &texture.data_ref().image;
|
||||
let rgba = texture.to_rgba8();
|
||||
let dimensions = texture.dimensions();
|
||||
let size = wgpu::Extent3d {
|
||||
|
|
|
@ -373,7 +373,7 @@ impl Plugin for WindowPlugin {
|
|||
fn setup(&self, game: &mut crate::game::Game) {
|
||||
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, &[]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,9 +4,6 @@ pub use mesh::*;
|
|||
pub mod model;
|
||||
pub use model::*;
|
||||
|
||||
pub mod transform;
|
||||
pub use transform::*;
|
||||
|
||||
pub mod camera;
|
||||
pub use camera::*;
|
||||
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use lyra_ecs::Component;
|
||||
use lyra_reflect::Reflect;
|
||||
use lyra_resource::ResHandle;
|
||||
|
||||
use crate::assets::Model;
|
||||
|
||||
#[derive(Clone, Component)]
|
||||
pub struct ModelComponent(pub ResHandle<Model>);
|
||||
#[derive(Clone, Component, Reflect)]
|
||||
pub struct ModelComponent(#[reflect(skip)] pub ResHandle<Model>);
|
||||
|
||||
impl From<ResHandle<Model>> for ModelComponent {
|
||||
fn from(value: ResHandle<Model>) -> Self {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -9,7 +9,9 @@ pub enum StagedExecutorError {
|
|||
#[error("[stage={0}] could not find a system's dependency named `{1}`")]
|
||||
MissingSystem(String, String),
|
||||
#[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 {
|
||||
|
@ -17,6 +19,7 @@ impl StagedExecutorError {
|
|||
match value {
|
||||
GraphExecutorError::MissingSystem(s) => Self::MissingSystem(stage, s),
|
||||
GraphExecutorError::SystemError(s, e) => Self::SystemError(stage, s, e),
|
||||
GraphExecutorError::Command(e) => Self::CommandError(stage, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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" }
|
|
@ -1,9 +1,9 @@
|
|||
use glam::{Vec3, Mat4, Quat};
|
||||
|
||||
use crate::math::angle::Angle;
|
||||
use super::Angle;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct Transform {
|
||||
pub translation: Vec3,
|
||||
pub rotation: Quat,
|
||||
|
@ -85,12 +85,18 @@ impl Transform {
|
|||
pub fn rotate_z(&mut self, angle: Angle) {
|
||||
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`.
|
||||
///
|
||||
/// 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
|
||||
/// extrapolated.
|
||||
/// will be equal to `rhs`.
|
||||
pub fn lerp(&self, rhs: Transform, alpha: f32) -> Self {
|
||||
|
||||
if alpha.is_finite() {
|
||||
|
@ -104,4 +110,18 @@ impl Transform {
|
|||
*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
|
||||
}
|
||||
}
|
|
@ -5,6 +5,10 @@ edition = "2021"
|
|||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
math = ["dep:lyra-math"]
|
||||
|
||||
[dependencies]
|
||||
lyra-reflect-derive = { path = "lyra-reflect-derive" }
|
||||
lyra-ecs = { path = "../lyra-ecs" }
|
||||
lyra-ecs = { path = "../lyra-ecs" }
|
||||
lyra-math = { path = "../lyra-math", optional = true }
|
|
@ -32,7 +32,7 @@ impl From<&Variant> for VariantType {
|
|||
|
||||
/// Generates the following different outputs:
|
||||
///
|
||||
/// ```rust
|
||||
/// ```compile_fail
|
||||
/// // for struct variants
|
||||
/// 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:
|
||||
///
|
||||
/// ```rust
|
||||
/// ```compile_fail
|
||||
/// /// generated one field here
|
||||
/// if name == "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 {
|
||||
let field_ifs = variant.fields.iter().map(|field| {
|
||||
let id = field.ident.as_ref().unwrap();
|
||||
let id_str = id.span().source_text().unwrap();
|
||||
let id_str = id.to_string();
|
||||
|
||||
quote! {
|
||||
if name == #id_str {
|
||||
|
@ -129,7 +129,7 @@ fn gen_if_field_names(variant: &Variant) -> proc_macro2::TokenStream {
|
|||
|
||||
/// Generates the following rust code:
|
||||
///
|
||||
/// ```rust
|
||||
/// ```compile_fail
|
||||
/// match name {
|
||||
/// "msg" | "code" => true,
|
||||
/// _ => false,
|
||||
|
@ -140,7 +140,7 @@ fn gen_match_names(variant: &Variant) -> proc_macro2::TokenStream {
|
|||
let field_name_strs = variant.fields.iter().map(|field| {
|
||||
let id = field.ident.as_ref()
|
||||
.expect("Could not find identifier for enum field!");
|
||||
id.span().source_text().unwrap()
|
||||
id.to_string()
|
||||
});
|
||||
|
||||
quote! {
|
||||
|
@ -153,7 +153,7 @@ fn gen_match_names(variant: &Variant) -> proc_macro2::TokenStream {
|
|||
|
||||
/// Generates the following:
|
||||
///
|
||||
/// ```rust
|
||||
/// ```compile_fail
|
||||
/// /// generated one field here
|
||||
/// if idx == 0 {
|
||||
/// return Some(a);
|
||||
|
@ -190,7 +190,7 @@ fn gen_if_field_indices(variant: &Variant) -> proc_macro2::TokenStream {
|
|||
|
||||
/// Generates the following:
|
||||
///
|
||||
/// ```rust
|
||||
/// ```compile_fail
|
||||
/// /// generated one field here
|
||||
/// if idx == 0 {
|
||||
/// return Some("a");
|
||||
|
@ -226,7 +226,7 @@ fn gen_if_field_indices_names(variant: &Variant) -> proc_macro2::TokenStream {
|
|||
}
|
||||
|
||||
/// Generates the following:
|
||||
/// ```rust
|
||||
/// ```compile_fail
|
||||
/// /// when `by_index` is false:
|
||||
///
|
||||
/// 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! { },
|
||||
}
|
||||
});
|
||||
println!("====");
|
||||
|
||||
quote! {
|
||||
#( #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:
|
||||
///
|
||||
/// ```rust
|
||||
/// ```compile_fail
|
||||
/// if let TestEnum::Error { msg, code } = self {
|
||||
/// return match name {
|
||||
/// // 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:
|
||||
///
|
||||
/// ```rust
|
||||
/// ```compile_fail
|
||||
/// match self {
|
||||
/// TestEnum::Start => 0,
|
||||
/// 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:
|
||||
///
|
||||
/// ```rust
|
||||
/// ```compile_fail
|
||||
/// if let TestEnum::Error { msg, code } = self {
|
||||
/// if idx == 0 {
|
||||
/// 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:
|
||||
/// ```rust
|
||||
/// ```compile_fail
|
||||
/// match self {
|
||||
/// TestEnum::Start => 0,
|
||||
/// 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.
|
||||
///
|
||||
/// Example:
|
||||
/// ```rust
|
||||
/// ```compile_fail
|
||||
/// match self {
|
||||
/// TestEnum::Start => EnumType::Unit,
|
||||
/// 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
|
||||
pub fn derive_reflect_enum(input: &DeriveInput, data_enum: &DataEnum) -> proc_macro2::TokenStream {
|
||||
|
||||
let type_path = &input.ident;
|
||||
let name = type_path.span().source_text().unwrap();
|
||||
//println!("Got type path: {}", type_path);
|
||||
let input_ident = &input.ident;
|
||||
let ident_str = input.ident.to_string();
|
||||
|
||||
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 variant_name = &variant.ident; */
|
||||
let field_at_ifs = gen_enum_if_stmts(input_ident, data_enum, true);
|
||||
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 field_mut_ifs = gen_enum_if_stmts(type_path, data_enum, false);
|
||||
|
||||
let field_at_ifs = gen_enum_if_stmts(type_path, data_enum, true);
|
||||
let field_at_mut_ifs = gen_enum_if_stmts(type_path, data_enum, true);
|
||||
|
||||
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 has_field = gen_enum_has_field(input_ident, data_enum);
|
||||
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 variant_name_match = gen_enum_variant_name(input_ident, data_enum, false);
|
||||
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 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();
|
||||
|
||||
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 {
|
||||
#name.to_string()
|
||||
#ident_str.to_string()
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -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> {
|
||||
let name = name.to_lowercase();
|
||||
let name = name.as_str();
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use proc_macro::TokenStream;
|
||||
use proc_macro2::Ident;
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::{Generics, Path, Attribute, GenericParam, parse_macro_input, DeriveInput, TypeParamBound};
|
||||
|
||||
|
@ -10,8 +11,55 @@ mod struct_derive;
|
|||
#[allow(unused_imports)]
|
||||
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)]
|
||||
pub(crate) struct ReflectDef {
|
||||
//pub ident: Ident,
|
||||
pub type_path: Path,
|
||||
pub generics: Generics,
|
||||
pub attributes: Vec<Attribute>
|
||||
|
@ -21,10 +69,12 @@ impl syn::parse::Parse for ReflectDef {
|
|||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
let attributes = input.call(Attribute::parse_outer)?;
|
||||
let type_path = Path::parse_mod_style(input)?;
|
||||
//let ident = type_path. //type_path.require_ident()?;
|
||||
let mut generics = input.parse::<Generics>()?;
|
||||
generics.where_clause = input.parse()?;
|
||||
|
||||
Ok(Self {
|
||||
//ident: ident.clone(),
|
||||
type_path,
|
||||
generics,
|
||||
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 {
|
||||
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]
|
||||
pub fn impl_reflect_trait_value(input: TokenStream) -> TokenStream {
|
||||
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 name = name_id.span().source_text().unwrap();
|
||||
let type_path = reflect.type_path;
|
||||
// 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();
|
||||
|
||||
TokenStream::from(quote! {
|
||||
impl #impl_generics lyra_engine::reflect::Reflect for #type_path #ty_generics #where_clause {
|
||||
fn name(&self) -> ::std::string::String {
|
||||
#name.to_string()
|
||||
#type_path_str.to_string()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn impl_reflect_simple_struct(input: TokenStream) -> TokenStream {
|
||||
struct_macro::impl_reflect_simple_struct(input)
|
||||
}
|
|
@ -1,13 +1,34 @@
|
|||
use quote::{quote, ToTokens};
|
||||
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
|
||||
/// contains a borrow (mutable borrow if `is_mut` is true) to the matching struct field.
|
||||
///
|
||||
/// Example:
|
||||
/// ```rust
|
||||
/// ```compile_fail
|
||||
/// // when `is_mut` = false
|
||||
/// match name {
|
||||
/// "x" => Some(&self.x),
|
||||
|
@ -22,35 +43,49 @@ use crate::add_trait_bounds;
|
|||
/// _ => 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 {
|
||||
let mut_tkn = if is_mut {
|
||||
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)
|
||||
}
|
||||
});
|
||||
let ty = StructType::new(data);
|
||||
|
||||
quote! {
|
||||
match name {
|
||||
#(#field_arms,)*
|
||||
_ => None,
|
||||
if ty == StructType::Named {
|
||||
let mut_tkn = if is_mut {
|
||||
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();
|
||||
|
||||
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
|
||||
/// with the provided `val`.
|
||||
///
|
||||
/// Example:
|
||||
/// ```rust
|
||||
/// ```compile_fail
|
||||
/// match name {
|
||||
/// "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()))
|
||||
|
@ -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 {
|
||||
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 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)
|
||||
}
|
||||
});
|
||||
let ty = StructType::new(data);
|
||||
|
||||
quote! {
|
||||
let any_val = val.as_any();
|
||||
match name {
|
||||
#(#field_arms,)*
|
||||
_ => {
|
||||
return false;
|
||||
},
|
||||
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 field_ty = &field.ty;
|
||||
|
||||
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
|
||||
/// the type of the field.
|
||||
///
|
||||
/// Example:
|
||||
/// ```rust
|
||||
/// ```compile_fail
|
||||
/// match name {
|
||||
/// "x" => 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 {
|
||||
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 ty = StructType::new(data);
|
||||
|
||||
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! {
|
||||
#field_name_str => Some(#s)
|
||||
match name {
|
||||
#(#field_arms,)*
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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
|
||||
/// with the provided `val`.
|
||||
///
|
||||
/// Example:
|
||||
/// ```rust
|
||||
/// ```compile_fail
|
||||
/// match name {
|
||||
/// 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()))
|
||||
|
@ -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 {
|
||||
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_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! {
|
||||
#idx => 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()
|
||||
|
||||
let attrs = FieldAttributes::from_vec(&field.attrs)
|
||||
.expect("Failure to parse reflect attributes");
|
||||
if attrs.has_skip() {
|
||||
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;
|
||||
},
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -165,7 +243,7 @@ fn gen_struct_set_field_match_idx(data: &DataStruct) -> proc_macro2::TokenStream
|
|||
/// type of the field.
|
||||
///
|
||||
/// Example:
|
||||
/// ```rust
|
||||
/// ```compile_fail
|
||||
/// match name {
|
||||
/// 0 => 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.
|
||||
///
|
||||
/// Example:
|
||||
/// ```rust
|
||||
/// ```compile_fail
|
||||
/// // when `is_mut` = false
|
||||
/// match idx {
|
||||
/// 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 {
|
||||
let ty = StructType::new(data);
|
||||
|
||||
if ty == StructType::Unit {
|
||||
return quote!(None);
|
||||
}
|
||||
|
||||
let mut_tkn = if is_mut {
|
||||
quote! {
|
||||
mut
|
||||
|
@ -219,10 +303,23 @@ fn gen_struct_field_match_idx(data: &DataStruct, is_mut: bool) -> proc_macro2::T
|
|||
} else { quote!{} };
|
||||
|
||||
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! {
|
||||
#idx => Some(&#mut_tkn self.#field_ident)
|
||||
if let Some(field_ident) = &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.
|
||||
///
|
||||
/// Example:
|
||||
/// ```rust
|
||||
/// ```compile_fail
|
||||
/// match idx {
|
||||
/// 0 => Some("x"),
|
||||
/// 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 {
|
||||
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();
|
||||
|
||||
quote! {
|
||||
#idx => Some(#field_name_str)
|
||||
}
|
||||
});
|
||||
let ty = StructType::new(data);
|
||||
|
||||
quote! {
|
||||
match idx {
|
||||
#(#field_arms,)*
|
||||
_ => None,
|
||||
if ty == StructType::Named {
|
||||
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();
|
||||
|
||||
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
|
||||
pub fn derive_reflect_struct(input: &DeriveInput, data_struct: &DataStruct) -> proc_macro2::TokenStream {
|
||||
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 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 {
|
||||
#set_field_named
|
||||
true
|
||||
}
|
||||
|
||||
fn set_field_at(&mut self, idx: usize, val: &dyn lyra_engine::reflect::Reflect) -> bool {
|
||||
#set_field_idx
|
||||
true
|
||||
}
|
||||
|
||||
fn field_type(&self, name: &str) -> Option<&'static str> {
|
||||
|
|
|
@ -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!()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
|
@ -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>)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
|
@ -4,7 +4,7 @@ use crate::List;
|
|||
|
||||
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!(char);
|
|
@ -0,0 +1,4 @@
|
|||
pub mod impl_std;
|
||||
|
||||
#[cfg(feature="math")]
|
||||
pub mod impl_math;
|
|
@ -6,6 +6,8 @@ use lyra_ecs::{world::World, DynamicBundle, Component, Entity, ComponentInfo};
|
|||
|
||||
extern crate self as lyra_reflect;
|
||||
|
||||
pub use lyra_reflect_derive::*;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) mod lyra_engine {
|
||||
pub(crate) mod reflect {
|
||||
|
@ -34,6 +36,12 @@ pub use dynamic_tuple::*;
|
|||
pub mod reflected_field;
|
||||
pub use reflected_field::*;
|
||||
|
||||
pub mod component;
|
||||
pub use component::*;
|
||||
|
||||
pub mod resource;
|
||||
pub use resource::*;
|
||||
|
||||
pub mod util;
|
||||
pub mod field;
|
||||
pub use field::*;
|
||||
|
@ -42,7 +50,7 @@ pub use method::*;
|
|||
pub mod registry;
|
||||
pub use registry::*;
|
||||
|
||||
pub mod impl_std;
|
||||
pub mod impls;
|
||||
|
||||
pub trait Reflect: Any {
|
||||
fn name(&self) -> String;
|
||||
|
@ -235,67 +243,48 @@ pub trait FromType<T> {
|
|||
fn from_type() -> Self;
|
||||
}
|
||||
|
||||
#[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: &dyn Reflect),
|
||||
/// Inserts component into a bundle
|
||||
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>>,
|
||||
pub trait ReflectWorldExt {
|
||||
/// Retrieves the type registry from the world.
|
||||
fn get_type_registry(&self) -> Ref<TypeRegistry>;
|
||||
/// Retrieves the type registry mutably from the world.
|
||||
fn get_type_registry_mut(&self) -> RefMut<TypeRegistry>;
|
||||
/// 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>>;
|
||||
/// Get a mutable registered type from the type registry in the world. returns `None` if the type is not registered.
|
||||
fn get_type_mut<T>(&self, type_id: TypeId) -> Option<RefMut<RegisteredType>>;
|
||||
/// Get a registered type, or register a new type and return it.
|
||||
fn get_type_or_default<T>(&self, type_id: TypeId) -> RefMut<RegisteredType>;
|
||||
}
|
||||
|
||||
impl ReflectedComponent {
|
||||
/// Insert the reflected component into an entity.
|
||||
pub fn insert(&self, world: &mut World, entity: Entity, component: &dyn Reflect) {
|
||||
(self.fn_insert)(world, entity, component);
|
||||
impl ReflectWorldExt for World {
|
||||
fn get_type_registry(&self) -> Ref<TypeRegistry> {
|
||||
self.get_resource::<TypeRegistry>()
|
||||
}
|
||||
|
||||
/// Insert this component into a DynamicBundle
|
||||
pub fn bundle_insert(&self, dynamic_bundle: &mut DynamicBundle, component: &dyn Reflect) {
|
||||
(self.fn_bundle_insert)(dynamic_bundle, component)
|
||||
|
||||
fn get_type_registry_mut(&self) -> RefMut<TypeRegistry> {
|
||||
self.get_resource_mut::<TypeRegistry>()
|
||||
}
|
||||
|
||||
/// 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 + 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<T>(&self, type_id: TypeId) -> Option<Ref<RegisteredType>> {
|
||||
let r = self.get_resource::<TypeRegistry>();
|
||||
if r.has_type(type_id) {
|
||||
Some(Ref::map(r, |tr| tr.get_type(type_id).unwrap()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
|
@ -52,7 +52,6 @@ pub trait Enum: Reflect {
|
|||
#[allow(unused_variables)]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use lyra_reflect_derive::Reflect;
|
||||
|
||||
use super::EnumType;
|
||||
use crate::{lyra_engine, Reflect, ReflectRef};
|
||||
|
|
|
@ -41,7 +41,6 @@ pub trait Struct: Reflect {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use lyra_reflect_derive::Reflect;
|
||||
use crate::{Reflect, ReflectRef, ReflectMut};
|
||||
use crate::lyra_engine;
|
||||
|
||||
|
|
|
@ -21,6 +21,12 @@ impl TypeRegistry {
|
|||
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)
|
||||
where
|
||||
T: AsRegisteredType + 'static
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,15 +6,19 @@ edition = "2021"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
lyra-ecs = { path = "../lyra-ecs" }
|
||||
anyhow = "1.0.75"
|
||||
base64 = "0.21.4"
|
||||
edict = "0.5.0"
|
||||
crossbeam = { version = "0.8.4", features = [ "crossbeam-channel" ] }
|
||||
glam = "0.24.1"
|
||||
gltf = { version = "1.3.0", features = ["KHR_materials_pbrSpecularGlossiness", "KHR_materials_specular"] }
|
||||
image = "0.24.7"
|
||||
# not using custom matcher, or file type from file path
|
||||
infer = { version = "0.15.0", default-features = false }
|
||||
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"
|
||||
thiserror = "1.0.48"
|
||||
tracing = "0.1.37"
|
||||
|
|
|
@ -16,4 +16,17 @@ pub use model::*;
|
|||
pub mod 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::*;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::{fs::File, sync::Arc, io::Read};
|
|||
|
||||
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};
|
||||
|
||||
|
@ -62,7 +62,7 @@ impl ResourceLoader for ImageLoader {
|
|||
let texture = Texture {
|
||||
image,
|
||||
};
|
||||
let res = Resource::with_data(path, texture);
|
||||
let res = ResHandle::with_data(path, texture);
|
||||
|
||||
Ok(Arc::new(res))
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ impl ResourceLoader for ImageLoader {
|
|||
let texture = Texture {
|
||||
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))
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ impl From<io::Error> for LoaderError {
|
|||
}
|
||||
|
||||
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];
|
||||
/// Returns the mime types that this loader supports.
|
||||
fn mime_types(&self) -> &[&str];
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::{sync::Arc, path::PathBuf};
|
|||
|
||||
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;
|
||||
|
||||
|
@ -189,7 +189,7 @@ impl ResourceLoader for ModelLoader {
|
|||
.collect();
|
||||
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)]
|
||||
|
@ -216,8 +216,8 @@ mod tests {
|
|||
let mut manager = ResourceManager::new();
|
||||
let loader = ModelLoader::default();
|
||||
let model = loader.load(&mut manager, &path).unwrap();
|
||||
let model = Arc::downcast::<Resource<Model>>(model.as_arc_any()).unwrap();
|
||||
let model = model.data.as_ref().unwrap();
|
||||
let model = Arc::downcast::<ResHandle<Model>>(model.as_arc_any()).unwrap();
|
||||
let model = model.data_ref();
|
||||
assert_eq!(model.meshes.len(), 1); // There should only be 1 mesh
|
||||
let mesh = &model.meshes[0];
|
||||
assert!(mesh.position().unwrap().len() > 0);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use crate::Material;
|
||||
use crate::lyra_engine;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
|
@ -81,7 +82,7 @@ pub enum MeshVertexAttribute {
|
|||
Other(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, edict::Component)]
|
||||
#[derive(Clone, lyra_ecs::Component)]
|
||||
pub struct Mesh {
|
||||
pub uuid: uuid::Uuid,
|
||||
pub attributes: HashMap<MeshVertexAttribute, VertexAttributeData>,
|
||||
|
|
|
@ -1,32 +1,174 @@
|
|||
use std::sync::Arc;
|
||||
use std::{any::Any, sync::{Arc, RwLock}};
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::ResourceStorage;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum ResourceState {
|
||||
Loading,
|
||||
Ready,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Resource<T> {
|
||||
pub path: String,
|
||||
pub data: Option<Arc<T>>,
|
||||
pub uuid: Uuid,
|
||||
pub state: ResourceState,
|
||||
pub struct ResourceDataRef<'a, T> {
|
||||
guard: std::sync::RwLockReadGuard<'a, Resource<T>>,
|
||||
}
|
||||
|
||||
/// A helper type to make it easier to use resources
|
||||
pub type ResHandle<T> = Arc<Resource<T>>;
|
||||
impl<'a, T> std::ops::Deref for ResourceDataRef<'a, 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`
|
||||
pub fn with_data(path: &str, data: T) -> Self {
|
||||
Self {
|
||||
let res_version = Resource {
|
||||
path: path.to_string(),
|
||||
data: Some(Arc::new(data)),
|
||||
uuid: Uuid::new_v4(),
|
||||
data: Some(data),
|
||||
version: 0,
|
||||
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()
|
||||
}
|
||||
}
|
|
@ -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 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 {
|
||||
fn as_any(&self) -> &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_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
|
||||
impl<T: Send + Sync + 'static> ResourceStorage for 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 path(&self) -> String;
|
||||
fn version(&self) -> usize;
|
||||
fn state(&self) -> ResourceState;
|
||||
fn uuid(&self) -> Uuid;
|
||||
fn is_watched(&self) -> bool;
|
||||
fn is_loaded(&self) -> bool;
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
|
@ -46,9 +48,16 @@ impl From<LoaderError> for RequestError {
|
|||
/// A struct that stores all Manager data. This is requried for sending
|
||||
//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 {
|
||||
resources: HashMap<String, Arc<dyn ResourceStorage>>,
|
||||
loaders: Vec<Arc<dyn ResourceLoader>>,
|
||||
watchers: HashMap<String, ResourceWatcher>,
|
||||
}
|
||||
|
||||
impl Default for ResourceManager {
|
||||
|
@ -62,6 +71,7 @@ impl ResourceManager {
|
|||
Self {
|
||||
resources: HashMap::new(),
|
||||
loaders: vec![ Arc::new(ImageLoader), Arc::new(ModelLoader) ],
|
||||
watchers: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,11 +83,15 @@ impl ResourceManager {
|
|||
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()) {
|
||||
Some(res) => {
|
||||
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)
|
||||
},
|
||||
|
@ -88,12 +102,43 @@ impl ResourceManager {
|
|||
// 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());
|
||||
|
||||
// cast Arc<dyn ResourceStorage> to Arc<Resource<T>
|
||||
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>>`?");
|
||||
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)
|
||||
} else {
|
||||
|
@ -112,18 +157,23 @@ impl ResourceManager {
|
|||
/// * `bytes` - The bytes to store.
|
||||
///
|
||||
/// 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()
|
||||
.find(|l| l.does_support_mime(mime_type)) {
|
||||
let loader = loader.clone();
|
||||
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());
|
||||
// code here...
|
||||
|
||||
// cast Arc<dyn ResourceStorage> to Arc<Resource<T>
|
||||
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>>`?");
|
||||
let res = ResHandle::<T>::clone(&res);
|
||||
|
||||
Ok(res)
|
||||
} else {
|
||||
|
@ -132,11 +182,14 @@ impl ResourceManager {
|
|||
}
|
||||
|
||||
/// 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()) {
|
||||
Some(res) => {
|
||||
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)
|
||||
},
|
||||
|
@ -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)]
|
||||
|
@ -165,20 +303,20 @@ mod tests {
|
|||
fn load_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.data.as_ref();
|
||||
assert_eq!(res.state(), ResourceState::Ready);
|
||||
let img = res.try_data_ref();
|
||||
img.unwrap();
|
||||
}
|
||||
|
||||
/// Ensures that only one copy of the same thing made
|
||||
/// Ensures that only one copy of the same data was made
|
||||
#[test]
|
||||
fn ensure_single() {
|
||||
let mut man = ResourceManager::new();
|
||||
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();
|
||||
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
|
||||
|
@ -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()));
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -7,11 +7,13 @@ edition = "2021"
|
|||
|
||||
[features]
|
||||
default = ["lua"]
|
||||
lua = ["dep:mlua"]
|
||||
lua = ["dep:elua"]
|
||||
teal = ["lua", "elua/teal"]
|
||||
|
||||
[dependencies]
|
||||
lyra-ecs = { path = "../lyra-ecs" }
|
||||
lyra-reflect = { path = "../lyra-reflect" }
|
||||
lyra-scripting-derive = { path = "lyra-scripting-derive" }
|
||||
lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] }
|
||||
lyra-reflect = { path = "../lyra-reflect", features = [ "math" ] }
|
||||
lyra-resource = { path = "../lyra-resource" }
|
||||
lyra-game = { path = "../lyra-game" }
|
||||
thiserror = "1.0.50"
|
||||
|
@ -19,8 +21,10 @@ anyhow = "1.0.77"
|
|||
tracing = "0.1.37"
|
||||
|
||||
# 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]
|
||||
tracing-subscriber = { version = "0.3.16", features = [ "tracing-log" ] }
|
||||
tracing-subscriber = { version = "0.3.16", features = [ "tracing-log" ] }
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 54c9926a04cdef657289fd67730c0b85d1bdda3e
|
|
@ -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"
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
|
@ -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`"))?,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -1,6 +1,6 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use lyra_ecs::ResourceObject;
|
||||
use lyra_ecs::{ResourceObject, Entity, World};
|
||||
|
||||
use crate::ScriptWorldPtr;
|
||||
|
||||
|
@ -8,15 +8,15 @@ use crate::ScriptWorldPtr;
|
|||
pub enum ScriptError {
|
||||
#[error("{0}")]
|
||||
#[cfg(feature = "lua")]
|
||||
MluaError(mlua::Error),
|
||||
MluaError(elua::Error),
|
||||
|
||||
#[error("{0}")]
|
||||
Other(anyhow::Error),
|
||||
}
|
||||
|
||||
#[cfg(feature = "lua")]
|
||||
impl From<mlua::Error> for ScriptError {
|
||||
fn from(value: mlua::Error) -> Self {
|
||||
impl From<elua::Error> for ScriptError {
|
||||
fn from(value: elua::Error) -> Self {
|
||||
ScriptError::MluaError(value)
|
||||
}
|
||||
}
|
||||
|
@ -29,8 +29,12 @@ impl From<anyhow::Error> for ScriptError {
|
|||
|
||||
#[derive(Clone, Hash, PartialEq, Eq)]
|
||||
pub struct ScriptData {
|
||||
/// The script id
|
||||
pub script_id: u64,
|
||||
/// The name of the script
|
||||
pub name: String,
|
||||
/// The entity that this script exists on
|
||||
pub entity: Entity,
|
||||
}
|
||||
|
||||
/// Provides an API to a scripting context.
|
||||
|
@ -38,12 +42,15 @@ pub trait ScriptApiProvider {
|
|||
/// The type used as the script's context.
|
||||
type ScriptContext;
|
||||
|
||||
/// Exposes an API in the provided script context.
|
||||
fn expose_api(&mut self, ctx: &mut Self::ScriptContext) -> Result<(), ScriptError>;
|
||||
/// Prepare the ECS world for this api. Things like registering types with the type registry happen here.
|
||||
fn prepare_world(&mut self, world: &mut World) {
|
||||
let _ = world; // remove compiler warning
|
||||
}
|
||||
|
||||
/// Create a script in the script host.
|
||||
///
|
||||
/// This only creates the script for the host, it does not setup the script for execution. See [`ScriptHostProvider::setup_script`].
|
||||
/// Exposes an API in the provided script context.
|
||||
fn expose_api(&mut self, data: &ScriptData, ctx: &mut Self::ScriptContext) -> Result<(), ScriptError>;
|
||||
|
||||
/// Setup a script right before its 'init' method is called.
|
||||
fn setup_script(&mut self, data: &ScriptData, ctx: &mut Self::ScriptContext) -> Result<(), ScriptError>;
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// 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.
|
||||
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.
|
||||
fn update_script(&mut self, world: ScriptWorldPtr, script_data: &crate::ScriptData, ctx: &mut Self::ScriptContext, providers: &mut crate::ScriptApiProviders<Self>) -> Result<(), ScriptError>;
|
||||
/// Calls a event function in the script.
|
||||
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)]
|
||||
|
@ -110,4 +122,12 @@ impl<T> ScriptContexts<T> {
|
|||
pub fn has_context(&self, script_id: u64) -> bool {
|
||||
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()
|
||||
}
|
||||
}
|
|
@ -2,6 +2,9 @@
|
|||
pub mod lua;
|
||||
|
||||
pub mod world;
|
||||
use std::any::TypeId;
|
||||
|
||||
use lyra_ecs::{Component, ResourceObject};
|
||||
pub use world::*;
|
||||
|
||||
pub mod wrap;
|
||||
|
@ -15,16 +18,19 @@ pub use script::*;
|
|||
|
||||
use lyra_game::game::Game;
|
||||
|
||||
// required for some proc macros :(
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) mod lyra_engine {
|
||||
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)]
|
||||
pub enum ReflectBranch {
|
||||
Component(ReflectedComponent),
|
||||
Resource(ReflectedResource),
|
||||
}
|
||||
|
||||
impl ReflectBranch {
|
||||
|
@ -35,18 +41,51 @@ impl ReflectBranch {
|
|||
pub fn as_component_unchecked(&self) -> &ReflectedComponent {
|
||||
match self {
|
||||
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 {
|
||||
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 {
|
||||
reflect_branch: ReflectBranch,
|
||||
data: Option<Box<dyn Reflect>>,
|
||||
pub(crate) reflect_branch: ReflectBranch,
|
||||
pub(crate) data: Option<Box<dyn Reflect>>,
|
||||
}
|
||||
|
||||
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
|
||||
pub trait GameScriptExt {
|
||||
/// A helper method for adding a ScriptApiProvider into the world.
|
||||
fn add_script_api_provider<T, P>(&mut self, provider: P)
|
||||
where
|
||||
T: ScriptHost,
|
||||
|
@ -69,12 +137,13 @@ pub trait GameScriptExt {
|
|||
}
|
||||
|
||||
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
|
||||
T: ScriptHost,
|
||||
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>>();
|
||||
providers.add_provider(provider);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
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;
|
||||
|
||||
#[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)]
|
||||
pub struct DynamicViewIter {
|
||||
world_ptr: ScriptWorldPtr,
|
||||
|
@ -69,15 +74,15 @@ impl<'a> From<lyra_ecs::query::dynamic::DynamicViewIter<'a>> for DynamicViewIter
|
|||
}
|
||||
|
||||
impl Iterator for DynamicViewIter {
|
||||
type Item = Vec<DynamicType>;
|
||||
type Item = DynamicViewRow;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
if let Some(entity_index) = self.component_indices.next() {
|
||||
let mut fetch_res = vec![];
|
||||
|
||||
let entity_index = ArchetypeEntityId(entity_index);
|
||||
for fetcher in self.fetchers.iter_mut() {
|
||||
let entity_index = ArchetypeEntityId(entity_index);
|
||||
if !fetcher.can_visit_item(entity_index) {
|
||||
break;
|
||||
} else {
|
||||
|
@ -90,7 +95,14 @@ impl Iterator for DynamicViewIter {
|
|||
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 {
|
||||
if self.next_archetype >= self.archetypes.len() {
|
||||
return None; // ran out of archetypes to go through
|
||||
|
@ -108,7 +120,6 @@ impl Iterator for DynamicViewIter {
|
|||
continue;
|
||||
}
|
||||
|
||||
//let world = unsafe { self.world_ptr.as_ref() };
|
||||
let world = self.world_ptr.as_ref();
|
||||
|
||||
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 world: ScriptWorldPtr,
|
||||
pub dyn_view: DynamicViewIter,
|
||||
|
@ -128,10 +152,9 @@ pub struct ReflectedIterator {
|
|||
}
|
||||
|
||||
impl ReflectedIterator {
|
||||
|
||||
|
||||
#[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();
|
||||
|
||||
|
@ -142,23 +165,32 @@ impl ReflectedIterator {
|
|||
.map(|r| NonNull::from(r.deref()));
|
||||
}
|
||||
|
||||
let mut dynamic_row = Vec::new();
|
||||
for d in row.iter() {
|
||||
let mut dynamic_row = vec![];
|
||||
for d in row.item.iter() {
|
||||
let id = d.info.type_id.as_rust();
|
||||
let reflected_components =
|
||||
unsafe { self.reflected_components.as_ref().unwrap().as_ref() };
|
||||
|
||||
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>()
|
||||
// TODO: properly handle this error
|
||||
.expect("Type does not have ReflectLuaProxy as a TypeData");
|
||||
|
||||
let userdata = (proxy.fn_as_uservalue)(lua, d.ptr).unwrap();
|
||||
let value = (proxy.fn_as_lua)(lua, d.ptr.cast()).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 {
|
||||
None
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use lyra_resource::{ResourceLoader, Resource};
|
||||
use lyra_resource::{ResourceLoader, ResHandle};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LuaScript {
|
||||
|
@ -13,7 +13,7 @@ pub struct LuaLoader;
|
|||
|
||||
impl ResourceLoader for LuaLoader {
|
||||
fn extensions(&self) -> &[&str] {
|
||||
&[".lua"]
|
||||
&["lua"]
|
||||
}
|
||||
|
||||
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> {
|
||||
let bytes = std::fs::read(path)?;
|
||||
|
||||
let s = Resource::with_data(path, LuaScript {
|
||||
let s = ResHandle::with_data(path, LuaScript {
|
||||
bytes
|
||||
});
|
||||
|
||||
|
@ -34,7 +34,7 @@ impl ResourceLoader for LuaLoader {
|
|||
let end = offset + length;
|
||||
let bytes = bytes[offset..end].to_vec();
|
||||
|
||||
let s = Resource::with_data("from bytes", LuaScript {
|
||||
let s = ResHandle::with_data("from bytes", LuaScript {
|
||||
bytes
|
||||
});
|
||||
|
||||
|
|
|
@ -2,8 +2,7 @@ pub mod dynamic_iter;
|
|||
pub use dynamic_iter::*;
|
||||
|
||||
pub mod world;
|
||||
use lyra_game::plugin::Plugin;
|
||||
use lyra_resource::ResourceManager;
|
||||
use elua::FromLua;
|
||||
pub use world::*;
|
||||
|
||||
pub mod script;
|
||||
|
@ -12,120 +11,174 @@ pub use script::*;
|
|||
pub mod loader;
|
||||
pub use loader::*;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
pub mod providers;
|
||||
pub mod wrappers;
|
||||
|
||||
use std::{ptr::NonNull, sync::Mutex};
|
||||
pub mod proxy;
|
||||
pub use proxy::*;
|
||||
|
||||
use lyra_ecs::DynamicBundle;
|
||||
use lyra_reflect::{Reflect, RegisteredType, FromType, AsRegisteredType};
|
||||
pub mod system;
|
||||
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";
|
||||
|
||||
/// 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";
|
||||
|
||||
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 {
|
||||
fn from_lua(value: mlua::Value<'lua>, _lua: &'lua Lua) -> mlua::Result<Self> {
|
||||
/// A trait used for registering a Lua type with the world.
|
||||
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 {
|
||||
mlua::Value::UserData(ud) => Ok(ud.borrow::<Self>()?.clone()),
|
||||
elua::Value::Userdata(ud) => Ok(ud.as_ref::<Self>()?.clone()),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl mlua::UserData for ScriptBorrow {}
|
||||
|
||||
pub fn reflect_user_data(ud: &mlua::AnyUserData) -> ScriptBorrow {
|
||||
ud.call_method::<_, ScriptBorrow>(FN_NAME_INTERNAL_REFLECT, ())
|
||||
.expect("Type does not implement '__internal_reflect' properly")
|
||||
}
|
||||
|
||||
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> elua::FromLuaVec<'lua> for ScriptBorrow {
|
||||
fn from_lua_value_vec(state: &'lua elua::State, mut values: elua::ValueVec<'lua>) -> elua::Result<Self> {
|
||||
if let Some(v) = values.pop_front() {
|
||||
ScriptBorrow::from_lua(state, v)
|
||||
} else {
|
||||
Err(elua::Error::Nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'lua> mlua::FromLua<'lua> for ScriptDynamicBundle {
|
||||
fn from_lua(value: mlua::Value<'lua>, _lua: &'lua Lua) -> mlua::Result<Self> {
|
||||
match value {
|
||||
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!(),
|
||||
}
|
||||
impl elua::Userdata for ScriptBorrow {
|
||||
fn name() -> String {
|
||||
"ScriptBorrow".to_string()
|
||||
}
|
||||
|
||||
fn build<'a>(_: &mut elua::UserdataBuilder<'a, Self>) { }
|
||||
}
|
||||
|
||||
impl mlua::UserData for ScriptDynamicBundle {
|
||||
fn add_methods<'lua, M: mlua::prelude::LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_function("new", |_, ()| {
|
||||
Ok(ScriptDynamicBundle(DynamicBundle::new()))
|
||||
});
|
||||
|
||||
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>();
|
||||
}
|
||||
/// Helper function used for reflecting userdata as a ScriptBorrow
|
||||
pub fn reflect_user_data(ud: &elua::AnyUserdata) -> ScriptBorrow {
|
||||
ud.execute_method::<_, ScriptBorrow>(FN_NAME_INTERNAL_REFLECT, ())
|
||||
.expect("Type does not implement internal reflect method properly")
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
pub mod util;
|
||||
pub use util::*;
|
||||
|
||||
pub mod math;
|
||||
pub use math::*;
|
||||
|
||||
pub mod ecs;
|
||||
pub use ecs::*;
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -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(())
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,46 +1,41 @@
|
|||
use std::sync::Mutex;
|
||||
|
||||
use tracing::debug;
|
||||
use elua::{AsLua, StdLibraries};
|
||||
|
||||
use crate::{ScriptHost, ScriptError, ScriptWorldPtr};
|
||||
use crate::{ScriptHost, ScriptError, ScriptWorldPtr, ScriptEntity};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct LuaHost;
|
||||
|
||||
fn try_call_lua_function(lua: &mlua::Lua, fn_name: &str) -> Result<(), ScriptError> {
|
||||
let globals = lua.globals();
|
||||
|
||||
match globals.get::<_, mlua::Function>(fn_name) {
|
||||
Ok(init_fn) => {
|
||||
init_fn.call(())
|
||||
.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));
|
||||
},
|
||||
fn try_call_lua_function(lua: &elua::State, fn_name: &str) -> Result<(), ScriptError> {
|
||||
let globals = lua.globals()?;
|
||||
|
||||
if globals.has_key(fn_name)? {
|
||||
let lua_fn = globals.get::<_, elua::Function>(fn_name)?;
|
||||
lua_fn.exec(())
|
||||
.map_err(ScriptError::MluaError)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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> {
|
||||
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() {
|
||||
provider.expose_api(&mut ctx)?;
|
||||
provider.expose_api(script_data, &mut ctx)?;
|
||||
}
|
||||
|
||||
let lua = ctx.lock().unwrap();
|
||||
lua.load(script)
|
||||
.set_name(&script_data.name)
|
||||
.exec()
|
||||
lua.load(&script_data.name, script)?
|
||||
.execute(())
|
||||
.map_err(|e| ScriptError::MluaError(e))?;
|
||||
drop(lua);
|
||||
|
||||
|
@ -52,22 +47,26 @@ impl ScriptHost for LuaHost {
|
|||
provider.setup_script(script_data, ctx)?;
|
||||
}
|
||||
|
||||
let ctx = ctx.lock().expect("Failure to get Lua ScriptContext");
|
||||
try_call_lua_function(&ctx, "init")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Runs the update step of the lua script.
|
||||
///
|
||||
/// 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> {
|
||||
/// It looks for an `update` function with zero parameters in the [`ScriptContext`] and executes it.
|
||||
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() {
|
||||
provider.update_script_environment(world.clone(), script_data, ctx)?;
|
||||
}
|
||||
|
||||
|
||||
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(())
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
&[],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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(())
|
||||
}
|
|
@ -1,22 +1,36 @@
|
|||
use lyra_ecs::query::dynamic::QueryDynamicType;
|
||||
use lyra_reflect::TypeRegistry;
|
||||
use mlua::{AnyUserDataExt, IntoLua, IntoLuaMulti};
|
||||
use std::{ptr::NonNull, sync::Arc};
|
||||
|
||||
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 {
|
||||
fn from_lua(value: mlua::Value<'lua>, _lua: &'lua mlua::Lua) -> mlua::Result<Self> {
|
||||
impl<'lua> elua::FromLua<'lua> for ScriptEntity {
|
||||
fn from_lua(_: &'lua elua::State, value: elua::Value<'lua>) -> elua::Result<Self> {
|
||||
match value {
|
||||
mlua::Value::UserData(ud) => Ok(ud.borrow::<Self>()?.clone()),
|
||||
mlua::Value::Nil => Err(mlua::Error::FromLuaConversionError { from: "Nil", to: "ScriptEntity", message: Some("Value was nil".to_string()) }),
|
||||
elua::Value::Userdata(ud) => Ok(ud.as_ref::<Self>()?.clone()),
|
||||
elua::Value::Nil => Err(elua::Error::type_mismatch("ScriptEntity", "Nil")),
|
||||
_ => 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)]
|
||||
pub enum WorldError {
|
||||
|
@ -24,108 +38,271 @@ pub enum WorldError {
|
|||
LuaInvalidUsage(String),
|
||||
}
|
||||
|
||||
impl mlua::UserData for ScriptWorldPtr {
|
||||
fn add_methods<'lua, M: mlua::prelude::LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_method_mut("spawn", |_, this, (bundle,): (ScriptDynamicBundle,)| {
|
||||
let world = unsafe { this.inner.as_mut() };
|
||||
|
||||
Ok(ScriptEntity(world.spawn(bundle.0)))
|
||||
});
|
||||
impl<'a> elua::FromLua<'a> for ScriptWorldPtr {
|
||||
fn from_lua(_: &'a elua::State, val: elua::Value<'a>) -> elua::Result<Self> {
|
||||
match val {
|
||||
elua::Value::Userdata(ud) => Ok(ud.as_ref::<Self>()?.clone()),
|
||||
elua::Value::Nil => Err(elua::Error::type_mismatch("ScriptWorldPtr", "Nil")),
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
methods.add_method("view_iter", |lua, this, queries: mlua::Variadic<mlua::AnyUserData>| {
|
||||
let world = unsafe { this.inner.as_ref() };
|
||||
let mut view = world.dynamic_view();
|
||||
impl elua::Userdata for ScriptWorldPtr {
|
||||
fn name() -> String {
|
||||
"World".to_string()
|
||||
}
|
||||
|
||||
for comp in queries.into_iter() {
|
||||
let script_brw = comp.call_function::<_, ScriptBorrow>(FN_NAME_INTERNAL_REFLECT_TYPE, ())
|
||||
.expect("Type does not implement '__internal_reflect_type' properly");
|
||||
let refl_comp = script_brw.reflect_branch.as_component_unchecked();
|
||||
fn build<'a>(builder: &mut elua::UserdataBuilder<'a, Self>) {
|
||||
builder
|
||||
.method_mut("spawn", |_, this, vals: elua::ValueVec| {
|
||||
let world = this.as_mut();
|
||||
|
||||
let dyn_type = QueryDynamicType::from_info(refl_comp.info);
|
||||
view.push(dyn_type);
|
||||
}
|
||||
let mut bundle = DynamicBundle::new();
|
||||
|
||||
let iter = view.into_iter();
|
||||
let mut reflected_iter = ReflectedIterator {
|
||||
world: this.clone(),
|
||||
dyn_view: DynamicViewIter::from(iter),
|
||||
reflected_components: None,
|
||||
};
|
||||
//while let Some(val) = vals.pop_front() {
|
||||
for (i, val) in vals.into_iter().enumerate() {
|
||||
let ud = val.as_userdata().ok_or(
|
||||
elua::Error::bad_arg(
|
||||
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, ()| {
|
||||
if let Some(row) = reflected_iter.next_lua(lua) {
|
||||
let row = row.into_iter().map(|(_, ud)| ud.into_lua(lua))
|
||||
.collect::<mlua::Result<Vec<mlua::Value>>>()?;
|
||||
Ok(mlua::MultiValue::from_vec(row))
|
||||
} else {
|
||||
Ok(mlua::Value::Nil.into_lua_multi(lua)?)
|
||||
ud.execute_method::<_, ScriptBorrow>(FN_NAME_INTERNAL_REFLECT, ())?
|
||||
} else {
|
||||
ud.execute_method::<_, ScriptBorrow>(FN_NAME_INTERNAL_REFLECT, ())?
|
||||
}
|
||||
};
|
||||
|
||||
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>)| {
|
||||
if queries.is_empty() {
|
||||
panic!("No components were provided!");
|
||||
}
|
||||
|
||||
let world = unsafe { this.inner.as_ref() };
|
||||
let mut view = world.dynamic_view();
|
||||
Ok(ScriptEntity(entity))
|
||||
})
|
||||
.method_mut(
|
||||
"view",
|
||||
|lua, this, (system, queries): (elua::Function, elua::ValueVec)| {
|
||||
if queries.is_empty() {
|
||||
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 reflect = comp.call_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 world = unsafe { this.inner.as_ref() };
|
||||
let mut view = world.dynamic_view();
|
||||
|
||||
let dyn_type = QueryDynamicType::from_info(refl_comp.info);
|
||||
view.push(dyn_type);
|
||||
}
|
||||
for (idx, comp) in queries.into_iter().enumerate() {
|
||||
match comp {
|
||||
elua::Value::Table(t) => {
|
||||
let name: String = t.get(elua::MetaMethod::Name)?;
|
||||
|
||||
let iter = view.into_iter();
|
||||
let mut reflected_iter = ReflectedIterator {
|
||||
world: this.clone(),
|
||||
dyn_view: DynamicViewIter::from(iter),
|
||||
reflected_components: None,
|
||||
};
|
||||
let lookup = world
|
||||
.try_get_resource::<LuaTableProxyLookup>()
|
||||
.ok_or(elua::Error::runtime(
|
||||
"Unable to lookup table proxy, none were ever registered!",
|
||||
))?;
|
||||
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>();
|
||||
|
||||
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)?
|
||||
let dyn_type = QueryDynamicType::from_info(info.clone());
|
||||
view.push(dyn_type);
|
||||
}
|
||||
None => {
|
||||
panic!("A userdata value was not returned!");
|
||||
elua::Value::Userdata(ud) => {
|
||||
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))
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>()
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
});
|
||||
}
|
||||
);
|
|
@ -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::*;
|
|
@ -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>()
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -1,8 +1,12 @@
|
|||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
use lyra_ecs::Component;
|
||||
use lyra_resource::ResHandle;
|
||||
|
||||
use crate::lyra_engine;
|
||||
|
||||
static SCRIPT_ID_COUNTER: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Script<T> {
|
||||
res: ResHandle<T>,
|
||||
|
@ -15,7 +19,7 @@ impl<T> Script<T> {
|
|||
Self {
|
||||
res: script,
|
||||
name: name.to_string(),
|
||||
id: 0 // TODO: make a counter
|
||||
id: SCRIPT_ID_COUNTER.fetch_add(1, Ordering::AcqRel)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,14 @@ use lyra_ecs::{world::World, Entity};
|
|||
#[derive(Clone)]
|
||||
pub struct ScriptEntity(pub Entity);
|
||||
|
||||
impl std::ops::Deref for ScriptEntity {
|
||||
type Target = Entity;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ScriptWorldPtr {
|
||||
pub inner: NonNull<World>,
|
||||
|
|
|
@ -12,9 +12,11 @@ mkShell rec {
|
|||
heaptrack
|
||||
mold
|
||||
udev
|
||||
lua5_4_compat
|
||||
];
|
||||
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
|
||||
libxkbcommon wayland # To use the wayland feature
|
||||
];
|
||||
|
|
Loading…
Reference in New Issue