diff --git a/examples/lua-scripting/scripts/test.lua b/examples/lua-scripting/scripts/test.lua index 2c4fb14..334d5be 100644 --- a/examples/lua-scripting/scripts/test.lua +++ b/examples/lua-scripting/scripts/test.lua @@ -1,4 +1,5 @@ local is_window_setup = false +local cube_entity = nil ---Return the userdata's name from its metatable. --- @@ -40,24 +41,26 @@ function on_init() local pos = Transform.from_translation(Vec3.new(0, 0, -8.0)) local e = world:spawn(pos, cube_scene) + cube_entity = e print("spawned entity " .. tostring(e)) end function on_first() if not is_window_setup then - world:view( - ---@param w Window - function (w) - if w.cursor_grab == CursorGrabMode.NONE then - w.cursor_grab = CursorGrabMode.LOCKED - w.cursor_visible = false - return w - else - is_window_setup = true - print("Window setup") - end - end, Window - ) + local view = View.new(Window) + local res = world:view(view) + + ---@param w Window + for en, w in res:iter() do + if w.cursor_grab == CursorGrabMode.NONE then + w.cursor_grab = CursorGrabMode.LOCKED + w.cursor_visible = false + en:update(w) + else + is_window_setup = true + print("Window setup") + end + end end ---@type EventReader @@ -77,42 +80,38 @@ end 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") + -- Get entities without WorldTransform + local view = View.new(Transform, Not(Has(WorldTransform)), Res(DeltaTime)) + local res = world:view(view) + ---@param transform Transform + ---@param dt DeltaTime + for entity, transform, dt in res:iter() do + transform:translate(0, 0.15 * dt, 0) + entity:update(transform) + end - world:view(function (t) - if move_objs ~= nil then - t:translate(0, move_objs * 0.35 * dt, 0) - return t - end - end, Transform) ]] + local changed_view = View.new(Changed(Transform)) + local changed_res = world:view(changed_view) + ---@param transform Transform + for _, transform in changed_res:iter() do + print("Entity transform changed to: '" .. tostring(transform) .. "' on tick " .. tostring(world:get_tick())) + end - ---@type number - local dt = world:resource(DeltaTime) + local tick_view = View.new(TickOf(Transform)) + local tick_res = world:view(tick_view) + ---@param tick number + for _, tick in tick_res:iter() do + print("Entity transform last changed on tick " .. tostring(tick)) + end - world:view( - ---@param t Transform - ---@param wt WorldTransform - function (t, wt) - print("Entity is at: " .. tostring(wt)) - t:translate(0, 0.15 * dt, 0) - return t - end, Transform, WorldTransform - ) - - --[[ world:view( - ---@param c Camera - function (c) - c.transform:translate(0, 0.15 * dt, 0) - - print("Moving camera to: " .. tostring(c.transform)) - - return c - end, Camera - ) ]] + local pos_view = View.new(Transform) + local vone = world:view_one(cube_entity --[[@as Entity]], pos_view) + local r = vone() -- short hand for 'vone:get()' + if r then + ---@type Transform + local pos = r[1] + print("Found cube entity at '" .. tostring(pos) .. "'") + end end --[[ function on_post_update() diff --git a/lyra-ecs/src/archetype.rs b/lyra-ecs/src/archetype.rs index c4e6294..aebfa93 100644 --- a/lyra-ecs/src/archetype.rs +++ b/lyra-ecs/src/archetype.rs @@ -1,6 +1,16 @@ -use std::{ptr::{NonNull, self}, alloc::{self, Layout, alloc, dealloc}, mem, collections::HashMap, cell::{RefCell, Ref, RefMut, BorrowError, BorrowMutError}, ops::DerefMut}; +use std::{ + alloc::{self, alloc, dealloc, Layout}, + cell::{BorrowError, BorrowMutError, Ref, RefCell, RefMut}, + collections::HashMap, + mem, + ops::DerefMut, + ptr::{self, NonNull}, +}; -use crate::{bundle::Bundle, component_info::ComponentInfo, world::ArchetypeEntityId, DynTypeId, Entity, Tick}; +use crate::{ + bundle::Bundle, component_info::ComponentInfo, world::ArchetypeEntityId, DynTypeId, Entity, + Tick, +}; #[derive(Clone)] pub struct ComponentColumn { @@ -32,8 +42,9 @@ impl ComponentColumn { pub unsafe fn alloc(component_layout: Layout, capacity: usize) -> NonNull { let new_layout = Layout::from_size_align( component_layout.size().checked_mul(capacity).unwrap(), - component_layout.align() - ).unwrap(); + component_layout.align(), + ) + .unwrap(); if let Some(data) = NonNull::new(alloc(new_layout)) { data @@ -44,7 +55,7 @@ impl ComponentColumn { pub unsafe fn new(info: ComponentInfo, capacity: usize) -> Self { let data = ComponentColumn::alloc(info.layout(), capacity); - + Self { data: RefCell::new(data), capacity, @@ -55,7 +66,7 @@ impl ComponentColumn { } /// Set a component from pointer at an entity index. - /// + /// /// # Safety /// This column must have space to fit the component, if it does not have room it will panic. pub unsafe fn set_at(&mut self, entity_index: usize, comp_src: NonNull, tick: Tick) { @@ -63,7 +74,7 @@ impl ComponentColumn { let mut data = self.data.borrow_mut(); let data = data.deref_mut(); - + let size = self.info.layout().size(); let dest = NonNull::new_unchecked(data.as_ptr().add(entity_index * size)); ptr::copy_nonoverlapping(comp_src.as_ptr(), dest.as_ptr(), size); @@ -83,7 +94,7 @@ impl ComponentColumn { let mut data = self.data.borrow_mut(); let data = data.deref_mut(); - + let size = self.info.layout().size(); let dest = NonNull::new_unchecked(data.as_ptr().add(entity_index * size)); ptr::copy_nonoverlapping(comp_src.as_ptr(), dest.as_ptr(), size); @@ -95,25 +106,25 @@ impl ComponentColumn { } /// Get a component at an entities index. - /// + /// /// # Safety - /// + /// /// This column MUST have the entity. If it does not, it WILL NOT panic and will cause UB. pub unsafe fn get(&self, entity_index: usize) -> Ref { let data = self.data.borrow(); Ref::map(data, |data| { - let ptr = NonNull::new_unchecked(data.as_ptr() - .add(entity_index * self.info.layout().size())) - .cast(); + let ptr = + NonNull::new_unchecked(data.as_ptr().add(entity_index * self.info.layout().size())) + .cast(); &*ptr.as_ptr() }) } /// Get a mutable borrow to the component at an entities index, ticking the entity. - /// + /// /// # Safety - /// + /// /// This column must have the entity. pub unsafe fn get_mut(&mut self, entity_index: usize, tick: &Tick) -> RefMut { self.entity_ticks[entity_index].tick_to(tick); @@ -121,22 +132,22 @@ impl ComponentColumn { let data = self.data.borrow_mut(); RefMut::map(data, |data| { - let ptr = NonNull::new_unchecked(data.as_ptr() - .add(entity_index * self.info.layout().size())) - .cast(); + let ptr = + NonNull::new_unchecked(data.as_ptr().add(entity_index * self.info.layout().size())) + .cast(); &mut *ptr.as_ptr() }) } /// Grow the column to fit `new_capacity` amount of components. - /// + /// /// Parameters: /// * `new_capacity` - The new capacity of components that can fit in this column. - /// + /// /// Note: This does not modify the Tick of this column, since no components were actually modified. - /// + /// /// # Safety - /// + /// /// Will panic if `new_capacity` is less than the current capacity of the column. pub unsafe fn grow(&mut self, new_capacity: usize) { assert!(new_capacity > self.capacity); @@ -155,7 +166,7 @@ impl ComponentColumn { // create a layout with the same alignment, but expand the size of the buffer. let old_layout = Layout::from_size_align_unchecked( layout.size().checked_mul(self.capacity).unwrap(), - layout.align() + layout.align(), ); mem::swap(data.deref_mut(), &mut new_ptr); @@ -163,7 +174,7 @@ impl ComponentColumn { } else { *data = new_ptr; } - + self.capacity = new_capacity; } @@ -171,19 +182,20 @@ impl ComponentColumn { pub unsafe fn remove_component(&mut self, entity_index: usize, tick: &Tick) -> Option { let _ = tick; // may be used at some point - debug_assert!(self.len > 0, "There are no entities in the Archetype to remove from!"); - + debug_assert!( + self.len > 0, + "There are no entities in the Archetype to remove from!" + ); + let mut data = self.data.borrow_mut(); let data = data.deref_mut(); let size = self.info.layout().size(); - let mut old_comp_ptr = NonNull::new_unchecked(data.as_ptr() - .add(entity_index * size)); + let mut old_comp_ptr = NonNull::new_unchecked(data.as_ptr().add(entity_index * size)); let moved_index = if entity_index != self.len - 1 { let moved_index = self.len - 1; - let mut new_comp_ptr = NonNull::new_unchecked(data.as_ptr() - .add(moved_index * size)); + let mut new_comp_ptr = NonNull::new_unchecked(data.as_ptr().add(moved_index * size)); ptr::copy_nonoverlapping(new_comp_ptr.as_ptr(), old_comp_ptr.as_ptr(), size); @@ -193,13 +205,43 @@ impl ComponentColumn { self.entity_ticks.swap_remove(entity_index); Some(moved_index) - } else { None }; + } else { + None + }; self.len -= 1; moved_index } + /// Get the pointer of the component for an entity. + /// + /// It is assumed that the component will be mutated, meaning the component's tick will be + /// updated. + pub fn component_ptr(&mut self, entity_index: usize, tick: &Tick) -> NonNull { + self.entity_ticks[entity_index] = *tick; + let size = self.info.layout().size(); + unsafe { NonNull::new_unchecked(self.borrow_ptr().as_ptr().add(entity_index * size)) } + } + + /// Get the pointer of the component for an entity without ticking. + /// + /// Since this does not tick, only use this if you know the pointer will not be mutated. + pub fn component_ptr_non_tick(&self, entity_index: usize) -> NonNull { + let size = self.info.layout().size(); + unsafe { NonNull::new_unchecked(self.borrow_ptr().as_ptr().add(entity_index * size)) } + } + + /// Get the tick of a component for an entity. + pub fn component_tick(&self, entity_index: usize) -> Option { + self.entity_ticks.get(entity_index).cloned() + } + + pub fn component_has_changed(&self, entity_index: usize, world_tick: Tick) -> Option { + self.component_tick(entity_index) + .map(|tick| *tick >= *world_tick - 1) + } + pub fn borrow_ptr(&self) -> Ref> { self.data.borrow() } @@ -226,13 +268,13 @@ impl ArchetypeId { pub(crate) fn increment(&mut self) -> Self { let v = self.0; self.0 += 1; - + ArchetypeId(v) } } /// Stores a group of entities with matching components. -/// +/// /// An Archetype can be thought of as a table, with entities as the rows and the entity's /// components as each column. This means you can have tightly packed components of entities and /// quickly iterate through entities with the same components. @@ -248,7 +290,7 @@ pub struct Archetype { /// Can be used to map `ArchetypeEntityId` to an Entity since `ArchetypeEntityId` has /// the index that the entity is stored at. pub(crate) entities: Vec, - pub(crate) columns: Vec, + pub columns: Vec, capacity: usize, } @@ -267,9 +309,10 @@ impl Archetype { } pub fn from_bundle_info(new_id: ArchetypeId, bundle_info: Vec) -> Archetype { - let columns = bundle_info.into_iter().map(|i| { - unsafe { ComponentColumn::new(i, DEFAULT_CAPACITY) } - }).collect(); + let columns = bundle_info + .into_iter() + .map(|i| unsafe { ComponentColumn::new(i, DEFAULT_CAPACITY) }) + .collect(); Archetype { id: new_id, @@ -281,13 +324,18 @@ impl Archetype { } /// Add an entity and its component bundle to the Archetype - /// + /// /// # Safety: - /// + /// /// Archetype must contain all of the components - pub(crate) fn add_entity(&mut self, entity: Entity, bundle: B, tick: &Tick) -> ArchetypeEntityId + pub(crate) fn add_entity( + &mut self, + entity: Entity, + bundle: B, + tick: &Tick, + ) -> ArchetypeEntityId where - B: Bundle + B: Bundle, { if self.capacity == self.entity_ids.len() { let new_cap = self.capacity * 2; @@ -301,28 +349,49 @@ impl Archetype { self.entities.push(entity); bundle.take(|data, type_id, info| { - self.put_component_at(tick, data, type_id, info.layout().size(), entity_index.0 as _); + self.put_component_at( + tick, + data, + type_id, + info.layout().size(), + entity_index.0 as _, + ); }); - + entity_index } - pub(crate) fn put_component_at(&mut self, tick: &Tick, ptr: NonNull, type_id: DynTypeId, size: usize, index: usize) { + pub(crate) fn put_component_at( + &mut self, + tick: &Tick, + ptr: NonNull, + type_id: DynTypeId, + size: usize, + index: usize, + ) { let _ = size; let col = self.get_column_mut(type_id).unwrap(); //unsafe { col.set_at(index, ptr, *tick) }; - unsafe { col.insert_entity(index, ptr, *tick); } + unsafe { + col.insert_entity(index, ptr, *tick); + } } /// Removes an entity from the Archetype and frees its components. - /// + /// /// Inside the component columns, the entities are swap-removed. Meaning that the last /// entity in the column is moved in the position of the entity that was removed. /// If there was an entity that was swapped, this function returns the entity, and its /// new index in the archetype that was put in place of the removed entity. - pub(crate) fn remove_entity(&mut self, entity: Entity, tick: &Tick) -> Option<(Entity, ArchetypeEntityId)> { - let entity_index = self.entity_ids.remove(&entity) + pub(crate) fn remove_entity( + &mut self, + entity: Entity, + tick: &Tick, + ) -> Option<(Entity, ArchetypeEntityId)> { + let entity_index = self + .entity_ids + .remove(&entity) .expect("The entity is not in this Archetype!"); let mut removed_entity: Option<(Entity, ArchetypeEntityId)> = None; @@ -339,19 +408,19 @@ impl Archetype { removed_entity = Some((just_removed, ArchetypeEntityId(moved_idx as u64))); } } else { - // If there wasn't a moved entity, make sure no other columns moved something. + // If there wasn't a moved entity, make sure no other columns moved something. assert!(removed_entity.is_none()); } } // safe from the .expect at the start of this method. //self.entity_ids.remove(&entity).unwrap(); - + // update the archetype index of the moved entity if let Some((moved, _old_idx)) = removed_entity { self.entity_ids.insert(moved, entity_index); } - + let removed = self.entities.swap_remove(entity_index.0 as _); assert_eq!(removed, entity); @@ -362,8 +431,12 @@ impl Archetype { /// Returns a boolean indicating whether this archetype can store the TypeIds given pub fn is_archetype_for(&self, types: &Vec) -> bool { if types.len() == self.columns.len() { - self.columns.iter().all(|c| types.contains(&c.info.type_id())) - } else { false } + self.columns + .iter() + .all(|c| types.contains(&c.info.type_id())) + } else { + false + } } /// Returns a boolean indicating whether this archetype has a column for `comp_type` @@ -383,18 +456,20 @@ impl Archetype { } /// Grows columns in the archetype - /// + /// /// Parameters: /// * `new_capacity` - The new capacity of components that can fit in this column. - /// + /// /// # Safety - /// + /// /// Will panic if new_capacity is less than the current capacity fn grow_columns(&mut self, new_capacity: usize) { assert!(new_capacity > self.capacity); for c in self.columns.iter_mut() { - unsafe { c.grow(new_capacity); } + unsafe { + c.grow(new_capacity); + } } self.capacity = new_capacity; @@ -412,11 +487,16 @@ impl Archetype { } /// Returns a mutable borrow to a component column for `type_id`. - /// + /// /// Note: This does not modify the tick for the column! - pub fn get_column_mut>(&mut self, type_id: I) -> Option<&mut ComponentColumn> { + pub fn get_column_mut>( + &mut self, + type_id: I, + ) -> Option<&mut ComponentColumn> { let type_id = type_id.into(); - self.columns.iter_mut().find(|c| c.info.type_id() == type_id) + self.columns + .iter_mut() + .find(|c| c.info.type_id() == type_id) } /// Reserves a slot in the columns for an entity and returns the index of that reserved spot @@ -427,8 +507,11 @@ impl Archetype { self.capacity = new_cap; } - debug_assert_eq!(self.entity_ids.len(), self.entities.len(), - "Somehow the Archetype's entity storage got unsynced"); + debug_assert_eq!( + self.entity_ids.len(), + self.entities.len(), + "Somehow the Archetype's entity storage got unsynced" + ); let entity_index = ArchetypeEntityId(self.entity_ids.len() as u64); self.entity_ids.insert(entity, entity_index); self.entities.push(entity); @@ -442,12 +525,15 @@ impl Archetype { /// Ensure that the internal entity lists are synced in length pub(crate) fn ensure_synced(&self) { - debug_assert_eq!(self.entity_ids.len(), self.entities.len(), - "Somehow the Archetype's entity storage got unsynced"); + debug_assert_eq!( + self.entity_ids.len(), + self.entities.len(), + "Somehow the Archetype's entity storage got unsynced" + ); } /// Moves the entity from this archetype into another one. - /// + /// /// # Safety /// The entity IS NOT removed from the old archetype. You must manually call [`Archetype::remove_entity`](crate::Archetype). /// It was done this way because I had some borrow check issues when writing [`World::insert`](crate::World) @@ -463,7 +549,7 @@ impl Archetype { // move the existing components into the new archetype for col in self.columns.iter() { let into_col = into_arch.get_column_mut(col.info.type_id()).unwrap(); - + // copy from the old column into the new column, then remove it from the old one unsafe { let ptr = col.borrow_ptr(); @@ -514,19 +600,20 @@ impl Archetype { } /// Extend the Archetype by adding more columns. - /// + /// /// In order to extend the Archetype, the archetype needs the components for the entities /// it already has. These are provided through the `new_columns` parameter. **If the Vec /// does not have the same amount of bundles in it as the amount of entities in the /// Archetype, it will panic!** pub fn extend(&mut self, tick: &Tick, new_columns: Vec) { - debug_assert_eq!(new_columns.len(), self.len(), "The amount of provided column does not \ - match the amount of entities"); - - let column_info = new_columns.iter() - .next() - .unwrap() - .info(); + debug_assert_eq!( + new_columns.len(), + self.len(), + "The amount of provided column does not \ + match the amount of entities" + ); + + let column_info = new_columns.iter().next().unwrap().info(); for coli in column_info.into_iter() { let col = unsafe { ComponentColumn::new(coli, self.capacity) }; @@ -534,11 +621,9 @@ impl Archetype { } for (eid, bundle) in new_columns.into_iter().enumerate() { - bundle.take(|ptr, tyid, _size| { - unsafe { - let col = self.get_column_mut(tyid).unwrap(); - col.insert_entity(eid, ptr, tick.clone()); - } + bundle.take(|ptr, tyid, _size| unsafe { + let col = self.get_column_mut(tyid).unwrap(); + col.insert_entity(eid, ptr, tick.clone()); }); } } @@ -550,7 +635,11 @@ mod tests { use rand::Rng; - use crate::{bundle::Bundle, tests::{Vec2, Vec3}, ComponentInfo, DynTypeId, DynamicBundle, Entity, EntityId, Tick}; + use crate::{ + bundle::Bundle, + tests::{Vec2, Vec3}, + ComponentInfo, DynTypeId, DynamicBundle, Entity, EntityId, Tick, + }; use super::Archetype; @@ -559,7 +648,7 @@ mod tests { let bundle = (Vec2::new(10.0, 20.0),); let entity = Entity { id: EntityId(0), - generation: 0 + generation: 0, }; let mut a = Archetype::from_bundle_info(super::ArchetypeId(0), bundle.info()); @@ -572,10 +661,10 @@ mod tests { #[test] fn one_entity_two_component() { - let bundle = (Vec2::new(10.0, 20.0),Vec3::new(15.0, 54.0, 84.0)); + let bundle = (Vec2::new(10.0, 20.0), Vec3::new(15.0, 54.0, 84.0)); let entity = Entity { id: EntityId(0), - generation: 0 + generation: 0, }; let mut a = Archetype::from_bundle_info(super::ArchetypeId(0), bundle.info()); @@ -595,12 +684,12 @@ mod tests { let b1 = (Vec2::new(10.0, 20.0),); let e1 = Entity { id: EntityId(0), - generation: 0 + generation: 0, }; let b2 = (Vec2::new(19.0, 43.0),); let e2 = Entity { id: EntityId(1), - generation: 0 + generation: 0, }; let mut a = Archetype::from_bundle_info(super::ArchetypeId(0), b1.info()); @@ -619,12 +708,12 @@ mod tests { let b1 = (Vec2::new(10.0, 20.0), Vec3::new(84.0, 283.0, 28.0)); let e1 = Entity { id: EntityId(0), - generation: 0 + generation: 0, }; let b2 = (Vec2::new(19.0, 43.0), Vec3::new(74.0, 28.0, 93.0)); let e2 = Entity { id: EntityId(1), - generation: 0 + generation: 0, }; let mut a = Archetype::from_bundle_info(super::ArchetypeId(0), b1.info()); @@ -659,7 +748,7 @@ mod tests { fn auto_archetype_growth() { let mut rng = rand::thread_rng(); let bundle_count = rng.gen_range(50..150); - + let mut bundles: Vec<(Vec2,)> = vec![]; bundles.reserve(bundle_count); @@ -669,14 +758,14 @@ mod tests { for i in 0..bundle_count { let c = (Vec2::rand(),); bundles.push(c); - + a.add_entity( Entity { id: EntityId(i as u64), - generation: 0 + generation: 0, }, c, - &Tick::default() + &Tick::default(), ); } println!("Inserted {} entities", bundle_count); @@ -701,25 +790,27 @@ mod tests { a.add_entity( Entity { id: EntityId(i as u64), - generation: 0 + generation: 0, }, (bundles[i],), - &Tick::default() + &Tick::default(), ); } // Remove the 'middle' entity in the column - let moved_entity = a.remove_entity( - Entity { - id: EntityId(1u64), - generation: 0, - }, - &Tick::default() - ).expect("No entity was moved"); + let moved_entity = a + .remove_entity( + Entity { + id: EntityId(1u64), + generation: 0, + }, + &Tick::default(), + ) + .expect("No entity was moved"); // The last entity in the column should have been moved assert!(moved_entity.0.id.0 == 2); - assert!(moved_entity.1.0 == 1); + assert!(moved_entity.1 .0 == 1); // make sure that the entities' component was actually moved in the column let col = &a.columns[0]; @@ -731,7 +822,8 @@ mod tests { #[test] fn dynamic_archetype() { let layout = Layout::new::(); - let info = ComponentInfo::new_unknown(Some("u32".to_string()), DynTypeId::Unknown(100), layout); + let info = + ComponentInfo::new_unknown(Some("u32".to_string()), DynTypeId::Unknown(100), layout); let infos = vec![info.clone()]; let mut a = Archetype::from_bundle_info(super::ArchetypeId(0), infos); @@ -744,10 +836,10 @@ mod tests { a.add_entity( Entity { id: EntityId(0), - generation: 0 + generation: 0, }, dynamic_bundle, - &Tick::default() + &Tick::default(), ); let col = a.columns.iter().next().unwrap(); @@ -764,15 +856,11 @@ mod tests { let ae = Entity { id: EntityId(0), - generation: 0 + generation: 0, }; - a.add_entity( - ae, - Vec2::new(10.0, 50.0), - &Tick::default() - ); + a.add_entity(ae, Vec2::new(10.0, 50.0), &Tick::default()); a.remove_entity(ae, &Tick::default()); } -} \ No newline at end of file +} diff --git a/lyra-ecs/src/entity.rs b/lyra-ecs/src/entity.rs index 62d10bb..b3629bf 100644 --- a/lyra-ecs/src/entity.rs +++ b/lyra-ecs/src/entity.rs @@ -77,4 +77,4 @@ impl Entities { pub(crate) fn insert_entity_record(&mut self, entity: Entity, record: Record) { self.arch_index.insert(entity.id, record); } -} +} \ No newline at end of file diff --git a/lyra-ecs/src/query/dynamic/view.rs b/lyra-ecs/src/query/dynamic/view.rs index 75bbeff..1aed1ff 100644 --- a/lyra-ecs/src/query/dynamic/view.rs +++ b/lyra-ecs/src/query/dynamic/view.rs @@ -24,6 +24,10 @@ impl DynamicViewState { } } + pub fn queries_num(&self) -> usize { + self.queries.len() + } + pub fn push(&mut self, dyn_query: QueryDynamicType) { self.queries.push(dyn_query); } diff --git a/lyra-ecs/src/query/dynamic/view_one.rs b/lyra-ecs/src/query/dynamic/view_one.rs index 3bdee13..b79a7c2 100644 --- a/lyra-ecs/src/query/dynamic/view_one.rs +++ b/lyra-ecs/src/query/dynamic/view_one.rs @@ -1,3 +1,5 @@ +use std::ops::{Deref, DerefMut}; + use crate::{query::Fetch, Entity, World}; use super::{DynamicType, FetchDynamicTypeUnsafe, QueryDynamicType}; @@ -9,16 +11,28 @@ use super::{DynamicType, FetchDynamicTypeUnsafe, QueryDynamicType}; /// since Rust doesn't actually need to know the types of what its iterating over. pub struct DynamicViewOne<'a> { world: &'a World, - pub entity: Entity, - pub queries: Vec + inner: DynamicViewOneOwned, +} + +impl<'a> Deref for DynamicViewOne<'a> { + type Target = DynamicViewOneOwned; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl<'a> DerefMut for DynamicViewOne<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } } impl<'a> DynamicViewOne<'a> { pub fn new(world: &'a World, entity: Entity) -> Self { Self { world, - entity, - queries: vec![], + inner: DynamicViewOneOwned::new(entity) } } @@ -26,36 +40,67 @@ impl<'a> DynamicViewOne<'a> { pub fn new_with(world: &'a World, entity: Entity, queries: Vec) -> Self { Self { world, + inner: DynamicViewOneOwned::new_with(entity, queries) + } + } + + pub fn get(self) -> Option> { + self.inner.get(&self.world) + } +} + +/// A variant of [`DynamicViewOne`] that doesn't store a borrow of the world. +#[derive(Clone)] +pub struct DynamicViewOneOwned { + pub entity: Entity, + pub queries: Vec +} + +impl DynamicViewOneOwned { + pub fn new(entity: Entity) -> Self { + Self { + entity, + queries: vec![], + } + } + + /// Create a new [`DynamicViewOne`] with queries. + pub fn new_with(entity: Entity, queries: Vec) -> Self { + Self { entity, queries } } - pub fn get(self) -> Option> { - let arch = self.world.entity_archetype(self.entity)?; - let aid = arch.entity_indexes().get(&self.entity)?; - - // get all fetchers for the queries - let mut fetchers: Vec = self.queries.iter() - .map(|q| unsafe { q.fetch(self.world, arch.id(), arch) } ) - .collect(); + pub fn get(self, world: &World) -> Option> { + dynamic_view_one_get_impl(world, &self.queries, self.entity) + } +} - let mut fetch_res = vec![]; - for fetcher in fetchers.iter_mut() { - if !fetcher.can_visit_item(*aid) { - return None; - } else { - let i = unsafe { fetcher.get_item(*aid) }; - fetch_res.push(i); - } - } - - if fetch_res.is_empty() { - None +fn dynamic_view_one_get_impl(world: &World, queries: &Vec, entity: Entity) -> Option> { + let arch = world.entity_archetype(entity)?; + let aid = arch.entity_indexes().get(&entity)?; + + // get all fetchers for the queries + let mut fetchers: Vec = queries.iter() + .map(|q| unsafe { q.fetch(world, arch.id(), arch) } ) + .collect(); + + let mut fetch_res = vec![]; + for fetcher in fetchers.iter_mut() { + if !fetcher.can_visit_item(*aid) { + return None; } else { - Some(fetch_res) + let i = unsafe { fetcher.get_item(*aid) }; + fetch_res.push(i); } } + + if fetch_res.is_empty() { + None + } else { + Some(fetch_res) + } } #[cfg(test)] diff --git a/lyra-ecs/src/query/resource.rs b/lyra-ecs/src/query/resource.rs index e8ac33c..f8ad30a 100644 --- a/lyra-ecs/src/query/resource.rs +++ b/lyra-ecs/src/query/resource.rs @@ -112,7 +112,7 @@ impl<'a, T: ResourceObject> Res<'a, T> { /// Returns a boolean indicating if the resource changed. pub fn changed(&self) -> bool { - *self.inner.tick - 1 >= *self.world_tick - 1 + *self.inner.tick >= *self.world_tick - 1 } /// The tick that this resource was last modified at diff --git a/lyra-ecs/src/tick.rs b/lyra-ecs/src/tick.rs index f2a8e65..16e0d88 100644 --- a/lyra-ecs/src/tick.rs +++ b/lyra-ecs/src/tick.rs @@ -72,6 +72,12 @@ impl TickTracker { #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Default, PartialOrd, Ord)] pub struct Tick(u64); +impl From for Tick { + fn from(value: u64) -> Self { + Self(value) + } +} + impl std::ops::Deref for Tick { type Target = u64; diff --git a/lyra-ecs/src/world.rs b/lyra-ecs/src/world.rs index 26da7ca..6d986c0 100644 --- a/lyra-ecs/src/world.rs +++ b/lyra-ecs/src/world.rs @@ -1,4 +1,4 @@ -use std::{any::TypeId, collections::HashMap, ptr::NonNull}; +use std::{any::TypeId, collections::HashMap, ops::Deref, ptr::NonNull}; use atomic_refcell::{AtomicRef, AtomicRefMut}; @@ -10,6 +10,14 @@ use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{dynamic #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct ArchetypeEntityId(pub u64); +impl Deref for ArchetypeEntityId { + type Target = u64; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct Record { pub id: ArchetypeId, @@ -18,11 +26,11 @@ pub struct Record { #[derive(Clone)] pub struct World { - pub(crate) archetypes: HashMap, + pub archetypes: HashMap, next_archetype_id: ArchetypeId, resources: HashMap, tracker: TickTracker, - pub(crate) entities: Entities, + pub entities: Entities, } impl Default for World { @@ -459,6 +467,13 @@ impl World { }) } + /// Get the tick of a resource. + /// + /// This tick represents the last time the resource was mutated. + pub fn get_resource_tick(&self) -> Option { + self.get_tracked_resource::().map(|r| r.tick) + } + /// Gets a reference to a change tracked resource. /// /// You will have to manually downcast the inner resource. Most people don't need this, see diff --git a/lyra-reflect/src/component.rs b/lyra-reflect/src/component.rs index a6fb54d..f16d323 100644 --- a/lyra-reflect/src/component.rs +++ b/lyra-reflect/src/component.rs @@ -1,6 +1,6 @@ use std::{any::{Any, TypeId}, cell::{Ref, RefMut}}; -use lyra_ecs::{Component, ComponentInfo, World, Entity, DynamicBundle}; +use lyra_ecs::{query::{filter::Changed, TickOf}, Component, ComponentInfo, DynamicBundle, Entity, Tick, World}; use crate::{Reflect, FromType}; @@ -18,6 +18,8 @@ pub struct ReflectedComponent { fn_bundle_insert: for<'a> fn (dynamic_bundle: &'a mut DynamicBundle, component: Box), fn_reflect: for<'a> fn (world: &'a World, entity: Entity) -> Option>, fn_reflect_mut: for<'a> fn (world: &'a mut World, entity: Entity) -> Option>, + fn_reflect_tick: for<'a> fn (world: &'a World, entity: Entity) -> Option, + fn_reflect_is_changed: for<'a> fn (world: &'a World, entity: Entity) -> Option, } impl ReflectedComponent { @@ -40,6 +42,14 @@ impl ReflectedComponent { pub fn reflect_mut<'a>(&'a mut self, world: &'a mut World, entity: Entity) -> Option> { (self.fn_reflect_mut)(world, entity) } + + pub fn reflect_tick<'a>(&'a self, world: &'a World, entity: Entity) -> Option { + (self.fn_reflect_tick)(world, entity) + } + + pub fn reflect_is_changed<'a>(&'a self, world: &'a World, entity: Entity) -> Option { + (self.fn_reflect_is_changed)(world, entity) + } } impl FromType for ReflectedComponent { @@ -69,6 +79,12 @@ impl FromType for ReflectedComponent { world.view_one::<&mut C>(entity) .get().map(|c| c as RefMut) }, + fn_reflect_tick: |world: &World, entity: Entity| { + world.view_one::>(entity).get() + }, + fn_reflect_is_changed: |world: &World, entity: Entity| { + world.view_one::>(entity).get() + } } } } \ No newline at end of file diff --git a/lyra-reflect/src/resource.rs b/lyra-reflect/src/resource.rs index db9ac45..3533348 100644 --- a/lyra-reflect/src/resource.rs +++ b/lyra-reflect/src/resource.rs @@ -1,6 +1,6 @@ use std::{any::{Any, TypeId}, ptr::NonNull}; -use lyra_ecs::{AtomicRef, AtomicRefMut, ResourceObject, World}; +use lyra_ecs::{AtomicRef, AtomicRefMut, ResourceObject, Tick, World}; use crate::{Reflect, FromType}; @@ -9,6 +9,8 @@ pub struct ReflectedResource { pub type_id: TypeId, fn_reflect: for<'a> fn (world: &'a World) -> Option>, + fn_reflect_tick: for<'a> fn (world: &'a World) -> Option, + fn_reflect_is_changed: fn (world: &World) -> Option, fn_reflect_mut: for<'a> fn (world: &'a mut World) -> Option>, fn_reflect_ptr: fn (world: &mut World) -> Option>, fn_refl_insert: fn (world: &mut World, this: Box), @@ -20,6 +22,14 @@ impl ReflectedResource { (self.fn_reflect)(world) } + pub fn reflect_tick(&self, world: &World) -> Option { + (self.fn_reflect_tick)(world) + } + + pub fn reflect_is_changed(&self, world: &World) -> Option { + (self.fn_reflect_is_changed)(world) + } + /// Retrieves a mutable reflected resource from the world. pub fn reflect_mut<'a>(&self, world: &'a mut World) -> Option> { (self.fn_reflect_mut)(world) @@ -47,6 +57,13 @@ impl FromType for ReflectedResource { AtomicRef::map(r, |r| r as &dyn Reflect) }) }, + fn_reflect_tick: |world: &World| { + world.get_resource_tick::() + }, + fn_reflect_is_changed: |world: &World| { + world.get_resource::() + .map(|r| r.changed()) + }, fn_reflect_mut: |world: &mut World| { world.get_resource_mut::() .map(|r| { diff --git a/lyra-scripting/scripts/lua/ecs.lua b/lyra-scripting/scripts/lua/ecs.lua new file mode 100644 index 0000000..2112f4e --- /dev/null +++ b/lyra-scripting/scripts/lua/ecs.lua @@ -0,0 +1,72 @@ +---Create a Resource query that will return the specific ECS world resource. +--- +---@see ResQuery +---@param resource table|userdata +---@return ResQuery +function Res(resource) + return ResQuery.new(resource) +end + +---@alias Query function|table|userdata + +---Create a `ChangedQuery` query that will return only if the resource or component has changed +---since last tick. +--- +---@see ChangedQuery +---@param val table|userdata +---@return ChangedQuery +function Changed(val) + return ChangedQuery.new(val) +end + +---Create a `HasQuery` filter that will return only if the entity has a specific component. +--- +---@see HasQuery +---@param val table|userdata +---@return HasQuery +function Has(val) + return HasQuery.new(val) +end + +---Create a `NotQuery` filter that will allow results if the query returns nothing or +---filter denies. +--- +---@see NotQuery +---@param val Query +---@return NotQuery +function Not(val) + return NotQuery.new(val) +end + +---Create a `AnyQuery` filter that will allow results if any of the queries return something. +--- +---The queries are evaluated in the order they were provided. +--- +---@see AnyQuery +---@param ... Query +---@return AnyQuery +function Any(...) + return AnyQuery.new(...) +end + +---Create a `TickOfQuery` for retrieving the tick of the resource or component on the entity. +--- +---@see TickOfQuery +---@param ... table|userdata +---@return TickOfQuery +function TickOf(...) + return TickOfQuery.new(...) +end + +---Create any `OptionalQuery` that allows for a query to return nothing. +--- +---If the query is a filter, its result will essentially be ignored. If the query returns `None` +---or `AlwaysNone`, this query will return `Nil`. If the query results in a value, its value +---will be the result of this query. +--- +---@see OptionalQuery +---@param q Query +---@return OptionalQuery +function Optional(q) + return OptionalQuery.new(q) +end \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/types/ecs/entity.lua b/lyra-scripting/scripts/lua/types/ecs/entity.lua index 52e3c00..b81a9c5 100644 --- a/lyra-scripting/scripts/lua/types/ecs/entity.lua +++ b/lyra-scripting/scripts/lua/types/ecs/entity.lua @@ -1,4 +1,29 @@ ---@meta +---An entity handle. ---@class Entity: userdata -Entity = {} \ No newline at end of file +Entity = {} + +---Get the id of the Entity. +---@return number +function Entity:id() end + +---Get the generation number of the Entity. +--- +---Entity handles are reused by the ECS World, the generation is used to tell reused Entity +---id's apart from previous generations. +--- +---@return number +function Entity:generation() end + +---A reference to an entity in the world. +--- +---Can be used to insert and update components on the entity. +--- +---@class EntityRef: userdata +EntityRef = {} + +---Update components that are **already** on an Entity. +--- +---@param ... any The components to update on the entity. +function EntityRef:update(...) end \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/types/ecs/event_reader.lua b/lyra-scripting/scripts/lua/types/ecs/event_reader.lua new file mode 100644 index 0000000..af55aa3 --- /dev/null +++ b/lyra-scripting/scripts/lua/types/ecs/event_reader.lua @@ -0,0 +1,9 @@ +---@meta + +---@class EventReader: userdata +EventReader = {} + +---Get an iterator for reading the event. +---@generic T +---@return fun(): T? iterator An iterator for reading the events. +function EventReader:read() end \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/types/ecs/init.lua b/lyra-scripting/scripts/lua/types/ecs/init.lua new file mode 100644 index 0000000..2a9eaef --- /dev/null +++ b/lyra-scripting/scripts/lua/types/ecs/init.lua @@ -0,0 +1,9 @@ +require "action_handler" +require "camera" +require "delta_time" +require "entity" +require "event_reader" +require "free_fly_camera" +require "window" +require "world_transform" +require "world" \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/types/ecs/query/any.lua b/lyra-scripting/scripts/lua/types/ecs/query/any.lua new file mode 100644 index 0000000..d5de560 --- /dev/null +++ b/lyra-scripting/scripts/lua/types/ecs/query/any.lua @@ -0,0 +1,25 @@ +---@meta + +---An ECS filter that will return if any of the provided queries return. +--- +---The queries are evaluated in the order they were provided. When a query or filter returns a value, +---that value will be returned. +--- +---Use the utility function `Any(...)` to create a new query since its faster to +---write than this. +--- +---@see Any +---@class AnyQuery: userdata +AnyQuery = {} + +---Create a new AnyQuery. +--- +---Use the utility function `Any(...)` to create a new query since its faster to +---write than this. +--- +---@see Any +---@param ... Query The query to invert. +function AnyQuery:new(...) end + +---An internal function used by the engine to retrieve the query result. +function AnyQuery:__lyra_internal_ecs_query_result(world, entity) end \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/types/ecs/query/changed.lua b/lyra-scripting/scripts/lua/types/ecs/query/changed.lua new file mode 100644 index 0000000..86a1e92 --- /dev/null +++ b/lyra-scripting/scripts/lua/types/ecs/query/changed.lua @@ -0,0 +1,22 @@ +---@meta + +---An ECS query used for obtaining **changed** resources or components from the world. +--- +---Use the utility function `Changed(...)` to create a new query since its faster to +---write than this. +--- +---This query will not return if the resource or component has not changed since the last tick. +--- +---@class ChangedQuery: userdata +ChangedQuery = {} + +---Create a new ChangedQuery. +--- +---Use the utility function `Changed(...)` to create a new query since its faster to +---write than this. +--- +---@param val table|userdata The component or resource to detect changed of. +function ChangedQuery:new(val) end + +---An internal function used by the engine to retrieve the query result. +function ChangedQuery:__lyra_internal_ecs_query_result(world, entity) end \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/types/ecs/query/has.lua b/lyra-scripting/scripts/lua/types/ecs/query/has.lua new file mode 100644 index 0000000..2116051 --- /dev/null +++ b/lyra-scripting/scripts/lua/types/ecs/query/has.lua @@ -0,0 +1,22 @@ +---@meta + +---An ECS filter that allows the query if the entity has the Component. +--- +---Use the utility function `Has(...)` to create a new query since its faster to +---write than this. +--- +---@see Has +---@class HasQuery: userdata +HasQuery = {} + +---Create a new HasQuery. +--- +---Use the utility function `Has(...)` to create a new query since its faster to +---write than this. +--- +---@see Has +---@param val table|userdata The component to look for on the entity. +function HasQuery:new(val) end + +---An internal function used by the engine to retrieve the query result. +function HasQuery:__lyra_internal_ecs_query_result(world, entity) end \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/types/ecs/query/init.lua b/lyra-scripting/scripts/lua/types/ecs/query/init.lua new file mode 100644 index 0000000..f2cd969 --- /dev/null +++ b/lyra-scripting/scripts/lua/types/ecs/query/init.lua @@ -0,0 +1,9 @@ +require "view" +require "view_one" +require "changed" +require "res" +require "has" +require "any" +require "not" +require "optional" +require "tick_of" \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/types/ecs/query/not.lua b/lyra-scripting/scripts/lua/types/ecs/query/not.lua new file mode 100644 index 0000000..60c6c2a --- /dev/null +++ b/lyra-scripting/scripts/lua/types/ecs/query/not.lua @@ -0,0 +1,22 @@ +---@meta + +---An ECS filter that inverts the provided filter/query result. +--- +---Use the utility function `Not(...)` to create a new query since its faster to +---write than this. +--- +---@see Not +---@class NotQuery: userdata +NotQuery = {} + +---Create a new NotQuery. +--- +---Use the utility function `Not(...)` to create a new query since its faster to +---write than this. +--- +---@see Not +---@param val Query The query to invert. +function NotQuery:new(val) end + +---An internal function used by the engine to retrieve the query result. +function NotQuery:__lyra_internal_ecs_query_result(world, entity) end \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/types/ecs/query/optional.lua b/lyra-scripting/scripts/lua/types/ecs/query/optional.lua new file mode 100644 index 0000000..afc35fd --- /dev/null +++ b/lyra-scripting/scripts/lua/types/ecs/query/optional.lua @@ -0,0 +1,26 @@ +---@meta + +---An ECS query that ignores filters and queries that dont return anything. +--- +---If the provided query returns nothing, this query will provide a `nil` value. +---The results of filters are essentially ignored, since it doesn't matter the result, this query +---will return. If the provided query has a result, this query will also return it. +--- +---Use the utility function `Optional(...)` to create a new query since its faster to +---write than this. +--- +---@see Optional +---@class OptionalQuery: userdata +OptionalQuery = {} + +---Create a new OptionalQuery. +--- +---Use the utility function `Optional(...)` to create a new query since its faster to +---write than this. +--- +---@see Optional +---@param val Query The query to invert. +function OptionalQuery:new(val) end + +---An internal function used by the engine to retrieve the query result. +function OptionalQuery:__lyra_internal_ecs_query_result(world, entity) end \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/types/ecs/query/res.lua b/lyra-scripting/scripts/lua/types/ecs/query/res.lua new file mode 100644 index 0000000..a392d70 --- /dev/null +++ b/lyra-scripting/scripts/lua/types/ecs/query/res.lua @@ -0,0 +1,16 @@ +---@meta + +---An ECS query used for obtaining Resources from the `World`. +---@class ResQuery: userdata +ResQuery = {} + +---Create a new ResQuery for getting a Resource from the `World`. +--- +---Use the utility function `Res(...)` to create a new query since its faster to +---write than this. +--- +---@param val table|userdata The resource type to obtain. +function ResQuery:new(val) end + +---An internal function used by the engine to retrieve the query result. +function ResQuery:__lyra_internal_ecs_query_result(world, entity) end \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/types/ecs/query/tick_of.lua b/lyra-scripting/scripts/lua/types/ecs/query/tick_of.lua new file mode 100644 index 0000000..cc98213 --- /dev/null +++ b/lyra-scripting/scripts/lua/types/ecs/query/tick_of.lua @@ -0,0 +1,22 @@ +---@meta + +---An ECS query that returns the tick of the resource or component provided. +--- +---Use the utility function `TickOf(...)` to create a new query since its faster to +---write than this. +--- +---@see TickOf +---@class TickOfQuery: userdata +TickOfQuery = {} + +---Create a new TickOfQuery. +--- +---Use the utility function `TickOf(...)` to create a new query since its faster to +---write than this. +--- +---@see TickOf +---@param val table|userdata The component or resource to retrieve the tick of. +function TickOfQuery:new(val) end + +---An internal function used by the engine to retrieve the query result. +function TickOfQuery:__lyra_internal_ecs_query_result(world, entity) end \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/types/ecs/query/view.lua b/lyra-scripting/scripts/lua/types/ecs/query/view.lua new file mode 100644 index 0000000..0be8af8 --- /dev/null +++ b/lyra-scripting/scripts/lua/types/ecs/query/view.lua @@ -0,0 +1,23 @@ +---@meta + +---@class View: userdata +View = {} + +---Create a new view to query for components and world resources. +--- +---Each parameter is a query. If you want to query entities with components, you would just use +---the component names. +---There are other queries, like `Changed` for querying for changed resources and components, +---and `Res` for querying for resources. +--- +---@return View +function View.new(...) end + +---@class ViewResult: userdata +ViewResult = {} + +---Returns an interator over the results of the View. +--- +---@generic T... +---@return fun(): EntityRef, T... iterator An iterator over the results. In the same order of the created View. +function ViewResult:iter() end \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/types/ecs/query/view_one.lua b/lyra-scripting/scripts/lua/types/ecs/query/view_one.lua new file mode 100644 index 0000000..dc16c9c --- /dev/null +++ b/lyra-scripting/scripts/lua/types/ecs/query/view_one.lua @@ -0,0 +1,19 @@ +---@meta + +---Results of a View over a single entity. +---@class ViewOneResult: userdata +ViewOneResult = {} + +---Returns the results of the view over a single entity. +--- +---@see ViewOneResult.__call +---@generic T... +---@return T... +function ViewOneResult:get() end + +---Returns the results of the view over a single entity. +--- +---@see ViewOneResult.get +---@generic T... +---@return T... +function ViewOneResult:__call() end \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/types/ecs/world.lua b/lyra-scripting/scripts/lua/types/ecs/world.lua index 5cd8f32..bd17a56 100644 --- a/lyra-scripting/scripts/lua/types/ecs/world.lua +++ b/lyra-scripting/scripts/lua/types/ecs/world.lua @@ -9,35 +9,6 @@ World = {} ---@return Entity function World:spawn(...) end ---- Query components from the world. ---- ---- The `system` parameter is a function with the requested components. The function ---- is ran every time for an entity. If you modify a component and want the changes to be ---- stored, return it in the function. The order of the returned components do not matter. ---- ---- Example: ---- ```lua ---- ---@type number ---- local dt = world:resource(DeltaTime) ---- ---- world:view( ---- ---@param t Transform ---- function (t) ---- -- Move the transform of the entity a bit ---- t:translate(0, 0.15 * dt, 0) ---- -- Since the transform was modified, it must be returned so ---- -- the engine can store the changes. ---- return t ---- end, ---- -- Specify the requested components here ---- Transform ---- ) ---- ``` ---- ----@param system fun(...): ... ----@param ... userdata -function World:view(system, ...) end - ---Get an ECS resource. --- ---Returns `nil` if the resource was not found in the world. Many resources will @@ -72,4 +43,53 @@ function World:add_resource(resource) end --- ---@param path string ---@return Handle asset An asset handle to the requested resource type. -function World:request_asset(path) end \ No newline at end of file +function World:request_asset(path) end + +---Get the current tick of the world. +--- +---The tick is used to drive changed detection of resources and components. +---The world tick is iterated every frame. +--- +---@return number +function World:get_tick() end + +---Get an event reader of a specific event. +--- +---@generic T +---@param event T +---@return EventReader +function World:read_event(event) end + +---View the world using the queries contained in a View. +--- +---Example: +---```lua +----- Get entities without WorldTransform +---local view = View.new(Transform, Not(Has(WorldTransform)), Res(DeltaTime)) +---local res = world:view_query(view) +------@param transform Transform +------@param dt DeltaTime +---for entity, transform, dt in res:iter() do +--- transform:translate(0, 0.15 * dt, 0) +--- entity:update(transform) +---end +---``` +--- +---@see View +---@see ViewResult +---@param view View +---@return ViewResult +function World:view(view) end + +---View a single entity in the world. +--- +---The view can contain queries and filters, but they will only be evaluated for a single entity. +--- +---@param en Entity +---@param view View +---@return ViewOneResult +function World:view_one(en, view) end + +--World global +---@type World +world = nil \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/types/init.lua b/lyra-scripting/scripts/lua/types/init.lua index 845a306..58beed2 100644 --- a/lyra-scripting/scripts/lua/types/init.lua +++ b/lyra-scripting/scripts/lua/types/init.lua @@ -1,10 +1,4 @@ -require "math.vec2" -require "math.vec3" -require "math.vec4" -require "math.quat" -require "math.transform" - -require "ecs.window" -require "ecs.delta_time" +require "math.init" +require "ecs.init" require "asset.handle" \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/types/math/init.lua b/lyra-scripting/scripts/lua/types/math/init.lua new file mode 100644 index 0000000..12c3c9a --- /dev/null +++ b/lyra-scripting/scripts/lua/types/math/init.lua @@ -0,0 +1,6 @@ +require "math.vec2" +require "math.vec3" +require "math.vec4" +require "math.quat" +require "math.transform" +require "math.angle" \ No newline at end of file diff --git a/lyra-scripting/src/lib.rs b/lyra-scripting/src/lib.rs index 5d2709b..6e54d22 100644 --- a/lyra-scripting/src/lib.rs +++ b/lyra-scripting/src/lib.rs @@ -38,6 +38,7 @@ impl ReflectBranch { /// /// # Panics /// If `self` is not a variant of [`ReflectBranch::Component`]. + #[deprecated(note = "use ReflectBranch::as_component instead")] pub fn as_component_unchecked(&self) -> &ReflectedComponent { match self { ReflectBranch::Component(c) => c, @@ -45,6 +46,16 @@ impl ReflectBranch { } } + /// Gets self as a [`ReflectedComponent`]. + /// + /// Returns `None` if `self` is not a component. + pub fn as_component(&self) -> Option<&ReflectedComponent> { + match self { + ReflectBranch::Component(c) => Some(c), + _ => None, + } + } + /// Returns a boolean indicating if `self` is a reflection of a Component. pub fn is_component(&self) -> bool { matches!(self, ReflectBranch::Component(_)) diff --git a/lyra-scripting/src/lua/dynamic_iter.rs b/lyra-scripting/src/lua/dynamic_iter.rs index 9eba864..40622af 100644 --- a/lyra-scripting/src/lua/dynamic_iter.rs +++ b/lyra-scripting/src/lua/dynamic_iter.rs @@ -1,14 +1,15 @@ -use std::{ptr::NonNull, ops::Deref}; +use std::ptr::NonNull; use lyra_ecs::{query::dynamic::DynamicViewStateIter, Entity}; use lyra_reflect::TypeRegistry; +use crate::ScriptWorldPtr; + #[cfg(feature = "lua")] use super::ReflectLuaProxy; #[cfg(feature = "lua")] pub struct ReflectedItem { - //pub proxy: &'a ReflectLuaProxy, pub comp_ptr: NonNull, pub comp_val: mlua::Value, } @@ -19,54 +20,63 @@ pub struct ReflectedRow { pub row: Vec, } +pub struct ReflectedIteratorOwned { + pub world_ptr: ScriptWorldPtr, + pub dyn_view: DynamicViewStateIter, +} + +impl ReflectedIteratorOwned { + pub fn next_lua(&mut self, lua: &mlua::Lua) -> Option { + let world = self.world_ptr.read(); + next_lua(lua, &world, &mut self.dyn_view) + } +} + pub struct ReflectedIterator<'a> { pub world: &'a lyra_ecs::World, pub dyn_view: DynamicViewStateIter, - pub reflected_components: Option> } impl<'a> ReflectedIterator<'a> { - #[cfg(feature = "lua")] pub fn next_lua(&mut self, lua: &mlua::Lua) -> Option { - use mlua::IntoLua; + next_lua(lua, &self.world, &mut self.dyn_view) + } +} - //let world = self.world.read(); - let n = self.dyn_view.next(&self.world); - - if let Some((en, row)) = n { - if self.reflected_components.is_none() { - self.reflected_components = self.world.get_resource::() - .map(|r| NonNull::from(r.deref())); - } +fn next_lua(lua: &mlua::Lua, world: &lyra_ecs::World, dyn_view: &mut DynamicViewStateIter) -> Option { + use mlua::IntoLua; - let mut dynamic_row = vec![]; - for d in row.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("Requested type was not found in TypeRegistry"); - let proxy = reg_type.get_data::() - // TODO: properly handle this error - .expect("Type does not have ReflectLuaProxy as a TypeData"); - let value = (proxy.fn_as_lua)(lua, d.ptr.cast()).unwrap() - .into_lua(lua).unwrap(); + //let world = world.read(); + let n = dyn_view.next(&world); + + if let Some((en, row)) = n { + let reflected_components = world.get_resource::().unwrap(); - dynamic_row.push(ReflectedItem { - comp_ptr: d.ptr, - comp_val: value - }); - } + let mut dynamic_row = vec![]; + for d in row.iter() { + let id = d.info.type_id().as_rust(); + + let reg_type = reflected_components.get_type(id) + .expect("Requested type was not found in TypeRegistry"); + let proxy = reg_type.get_data::() + // TODO: properly handle this error + .expect("Type does not have ReflectLuaProxy as a TypeData"); + let value = proxy.as_lua(lua, d.ptr.cast()).unwrap() + .into_lua(lua).unwrap(); - let row = ReflectedRow { - entity: en, - row: dynamic_row - }; - - Some(row) - } else { - None + dynamic_row.push(ReflectedItem { + comp_ptr: d.ptr, + comp_val: value + }); } + + let row = ReflectedRow { + entity: en, + row: dynamic_row + }; + + Some(row) + } else { + None } } \ No newline at end of file diff --git a/lyra-scripting/src/lua/ecs/mod.rs b/lyra-scripting/src/lua/ecs/mod.rs new file mode 100644 index 0000000..592426a --- /dev/null +++ b/lyra-scripting/src/lua/ecs/mod.rs @@ -0,0 +1,7 @@ +mod view; +pub use view::*; + +mod view_one; +pub use view_one::*; + +pub mod query; \ No newline at end of file diff --git a/lyra-scripting/src/lua/ecs/query/changed.rs b/lyra-scripting/src/lua/ecs/query/changed.rs new file mode 100644 index 0000000..4bc58d3 --- /dev/null +++ b/lyra-scripting/src/lua/ecs/query/changed.rs @@ -0,0 +1,107 @@ +use lyra_reflect::{ReflectWorldExt, RegisteredType, TypeRegistry}; +use mlua::IntoLua; + +use crate::{ + lua::{LuaComponent, ReflectLuaProxy, FN_NAME_INTERNAL_ECS_QUERY_RESULT}, + ReflectBranch, ScriptEntity, ScriptWorldPtr, +}; + +use super::LuaQueryResult; + +#[derive(Clone)] +pub struct LuaChangedQuery(LuaComponent); + +impl mlua::FromLua for LuaChangedQuery { + fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result { + let tyname = value.type_name(); + value + .as_userdata() + .ok_or(mlua::Error::FromLuaConversionError { + from: tyname, + to: "ChangedQuery".into(), + message: None, + }) + .and_then(|ud| ud.borrow::()) + .map(|ud| ud.clone()) + } +} + +impl mlua::UserData for LuaChangedQuery { + fn add_methods>(methods: &mut M) { + methods.add_function("new", |_, comp: LuaComponent| Ok(Self(comp))); + + methods.add_method( + FN_NAME_INTERNAL_ECS_QUERY_RESULT, + |lua, this, (world, en): (ScriptWorldPtr, ScriptEntity)| { + let mut world = world.write(); + let reflect = this.0.reflect_type()?; + + let tyid = reflect.reflect_branch.reflect_type_id(); + match &reflect.reflect_branch { + ReflectBranch::Component(comp) => { + if !comp.reflect_is_changed(&world, *en).unwrap_or(false) { + return Ok(LuaQueryResult::FilterDeny); + } + + // get the pointer of the component in the archetype column. + let arch = match world.entity_archetype(*en) { + Some(a) => a, + None => return Ok(LuaQueryResult::FilterDeny), + }; + let arch_idx = *arch.entity_indexes().get(&en).unwrap(); + + let col = match arch.get_column(tyid) { + Some(col) => col, + None => { + // the entity doesn't have the component + return Ok(LuaQueryResult::FilterDeny); + } + }; + + let col_ptr = col.component_ptr_non_tick(*arch_idx as usize).cast(); + + // get the type registry to apply the new value + let reg = world.get_resource::().unwrap(); + let reg_type = reg.get_type(tyid).unwrap(); + + let proxy = reg_type + .get_data::() + // 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"); + Ok(LuaQueryResult::Some(proxy.as_lua(lua, col_ptr)?)) + } + ReflectBranch::Resource(res) => { + // Check if the resource was changed. Per API spec, must return false. + match res.reflect_is_changed(&world) { + Some(false) => { + return Ok(LuaQueryResult::FilterDeny); + } + None => { + // the resource was not found + return Ok(LuaQueryResult::AlwaysNone); + } + _ => {} + } + + // unwrap is safe here since the match above would verify that the + // resource exists. + let res_ptr = res.reflect_ptr(&mut world).unwrap(); + let reg_type = world + .get_type::(tyid) + .expect("Resource is not type registered!"); + let proxy = reg_type + .get_data::() + .expect("Type does not have ReflectLuaProxy as a TypeData"); + + Ok(LuaQueryResult::Some( + proxy + .as_lua(lua, res_ptr.cast()) + .and_then(|ud| ud.into_lua(lua))?, + )) + } + } + }, + ); + } +} diff --git a/lyra-scripting/src/lua/ecs/query/has.rs b/lyra-scripting/src/lua/ecs/query/has.rs new file mode 100644 index 0000000..c55c634 --- /dev/null +++ b/lyra-scripting/src/lua/ecs/query/has.rs @@ -0,0 +1,60 @@ +use crate::{ + lua::{LuaComponent, FN_NAME_INTERNAL_ECS_QUERY_RESULT}, + ScriptEntity, ScriptWorldPtr, +}; + +use super::LuaQueryResult; + +#[derive(Clone)] +pub struct LuaHasQuery(LuaComponent); + +impl mlua::FromLua for LuaHasQuery { + fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result { + let tyname = value.type_name(); + value + .as_userdata() + .ok_or(mlua::Error::FromLuaConversionError { + from: tyname, + to: "HasQuery".into(), + message: None, + }) + .and_then(|ud| ud.borrow::()) + .map(|ud| ud.clone()) + } +} + +impl mlua::UserData for LuaHasQuery { + fn add_methods>(methods: &mut M) { + methods.add_function("new", |_, comp: LuaComponent| { + let reflect = comp.reflect_type()?; + if !reflect.reflect_branch.is_component() { + Err(mlua::Error::runtime("provided type is not a component!")) + } else { + Ok(Self(comp)) + } + }); + + methods.add_method( + FN_NAME_INTERNAL_ECS_QUERY_RESULT, + |_, this, (world, en): (ScriptWorldPtr, ScriptEntity)| { + let world = world.write(); + let reflect = this.0.reflect_type()?; + + let tyid = reflect.reflect_branch.reflect_type_id(); + + // try to find the entity's archetype and the component column in the archetype + let arch = match world.entity_archetype(*en) { + Some(a) => a, + None => return Ok(LuaQueryResult::FilterDeny) + }; + let component_col = arch.get_column(tyid); + + if component_col.is_some() { + Ok(LuaQueryResult::FilterPass) + } else { + Ok(LuaQueryResult::FilterDeny) + } + }, + ); + } +} diff --git a/lyra-scripting/src/lua/ecs/query/mod.rs b/lyra-scripting/src/lua/ecs/query/mod.rs new file mode 100644 index 0000000..37a489a --- /dev/null +++ b/lyra-scripting/src/lua/ecs/query/mod.rs @@ -0,0 +1,183 @@ +mod res; +pub use res::*; + +mod changed; +pub use changed::*; + +mod has; +pub use has::*; + +mod not; +pub use not::*; + +mod or; +pub use or::*; + +mod tick_of; +pub use tick_of::*; + +mod optional; +pub use optional::*; + +use lyra_ecs::Entity; + +use crate::{ + lua::{LuaComponent, FN_NAME_INTERNAL_ECS_QUERY_RESULT}, + ScriptEntity, ScriptWorldPtr, +}; + +#[derive(Clone)] +enum QueryInner { + Component(LuaComponent), + Function(mlua::Function), +} + +#[derive(Clone)] +pub struct LuaQuery(QueryInner); + +impl LuaQuery { + pub fn new(query: LuaComponent) -> Self { + Self(QueryInner::Component(query)) + } + + pub fn from_function(f: mlua::Function) -> Self { + Self(QueryInner::Function(f)) + } + + /// Get the result of the query + /// + /// > WARNING: ensure that the world pointer is not locked. If its locked when you call this, + /// you WILL cause a deadlock. + pub fn get_query_result( + &self, + world: ScriptWorldPtr, + entity: Entity, + ) -> mlua::Result { + let lua_en = ScriptEntity(entity); + match &self.0 { + QueryInner::Component(comp) => { + comp.call_method(FN_NAME_INTERNAL_ECS_QUERY_RESULT, (world, lua_en)) + } + QueryInner::Function(function) => function.call((world, lua_en)), + } + } +} + +impl mlua::FromLua for LuaQuery { + fn from_lua(value: mlua::Value, lua: &mlua::Lua) -> mlua::Result { + let tyname = value.type_name(); + + if let Some(f) = value.as_function() { + Ok(Self(QueryInner::Function(f.clone()))) + } else if let Ok(c) = LuaComponent::from_lua(value, lua) { + Ok(Self(QueryInner::Component(c))) + } else { + Err(mlua::Error::FromLuaConversionError { + from: tyname, + to: "Query".into(), + message: Some("expected query function, table, or user data".into()), + }) + } + } +} + +#[derive(Debug, Clone)] +pub enum LuaQueryResult { + None, + AlwaysNone, + FilterPass, + FilterDeny, + Some(mlua::Value), +} + +impl mlua::IntoLua for LuaQueryResult { + fn into_lua(self, lua: &mlua::Lua) -> mlua::Result { + let t = lua.create_table()?; + t.set("enum_ty", "query_result")?; + + match self { + LuaQueryResult::None => { + t.set("result", "none")?; + } + LuaQueryResult::AlwaysNone => { + t.set("result", "always_none")?; + } + LuaQueryResult::FilterPass => { + t.set("result", "filter_pass")?; + } + LuaQueryResult::FilterDeny => { + t.set("result", "filter_deny")?; + } + LuaQueryResult::Some(value) => { + t.set("result", "some")?; + t.set("val", value)?; + } + } + + t.into_lua(lua) + } +} + +#[inline(always)] +fn from_lua_error_query_result(ty: &'static str, msg: &str) -> mlua::Error { + mlua::Error::FromLuaConversionError { + from: ty, + to: "QueryResult".into(), + message: Some(msg.into()), + } +} + +#[inline(always)] +fn malformed_table_error_query_result(ty: &'static str, missing_field: &str) -> mlua::Error { + mlua::Error::FromLuaConversionError { + from: ty, + to: "QueryResult".into(), + message: Some(format!( + "malformed table, cannot convert, failed to get field '{}'", + missing_field + )), + } +} + +impl mlua::FromLua for LuaQueryResult { + fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result { + let ty = value.type_name(); + let table = value + .as_table() + .ok_or(from_lua_error_query_result(ty, "expected Table"))?; + + let var_name: String = table + .get("enum_ty") + .map_err(|_| malformed_table_error_query_result(ty, "enum_ty"))?; + if var_name != "query_result" { + return Err(mlua::Error::FromLuaConversionError { + from: ty, + to: "QueryResult".into(), + message: Some(format!("mismatched enum_ty: '{}'", var_name)), + }); + } + + let result: String = table + .get("result") + .map_err(|_| malformed_table_error_query_result(ty, "result"))?; + let result_str = result.as_str(); + + match result_str { + "none" => Ok(Self::None), + "always_none" => Ok(Self::AlwaysNone), + "filter_pass" => Ok(Self::FilterPass), + "filter_deny" => Ok(Self::FilterDeny), + "some" => { + let val: mlua::Value = table + .get("val") + .map_err(|_| malformed_table_error_query_result(ty, "val"))?; + Ok(Self::Some(val)) + } + _ => Err(mlua::Error::FromLuaConversionError { + from: ty, + to: "QueryResult".into(), + message: Some(format!("unknown result type: '{}'", result_str)), + }), + } + } +} diff --git a/lyra-scripting/src/lua/ecs/query/not.rs b/lyra-scripting/src/lua/ecs/query/not.rs new file mode 100644 index 0000000..91a7182 --- /dev/null +++ b/lyra-scripting/src/lua/ecs/query/not.rs @@ -0,0 +1,47 @@ +use crate::{ + lua::FN_NAME_INTERNAL_ECS_QUERY_RESULT, + ScriptEntity, ScriptWorldPtr, +}; + +use super::{LuaQuery, LuaQueryResult}; + +#[derive(Clone)] +pub struct LuaNotQuery(LuaQuery); + +impl mlua::FromLua for LuaNotQuery { + fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result { + let tyname = value.type_name(); + value + .as_userdata() + .ok_or(mlua::Error::FromLuaConversionError { + from: tyname, + to: "NotQuery".into(), + message: None, + }) + .and_then(|ud| ud.borrow::()) + .map(|ud| ud.clone()) + } +} + +impl mlua::UserData for LuaNotQuery { + fn add_methods>(methods: &mut M) { + methods.add_function("new", |_, q: LuaQuery| { + Ok(Self(q)) + }); + + methods.add_method( + FN_NAME_INTERNAL_ECS_QUERY_RESULT, + |_, this, (world, en): (ScriptWorldPtr, ScriptEntity)| { + let res = this.0.get_query_result(world, en.0)?; + + match res { + LuaQueryResult::None => Ok(LuaQueryResult::FilterPass), + LuaQueryResult::AlwaysNone => Ok(LuaQueryResult::FilterPass), + LuaQueryResult::FilterPass => Ok(LuaQueryResult::FilterDeny), + LuaQueryResult::FilterDeny => Ok(LuaQueryResult::FilterPass), + LuaQueryResult::Some(_) => Ok(LuaQueryResult::FilterDeny), + } + }, + ); + } +} diff --git a/lyra-scripting/src/lua/ecs/query/optional.rs b/lyra-scripting/src/lua/ecs/query/optional.rs new file mode 100644 index 0000000..3836223 --- /dev/null +++ b/lyra-scripting/src/lua/ecs/query/optional.rs @@ -0,0 +1,44 @@ +use crate::{ + lua::FN_NAME_INTERNAL_ECS_QUERY_RESULT, ScriptEntity, ScriptWorldPtr, +}; + +use super::{LuaQuery, LuaQueryResult}; + +#[derive(Clone)] +pub struct LuaOptionalQuery(LuaQuery); + +impl mlua::FromLua for LuaOptionalQuery { + fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result { + let tyname = value.type_name(); + value + .as_userdata() + .ok_or(mlua::Error::FromLuaConversionError { + from: tyname, + to: "OptionalQuery".into(), + message: None, + }) + .and_then(|ud| ud.borrow::()) + .map(|ud| ud.clone()) + } +} + +impl mlua::UserData for LuaOptionalQuery { + fn add_methods>(methods: &mut M) { + methods.add_function("new", |_, q: LuaQuery| Ok(Self(q))); + + methods.add_method( + FN_NAME_INTERNAL_ECS_QUERY_RESULT, + |_, this, (world, en): (ScriptWorldPtr, ScriptEntity)| { + let res = this.0.get_query_result(world, en.0)?; + + match res { + LuaQueryResult::None => Ok(LuaQueryResult::Some(mlua::Value::Nil)), + LuaQueryResult::AlwaysNone => Ok(LuaQueryResult::Some(mlua::Value::Nil)), + LuaQueryResult::FilterPass => Ok(LuaQueryResult::FilterPass), + LuaQueryResult::FilterDeny => Ok(LuaQueryResult::FilterPass), + LuaQueryResult::Some(v) => Ok(LuaQueryResult::Some(v)), + } + }, + ); + } +} diff --git a/lyra-scripting/src/lua/ecs/query/or.rs b/lyra-scripting/src/lua/ecs/query/or.rs new file mode 100644 index 0000000..af2dbab --- /dev/null +++ b/lyra-scripting/src/lua/ecs/query/or.rs @@ -0,0 +1,48 @@ +use crate::{lua::FN_NAME_INTERNAL_ECS_QUERY_RESULT, ScriptEntity, ScriptWorldPtr}; + +use super::{LuaQuery, LuaQueryResult}; + +#[derive(Clone)] +pub struct LuaOrQuery(Vec); + +impl mlua::FromLua for LuaOrQuery { + fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result { + let tyname = value.type_name(); + value + .as_userdata() + .ok_or(mlua::Error::FromLuaConversionError { + from: tyname, + to: "OrQuery".into(), + message: None, + }) + .and_then(|ud| ud.borrow::()) + .map(|ud| ud.clone()) + } +} + +impl mlua::UserData for LuaOrQuery { + fn add_methods>(methods: &mut M) { + methods.add_function("new", |_, qs: mlua::Variadic| { + Ok(Self(qs.to_vec())) + }); + + methods.add_method( + FN_NAME_INTERNAL_ECS_QUERY_RESULT, + |_, this, (world, en): (ScriptWorldPtr, ScriptEntity)| { + for q in &this.0 { + let res = q.get_query_result(world.clone(), en.0)?; + + match res { + LuaQueryResult::None + | LuaQueryResult::AlwaysNone + | LuaQueryResult::FilterDeny => {} + LuaQueryResult::FilterPass => return Ok(LuaQueryResult::FilterPass), + LuaQueryResult::Some(v) => return Ok(LuaQueryResult::Some(v)), + } + } + + Ok(LuaQueryResult::FilterDeny) + }, + ); + } +} diff --git a/lyra-scripting/src/lua/ecs/query/res.rs b/lyra-scripting/src/lua/ecs/query/res.rs new file mode 100644 index 0000000..a9df81a --- /dev/null +++ b/lyra-scripting/src/lua/ecs/query/res.rs @@ -0,0 +1,63 @@ +use lyra_reflect::{ReflectWorldExt, RegisteredType}; +use mlua::IntoLua; + +use crate::{ + lua::{LuaComponent, ReflectLuaProxy, FN_NAME_INTERNAL_ECS_QUERY_RESULT}, + ScriptEntity, ScriptWorldPtr, +}; + +use super::LuaQueryResult; + +#[derive(Clone)] +pub struct LuaResQuery { + ty: LuaComponent, +} + +impl mlua::FromLua for LuaResQuery { + fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result { + let tyname = value.type_name(); + value + .as_userdata() + .ok_or(mlua::Error::FromLuaConversionError { + from: tyname, + to: "ResQuery".into(), + message: None, + }) + .and_then(|ud| ud.borrow::()) + .map(|ud| ud.clone()) + } +} + +impl mlua::UserData for LuaResQuery { + fn add_methods>(methods: &mut M) { + methods.add_function("new", |_, comp: LuaComponent| Ok(Self { ty: comp })); + + methods.add_method( + FN_NAME_INTERNAL_ECS_QUERY_RESULT, + |lua, this, (world, _): (ScriptWorldPtr, ScriptEntity)| { + let mut world = world.write(); + let reflect = this.ty.reflect_type()?; + + let res = reflect.reflect_branch.as_resource_unchecked(); + if let Some(res_ptr) = res.reflect_ptr(&mut world) { + let reg_type = world + .get_type::(reflect.reflect_branch.reflect_type_id()) + .expect("Resource is not type registered!"); + let proxy = reg_type + .get_data::() + .expect("Type does not have ReflectLuaProxy as a TypeData"); + + Ok(LuaQueryResult::Some( + proxy + .as_lua(lua, res_ptr.cast()) + .and_then(|ud| ud.into_lua(lua))?, + )) + } else { + // if the resource is not found in the world, return nil + //Ok(mlua::Value::Nil) + Ok(LuaQueryResult::AlwaysNone) + } + }, + ); + } +} diff --git a/lyra-scripting/src/lua/ecs/query/tick_of.rs b/lyra-scripting/src/lua/ecs/query/tick_of.rs new file mode 100644 index 0000000..d190b3a --- /dev/null +++ b/lyra-scripting/src/lua/ecs/query/tick_of.rs @@ -0,0 +1,54 @@ +use crate::{ + lua::{LuaComponent, FN_NAME_INTERNAL_ECS_QUERY_RESULT}, + ReflectBranch, ScriptEntity, ScriptWorldPtr, +}; + +use super::LuaQueryResult; + +#[derive(Clone)] +pub struct LuaTickOfQuery(LuaComponent); + +impl mlua::FromLua for LuaTickOfQuery { + fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result { + let tyname = value.type_name(); + value + .as_userdata() + .ok_or(mlua::Error::FromLuaConversionError { + from: tyname, + to: "TickOfQuery".into(), + message: None, + }) + .and_then(|ud| ud.borrow::()) + .map(|ud| ud.clone()) + } +} + +impl mlua::UserData for LuaTickOfQuery { + fn add_methods>(methods: &mut M) { + methods.add_function("new", |_, comp: LuaComponent| Ok(Self(comp))); + + methods.add_method( + FN_NAME_INTERNAL_ECS_QUERY_RESULT, + |_, this, (world, en): (ScriptWorldPtr, ScriptEntity)| { + let world = world.read(); + let reflect = this.0.reflect_type()?; + match &reflect.reflect_branch { + ReflectBranch::Component(comp) => { + if let Some(tick) = comp.reflect_tick(&world, *en) { + Ok(LuaQueryResult::Some(mlua::Value::Number(*tick as _))) + } else { + Ok(LuaQueryResult::FilterDeny) + } + } + ReflectBranch::Resource(res) => { + if let Some(tick) = res.reflect_tick(&world) { + Ok(LuaQueryResult::Some(mlua::Value::Number(*tick as _))) + } else { + Ok(LuaQueryResult::FilterDeny) + } + } + } + }, + ); + } +} diff --git a/lyra-scripting/src/lua/ecs/view.rs b/lyra-scripting/src/lua/ecs/view.rs new file mode 100644 index 0000000..bb051ed --- /dev/null +++ b/lyra-scripting/src/lua/ecs/view.rs @@ -0,0 +1,345 @@ +use std::sync::Arc; + +use atomic_refcell::AtomicRefCell; +use lyra_ecs::{ + query::dynamic::{DynamicViewState, QueryDynamicType}, + Entity, +}; +use mlua::{IntoLua, IntoLuaMulti, ObjectLike}; + +use crate::{ + lua::{ + LuaComponent, LuaEntityRef, ReflectedIteratorOwned, TypeLookup, WorldError, + FN_NAME_INTERNAL_ECS_QUERY_RESULT, FN_NAME_INTERNAL_REFLECT_TYPE, + }, + ScriptBorrow, ScriptWorldPtr, +}; + +use super::query::{LuaQuery, LuaQueryResult}; + +#[derive(Clone)] +pub(crate) enum ViewQueryItem { + UserData(mlua::AnyUserData), + Table(mlua::Table), + Function(mlua::Function), +} + +impl mlua::FromLua for ViewQueryItem { + fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result { + let tyname = value.type_name(); + match value { + mlua::Value::Table(table) => Ok(Self::Table(table)), + mlua::Value::Function(function) => Ok(Self::Function(function)), + mlua::Value::UserData(any_user_data) => Ok(Self::UserData(any_user_data)), + _ => Err(mlua::Error::FromLuaConversionError { + from: tyname, + to: "ViewQueryItem".into(), + message: Some("expected Table, Function, or UserData".into()), + }), + } + } +} + +impl ViewQueryItem { + /// Returns `true` if the QueryItem has a function of `name`. + /// + /// Returns `false` if self is a function. + pub fn has_function(&self, name: &str) -> mlua::Result { + match self { + Self::UserData(ud) => ud.get::(name).map(|v| !v.is_nil()), + Self::Table(t) => t.contains_key(name), + Self::Function(_) => Ok(false), + } + } + + /// Returns `true` if self is a Query. + /// + /// If self is a function, it will return true. Else, it checks for a function with the + /// name of [`FN_NAME_INTERNAL_ECS_QUERY_RESULT`] on the table or userdata. If the function + /// is found, it returns true. + pub fn is_query(&self) -> mlua::Result { + Ok(matches!(self, ViewQueryItem::Function(_)) + || self.has_function(FN_NAME_INTERNAL_ECS_QUERY_RESULT)?) + } + + /// Get self as a [`LuaQuery`]. + /// + /// If self is a function, it assumes that it is a filter. + pub fn as_query(&self) -> LuaQuery { + match self.clone() { + ViewQueryItem::UserData(ud) => LuaQuery::new(LuaComponent::UserData(ud)), + ViewQueryItem::Table(t) => LuaQuery::new(LuaComponent::Table(t)), + ViewQueryItem::Function(function) => LuaQuery::from_function(function), + } + } +} + +#[derive(Clone)] +pub struct View { + pub(crate) items: Vec, +} + +impl mlua::FromLua for View { + fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result { + let tyname = value.type_name(); + value + .as_userdata() + .ok_or(mlua::Error::FromLuaConversionError { + from: tyname, + to: "View".into(), + message: None, + }) + .and_then(|ud| ud.borrow::()) + .map(|ud| ud.clone()) + } +} + +impl mlua::UserData for View { + fn add_methods>(methods: &mut M) { + methods.add_function("new", |_, args: mlua::Variadic| { + Ok(Self { + items: args.iter().cloned().collect(), + }) + }); + } +} + +/// Results of queries in a View. +/// +/// Represents the results of multiple queries. +#[derive(Debug, Clone)] +pub(crate) enum ViewQueryResult { + None, + AlwaysNone, + FilterDeny, + /// The results of the queries and the index they should be inserted at in the resulting row. + Some(Vec<(mlua::Value, u32)>), +} + +#[derive(Clone)] +pub struct ViewResult { + world: ScriptWorldPtr, + reflected_iter: Arc>, + /// The queries and the index they would be inserted in the result. + queries: Vec<(LuaQuery, u32)>, +} + +unsafe impl Send for ViewResult {} + +impl ViewResult { + pub fn new(world: ScriptWorldPtr, view: &View) -> Result { + let items = view.items.clone(); + let w = world.read(); + let mut view = DynamicViewState::new(); + let mut queries = vec![]; + + for (idx, comp) in items.iter().enumerate() { + if comp.is_query()? { + queries.push((comp.as_query(), idx as u32)); + continue; + } + + match comp { + ViewQueryItem::Table(t) => { + let name: String = t.get(mlua::MetaMethod::Type.name())?; + + let lookup = w.get_resource::().ok_or(mlua::Error::runtime( + "Unable to lookup table proxy, none were ever registered!", + ))?; + let info = lookup.comp_info_from_name.get(&name).ok_or_else(|| { + mlua::Error::BadArgument { + to: Some("ViewResult.new".into()), + pos: 2 + idx, + name: Some("query...".into()), + cause: Arc::new(mlua::Error::external(WorldError::LuaInvalidUsage( + format!("the 'Table' with name {} is unknown to the engine!", name), + ))), + } + })?; + + let dyn_type = QueryDynamicType::from_info(info.clone()); + view.push(dyn_type); + } + ViewQueryItem::UserData(ud) => { + let reflect = ud + .call_function::(FN_NAME_INTERNAL_REFLECT_TYPE, ()) + .expect("Type does not implement 'reflect_type' properly"); + let refl_comp = reflect.reflect_branch.as_component() + .expect("`self` is not an instance of `ReflectBranch::Component`"); + + let dyn_type = QueryDynamicType::from_info(refl_comp.info); + view.push(dyn_type); + } + // functions are queries, the if statement at the start would cause this to + // be unreachable. + ViewQueryItem::Function(_) => unreachable!() + } + } + + drop(w); + + let view_iter = view.into_iter(); + let reflected_iter = ReflectedIteratorOwned { + world_ptr: world.clone(), + dyn_view: view_iter, + }; + + Ok(Self { + world, + reflected_iter: Arc::new(AtomicRefCell::new(reflected_iter)), + queries, + }) + } + + /// Get the next row of components + fn next_components( + &mut self, + lua: &mlua::Lua, + ) -> Result, mlua::Error> { + let mut query_iter = self.reflected_iter.borrow_mut(); + if let Some(row) = query_iter.next_lua(lua) { + let (values, _): (Vec<_>, Vec<_>) = row + .row + .into_iter() + .into_iter() + .map(|r| (r.comp_val, r.comp_ptr.cast::<()>())) + .unzip(); + let mult_val = mlua::MultiValue::from_iter(values.into_iter()); + Ok(Some((row.entity, mult_val))) + } else { + Ok(None) + } + } + + /// Get the query results and the indexes that they were provided in. + /// + /// The indexes are used to make sure that the results are in the same order that the script + /// requested them in. + fn get_query_results(&self, entity: Entity) -> mlua::Result { + let mut query_vals = vec![]; + + // A modifier is used that will be incremented every time a filter allowed the query. + // this is used to remove the value of a filter without leaving a gap in the results. + let mut index_mod = 0; + for (query, i) in &self.queries { + let qres = query.get_query_result(self.world.clone(), entity)?; + + match qres { + LuaQueryResult::None => return Ok(ViewQueryResult::None), + LuaQueryResult::AlwaysNone => return Ok(ViewQueryResult::AlwaysNone), + LuaQueryResult::FilterPass => { + // do not push a boolean to values, its considered a filter + index_mod += 1; + }, + LuaQueryResult::FilterDeny => return Ok(ViewQueryResult::FilterDeny), + LuaQueryResult::Some(value) => { + let idx = (*i - index_mod).max(0); + query_vals.push((value, idx)); + }, + } + } + + Ok(ViewQueryResult::Some(query_vals)) + } +} + +impl mlua::FromLua for ViewResult { + fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result { + let tyname = value.type_name(); + value + .as_userdata() + .ok_or(mlua::Error::FromLuaConversionError { + from: tyname, + to: "View".into(), + message: None, + }) + .and_then(|ud| ud.borrow::()) + .map(|ud| ud.clone()) + } +} + +impl mlua::UserData for ViewResult { + fn add_methods>(methods: &mut M) { + methods.add_method_mut("next", |lua, this, ()| { + match this.next_components(lua)? { + Some((en, mut vals)) => { + loop { + let query_vals = match this.get_query_results(en)? { + ViewQueryResult::Some(v) => v, + ViewQueryResult::AlwaysNone => { + return mlua::Value::Nil.into_lua_multi(lua); + }, + ViewQueryResult::None | ViewQueryResult::FilterDeny => { + // try to get it next loop + continue; + }, + }; + + // insert query values to the result row + for (qval, qi) in query_vals { + vals.insert(qi as _, qval); + } + + vals.push_front(LuaEntityRef::new(this.world.clone(), en).into_lua(lua)?); + return Ok(vals); + } + } + None => mlua::Value::Nil.into_lua_multi(lua), + } + }); + + methods.add_method("iter", |lua, this, ()| { + let key_arc = Arc::new(atomic_refcell::AtomicRefCell::new(Some( + lua.create_registry_value(this.clone())?, + ))); + + lua.create_function(move |lua, ()| { + let mut key_mut = key_arc.borrow_mut(); + + if let Some(key) = key_mut.as_ref() { + let mut this = lua.registry_value::>(&key)?; + + loop { + match this.next_components(lua)? { + Some((en, mut vals)) => { + let lua_en = + LuaEntityRef::new(this.world.clone(), en).into_lua(lua)?; + + let query_vals = match this.get_query_results(en)? { + ViewQueryResult::Some(v) => v, + ViewQueryResult::AlwaysNone => { + return mlua::Value::Nil.into_lua_multi(lua); + }, + ViewQueryResult::None | ViewQueryResult::FilterDeny => { + // try to get it next loop + continue; + }, + }; + + // insert query values to the result row + for (qval, qi) in query_vals { + vals.insert(qi as _, qval); + } + + vals.push_front(lua_en); + return Ok(vals); + } + None => { + // If this is the last row, remove the registry value + // This doesn't protect against iterators that aren't fully consumed, + // that would cause a leak in the lua registry. + // TODO: fix leak + let key = key_mut.take().unwrap(); + lua.remove_registry_value(key)?; + + return mlua::Value::Nil.into_lua_multi(lua); + } + } + } + } else { + mlua::Value::Nil.into_lua_multi(lua) + } + }) + }); + } +} diff --git a/lyra-scripting/src/lua/ecs/view_one.rs b/lyra-scripting/src/lua/ecs/view_one.rs new file mode 100644 index 0000000..b22b259 --- /dev/null +++ b/lyra-scripting/src/lua/ecs/view_one.rs @@ -0,0 +1,175 @@ +use std::sync::Arc; + +use lyra_ecs::{query::dynamic::{DynamicViewOneOwned, QueryDynamicType}, Entity}; +use lyra_reflect::TypeRegistry; +use mlua::{IntoLua, IntoLuaMulti, ObjectLike}; + +use crate::{lua::{ReflectLuaProxy, TypeLookup, WorldError, FN_NAME_INTERNAL_REFLECT_TYPE}, ScriptBorrow, ScriptWorldPtr}; + +use super::{query::{LuaQuery, LuaQueryResult}, View, ViewQueryItem, ViewQueryResult}; + +/// The result of an ecs world View of a single entity. +#[derive(Clone)] +pub struct ViewOneResult { + world: ScriptWorldPtr, + dynamic_view: DynamicViewOneOwned, + /// The queries and the index they would be inserted in the result. + queries: Vec<(LuaQuery, u32)>, +} + +impl mlua::FromLua for ViewOneResult { + fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result { + let tyname = value.type_name(); + value + .as_userdata() + .ok_or(mlua::Error::FromLuaConversionError { + from: tyname, + to: "View".into(), + message: None, + }) + .and_then(|ud| ud.borrow::()) + .map(|ud| ud.clone()) + } +} + +impl ViewOneResult { + pub fn new(world: ScriptWorldPtr, entity: Entity, view: &View) -> Result { + let items = view.items.clone(); + let w = world.read(); + let mut view = DynamicViewOneOwned::new(entity); + let mut queries = vec![]; + + for (idx, comp) in items.iter().enumerate() { + if comp.is_query()? { + queries.push((comp.as_query(), idx as u32)); + continue; + } + + match comp { + ViewQueryItem::Table(t) => { + let name: String = t.get(mlua::MetaMethod::Type.name())?; + + let lookup = w.get_resource::().ok_or(mlua::Error::runtime( + "Unable to lookup table proxy, none were ever registered!", + ))?; + let info = lookup.comp_info_from_name.get(&name).ok_or_else(|| { + mlua::Error::BadArgument { + to: Some("ViewOneResult.new".into()), + pos: 2 + idx, + name: Some("query...".into()), + cause: Arc::new(mlua::Error::external(WorldError::LuaInvalidUsage( + format!("the 'Table' with name {} is unknown to the engine!", name), + ))), + } + })?; + + let dyn_type = QueryDynamicType::from_info(info.clone()); + view.queries.push(dyn_type); + } + ViewQueryItem::UserData(ud) => { + let reflect = ud + .call_function::(FN_NAME_INTERNAL_REFLECT_TYPE, ()) + .expect("Type does not implement 'reflect_type' properly"); + let refl_comp = reflect.reflect_branch.as_component() + .expect("`self` is not an instance of `ReflectBranch::Component`"); + + let dyn_type = QueryDynamicType::from_info(refl_comp.info); + view.queries.push(dyn_type); + } + // functions are queries, the if statement at the start would cause this to + // be unreachable. + ViewQueryItem::Function(_) => unreachable!() + } + } + + drop(w); + + Ok(Self { + world, + dynamic_view: view, + queries, + }) + } + + /// Get the query results and the indexes that they were provided in. + /// + /// The indexes are used to make sure that the results are in the same order that the script + /// requested them in. + fn get_query_results(&self, entity: Entity) -> mlua::Result { + let mut query_vals = vec![]; + + // A modifier is used that will be incremented every time a filter allowed the query. + // this is used to remove the value of a filter without leaving a gap in the results. + let mut index_mod = 0; + for (query, i) in &self.queries { + let qres = query.get_query_result(self.world.clone(), entity)?; + + match qres { + LuaQueryResult::None => return Ok(ViewQueryResult::None), + LuaQueryResult::AlwaysNone => return Ok(ViewQueryResult::AlwaysNone), + LuaQueryResult::FilterPass => { + // do not push a boolean to values, its considered a filter + index_mod += 1; + }, + LuaQueryResult::FilterDeny => return Ok(ViewQueryResult::FilterDeny), + LuaQueryResult::Some(value) => { + let idx = (*i - index_mod).max(0); + query_vals.push((value, idx)); + }, + } + } + + Ok(ViewQueryResult::Some(query_vals)) + } + + fn get_res_impl(&self, lua: &mlua::Lua) -> mlua::Result { + let world = self.world.read(); + + let qresults = self.get_query_results(self.dynamic_view.entity)?; + let qvals = match qresults { + ViewQueryResult::None => return mlua::Value::Nil.into_lua_multi(lua), + ViewQueryResult::AlwaysNone => return mlua::Value::Nil.into_lua_multi(lua), + ViewQueryResult::FilterDeny => return mlua::Value::Nil.into_lua_multi(lua), + ViewQueryResult::Some(vec) => vec, + }; + + let dv = self.dynamic_view.clone(); + if let Some(row) = dv.get(&world) { + let reg = world.get_resource::().unwrap(); + let mut vals = vec![]; + for d in row.iter() { + let id = d.info.type_id().as_rust(); + + let reg_type = reg.get_type(id) + .expect("Requested type was not found in TypeRegistry"); + let proxy = reg_type.get_data::() + // TODO: properly handle this error + .expect("Type does not have ReflectLuaProxy as a TypeData"); + let value = proxy.as_lua(lua, d.ptr.cast()).unwrap() + .into_lua(lua).unwrap(); + + vals.push(value); + } + + // insert query values to the result row + for (v, i) in qvals { + vals.insert(i as _, v); + } + + vals.into_lua_multi(lua) + } else { + mlua::Value::Nil.into_lua_multi(lua) + } + } +} + +impl mlua::UserData for ViewOneResult { + fn add_methods>(methods: &mut M) { + methods.add_method_mut("get", |lua, this, ()| { + this.get_res_impl(lua) + }); + methods.add_meta_method(mlua::MetaMethod::Call, |lua, this, ()| { + this.get_res_impl(lua) + }); + } +} diff --git a/lyra-scripting/src/lua/entity_ref.rs b/lyra-scripting/src/lua/entity_ref.rs new file mode 100644 index 0000000..2444413 --- /dev/null +++ b/lyra-scripting/src/lua/entity_ref.rs @@ -0,0 +1,159 @@ +use std::{any::TypeId, sync::Arc}; + +use lyra_ecs::{Entity, World}; +use lyra_reflect::TypeRegistry; +use mlua::{IntoLua, ObjectLike}; + +use crate::{ScriptBorrow, ScriptWorldPtr}; + +use super::{reflect_type_user_data, Error, ReflectLuaProxy, TypeLookup, FN_NAME_INTERNAL_REFLECT_TYPE}; + +#[derive(Clone)] +pub enum LuaComponent { + UserData(mlua::AnyUserData), + Table(mlua::Table), +} + +impl mlua::FromLua for LuaComponent { + fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result { + let tyname = value.type_name(); + match value { + mlua::Value::UserData(ud) => Ok(Self::UserData(ud)), + mlua::Value::Table(t) => Ok(Self::Table(t)), + _ => Err(mlua::Error::FromLuaConversionError { + from: tyname, + to: "LuaComponent".into(), + message: Some( + "expected Table or UserData that can be converted to a native struct".into(), + ), + }), + } + } +} + +impl mlua::IntoLua for LuaComponent { + fn into_lua(self, lua: &mlua::Lua) -> mlua::Result { + match self { + Self::Table(t) => t.into_lua(lua), + Self::UserData(ud) => ud.into_lua(lua), + } + } +} + +impl LuaComponent { + pub fn get_typeid(&self, world: &World) -> Option { + match self { + Self::Table(t) => { + let name: String = t.get(mlua::MetaMethod::Type.name()).ok()?; + let lookup = world.get_resource::().unwrap(); + lookup.typeid_from_name.get(&name).cloned() + } + Self::UserData(ud) => { + let lua_comp = reflect_type_user_data(ud); + let refl_comp = lua_comp.reflect_branch.as_component_unchecked(); + Some(refl_comp.info.type_id().as_rust()) + } + } + } + + /// Call the internal reflect type function and return the result. + /// + /// This calls the [`FN_NAME_INTERNAL_REFLECT_TYPE`] function on the Component. + pub fn reflect_type(&self) -> Result { + self.call_function(FN_NAME_INTERNAL_REFLECT_TYPE, ()) + .map_err(|_| Error::Reflect) + } + + /// Call a Lua function on the Component. + /// + /// This is a helper function so you don't have to match on the component. + pub fn call_function(&self, name: &str, args: impl mlua::IntoLuaMulti) -> mlua::Result { + match self { + LuaComponent::UserData(ud) => ud.call_function(name, args), + LuaComponent::Table(t) => t.call_function(name, args), + } + } + + /// Call a Lua method on the Component. + /// + /// This is a helper function so you don't have to match on the component. + pub fn call_method(&self, name: &str, args: impl mlua::IntoLuaMulti) -> mlua::Result { + match self { + LuaComponent::UserData(ud) => ud.call_method(name, args), + LuaComponent::Table(t) => t.call_method(name, args), + } + } + + /// Returns `true` if the Component has a function of `name`. + pub fn has_function(&self, name: &str) -> mlua::Result { + match self { + LuaComponent::UserData(ud) => { + ud.get::(name).map(|v| !v.is_nil()) + }, + LuaComponent::Table(t) => { + t.contains_key(name) + }, + } + } +} + +/// Reference to an Entity for Lua +/// +/// This can be used to make it easier to update things on the Entity. +pub struct LuaEntityRef { + en: Entity, + world: ScriptWorldPtr, +} + +impl LuaEntityRef { + pub fn new(world: ScriptWorldPtr, en: Entity) -> Self { + Self { + en, + world, + } + } +} + +impl mlua::UserData for LuaEntityRef { + fn add_methods>(methods: &mut M) { + methods.add_method( + "update", + |lua, this, comps: mlua::Variadic| { + let mut world = this.world.write(); + let world_tick = world.current_tick(); + + for (i, comp) in comps.iter().enumerate() { + let tid = comp.get_typeid(&world).ok_or(mlua::Error::BadArgument { + to: Some("Entity:update".into()), + pos: 2 + i, + name: Some("comps...".into()), + cause: Arc::new(mlua::Error::runtime( + "failed to get native TypeId from component", + )), + })?; + // convert component to mlua::Value + let comp = comp.clone().into_lua(lua)?; + + // get the pointer of the component in the archetype column. + let arch = world.entity_archetype_mut(this.en).unwrap(); + let arch_idx = *arch.entity_indexes().get(&this.en).unwrap(); + let col = arch.get_column_mut(tid).unwrap(); + let col_ptr = col.component_ptr(*arch_idx as usize, &world_tick).cast(); + + // get the type registry to apply the new value + let reg = world.get_resource::().unwrap(); + let reg_type = reg.get_type(tid).unwrap(); + + let proxy = reg_type + .get_data::() + // 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.apply(lua, col_ptr, &comp)?; + } + + Ok(()) + }, + ); + } +} diff --git a/lyra-scripting/src/lua/mod.rs b/lyra-scripting/src/lua/mod.rs index a04e86f..bf41c12 100644 --- a/lyra-scripting/src/lua/mod.rs +++ b/lyra-scripting/src/lua/mod.rs @@ -18,6 +18,11 @@ pub mod wrappers; pub mod proxy; pub use proxy::*; +pub mod ecs; + +mod entity_ref; +pub use entity_ref::*; + pub mod system; pub use system::*; use wrappers::{LuaHandleWrapper, LuaResHandleToComponent, LuaWrappedEventProxy}; @@ -42,7 +47,9 @@ pub enum Error { #[error("{0}")] Mlua(#[from] mlua::Error), #[error("unimplemented: {0}")] - Unimplemented(String) + Unimplemented(String), + #[error("Error calling internal reflection type")] + Reflect, } /* impl Into for Error { @@ -86,6 +93,21 @@ pub const FN_NAME_INTERNAL_REFLECT_TYPE: &str = "__lyra_internal_reflect_type"; /// method to return data**. pub const FN_NAME_INTERNAL_REFLECT: &str = "__lyra_internal_reflect"; +/// Name of a Lua function to retrieve the query result from a Userdata, or Table. +/// +/// The function must match the following definition: `fn(ScriptWorldPtr, Entity) -> LuaValue`. +/// +/// When `nil` is returned, its considered that the query will not result in anything for this +/// [`View`], **no matter the entity**. When the query is used in a [`View`] and returns `nil`, +/// it will NOT check for other entities. This is used in the [`ResQuery`] Lua query. If the +/// resource is missing, it will always be missing for the [`View`], no matter the entity. +/// +/// If it returns a boolean, the query will act as a filter. The boolean value will not be in the +/// result. When the boolean is `false`, other entities will be checked by the [`View`]. +/// +/// Any other value will be included in the result. +pub const FN_NAME_INTERNAL_ECS_QUERY_RESULT: &str = "__lyra_internal_ecs_query_result"; + /// 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, @@ -271,6 +293,16 @@ impl mlua::UserData for ScriptBorrow { /// Helper function used for reflecting userdata as a ScriptBorrow pub fn reflect_user_data(ud: &mlua::AnyUserData) -> ScriptBorrow { + let ud_name = ud.metatable().and_then(|mt| mt.get::(mlua::MetaMethod::Type)) + .unwrap_or("Unknown".to_string()); ud.call_method::(FN_NAME_INTERNAL_REFLECT, ()) - .expect("Type does not implement internal reflect method properly") + .unwrap_or_else(|_| panic!("UserData of name '{}' does not implement internal reflect method properly", ud_name)) +} + +/// Helper function used for reflecting userdata type as a ScriptBorrow +pub fn reflect_type_user_data(ud: &mlua::AnyUserData) -> ScriptBorrow { + let ud_name = ud.metatable().and_then(|mt| mt.get::(mlua::MetaMethod::Type)) + .unwrap_or("Unknown".to_string()); + ud.call_function::(FN_NAME_INTERNAL_REFLECT_TYPE, ()) + .unwrap_or_else(|_| panic!("UserData of name '{}' does not implement internal reflect type function properly", ud_name)) } \ No newline at end of file diff --git a/lyra-scripting/src/lua/providers/ecs.rs b/lyra-scripting/src/lua/providers/ecs.rs index 1ec3d13..9c47e22 100644 --- a/lyra-scripting/src/lua/providers/ecs.rs +++ b/lyra-scripting/src/lua/providers/ecs.rs @@ -1,7 +1,7 @@ use lyra_ecs::ResourceObject; use lyra_reflect::Reflect; -use crate::{lua::{wrappers::*, LuaContext, LuaWrapper, RegisterLuaType, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE}, ScriptApiProvider, ScriptBorrow, ScriptData, ScriptDynamicBundle, ScriptWorldPtr}; +use crate::{lua::{ecs::{query::{LuaChangedQuery, LuaHasQuery, LuaNotQuery, LuaOptionalQuery, LuaOrQuery, LuaResQuery, LuaTickOfQuery}, View}, wrappers::*, LuaContext, LuaWrapper, RegisterLuaType, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE}, ScriptApiProvider, ScriptBorrow, ScriptData, ScriptDynamicBundle, ScriptWorldPtr}; //fn register_lua_proxy::()?)?; @@ -42,14 +44,22 @@ impl ScriptApiProvider for LyraEcsApiProvider { globals.set("SceneHandler", ctx.create_proxy::()?)?; globals.set("ActionHandler", ctx.create_proxy::()?)?; globals.set("Window", ctx.create_proxy::()?)?; + globals.set("View", ctx.create_proxy::()?)?; + + globals.set("ResQuery", ctx.create_proxy::()?)?; + globals.set("ChangedQuery", ctx.create_proxy::()?)?; + globals.set("HasQuery", ctx.create_proxy::()?)?; + globals.set("NotQuery", ctx.create_proxy::()?)?; + globals.set("AnyQuery", ctx.create_proxy::()?)?; + globals.set("TickOfQuery", ctx.create_proxy::()?)?; + globals.set("OptionalQuery", ctx.create_proxy::()?)?; expose_comp_table_wrapper::(&ctx, &globals, "Camera")?; expose_comp_table_wrapper::(&ctx, &globals, "FreeFlyCamera")?; expose_comp_table_wrapper::(&ctx, &globals, "WorldTransform")?; expose_table_wrapper::(&ctx, &globals, "DeviceEvent")?; - let dt_table = create_reflect_table::(&ctx)?; - globals.set("DeltaTime", dt_table)?; + expose_resource_table_wrapper::(&ctx, &globals, "DeltaTime")?; Ok(()) } @@ -63,13 +73,14 @@ impl ScriptApiProvider for LyraEcsApiProvider { } } -fn create_reflect_table(lua: &mlua::Lua) -> mlua::Result { +fn expose_resource_table_wrapper(lua: &mlua::Lua, globals: &mlua::Table, name: &str) -> mlua::Result<()> { let table = lua.create_table()?; table.set(FN_NAME_INTERNAL_REFLECT_TYPE, lua.create_function(|_, ()| { Ok(ScriptBorrow::from_resource::(None)) })?)?; - Ok(table) + globals.set(name, table)?; + Ok(()) } fn create_reflect_comp_table(lua: &mlua::Lua, name: &str) -> mlua::Result diff --git a/lyra-scripting/src/lua/proxy.rs b/lyra-scripting/src/lua/proxy.rs index d330a43..758a080 100644 --- a/lyra-scripting/src/lua/proxy.rs +++ b/lyra-scripting/src/lua/proxy.rs @@ -1,6 +1,6 @@ use std::{any::TypeId, collections::HashMap, ptr::NonNull}; -use mlua::{ObjectLike, IntoLua}; +use mlua::ObjectLike; use lyra_ecs::{ComponentInfo, DynamicBundle}; use lyra_reflect::Reflect; @@ -78,9 +78,9 @@ pub struct TypeLookup { /// A struct used for Proxying types to and from Lua. #[derive(Clone)] pub struct ReflectLuaProxy { - pub fn_as_lua: + fn_as_lua: for<'a> fn(lua: &'a mlua::Lua, this_ptr: NonNull<()>) -> mlua::Result, - pub fn_apply: for<'a> fn( + fn_apply: for<'a> fn( lua: &'a mlua::Lua, this_ptr: NonNull<()>, value: &'a mlua::Value, @@ -125,6 +125,16 @@ impl ReflectLuaProxy { }, } } + + /// Reflect the pointer to get a Lua value. + pub fn as_lua(&self, lua: &mlua::Lua, this_ptr: NonNull<()>) -> mlua::Result { + (self.fn_as_lua)(lua, this_ptr) + } + + /// Set the contents in the pointer to a Lua value. + pub fn apply(&self, lua: &mlua::Lua, this_ptr: NonNull<()>, value: &mlua::Value) -> mlua::Result<()> { + (self.fn_apply)(lua, this_ptr, value) + } } impl mlua::FromLua for ScriptDynamicBundle { diff --git a/lyra-scripting/src/lua/world.rs b/lyra-scripting/src/lua/world.rs index ab414b7..f926d17 100644 --- a/lyra-scripting/src/lua/world.rs +++ b/lyra-scripting/src/lua/world.rs @@ -1,19 +1,16 @@ -use std::{ops::DerefMut, ptr::NonNull, sync::Arc}; +use std::{ops::DerefMut, sync::Arc}; use crate::{ScriptBorrow, ScriptEntity, ScriptWorldPtr}; -use lyra_ecs::{ - query::dynamic::{DynamicViewState, DynamicViewStateIter, QueryDynamicType}, - CommandQueue, Commands, DynamicBundle, World, -}; +use lyra_ecs::{CommandQueue, Commands, DynamicBundle, World}; use lyra_reflect::{ReflectWorldExt, RegisteredType, TypeRegistry}; use lyra_resource::ResourceManager; use mlua::{IntoLua, ObjectLike}; use super::{ - reflect_user_data, - wrappers::{LuaResHandleToComponent, LuaWrappedEventProxy}, - Error, ReflectLuaProxy, ReflectedIterator, TypeLookup, FN_NAME_INTERNAL_AS_COMPONENT, - FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE, + ecs::{View, ViewOneResult, ViewResult}, + wrappers::{LuaResHandleToComponent, LuaTick, LuaWrappedEventProxy}, + Error, ReflectLuaProxy, TypeLookup, FN_NAME_INTERNAL_AS_COMPONENT, FN_NAME_INTERNAL_REFLECT, + FN_NAME_INTERNAL_REFLECT_TYPE, }; impl mlua::FromLua for ScriptEntity { @@ -101,146 +98,6 @@ impl mlua::UserData for ScriptWorldPtr { Ok(ScriptEntity(entity)) }); - methods.add_method_mut( - "view", - |lua, this, (system, queries): (mlua::Function, mlua::MultiValue)| { - if queries.is_empty() { - return Err(mlua::Error::BadArgument { - to: Some("World:view".into()), - pos: 2, - name: Some("query...".into()), - cause: Arc::new(mlua::Error::external(WorldError::LuaInvalidUsage( - "no component types provided".into(), - ))), - }); - } - - let world = this.read(); - let mut view = DynamicViewState::new(); - - for (idx, comp) in queries.into_iter().enumerate() { - match comp { - mlua::Value::Table(t) => { - let name: String = t.get(mlua::MetaMethod::Type.name())?; - - let lookup = - world - .get_resource::() - .ok_or(mlua::Error::runtime( - "Unable to lookup table proxy, none were ever registered!", - ))?; - let info = lookup.comp_info_from_name.get(&name).ok_or_else(|| { - mlua::Error::BadArgument { - to: Some("World:view".into()), - pos: 2 + idx, - name: Some("query...".into()), - cause: Arc::new(mlua::Error::external( - WorldError::LuaInvalidUsage(format!( - "the 'Table' with name {} is unknown to the engine!", - name - )), - )), - } - })?; - - let dyn_type = QueryDynamicType::from_info(info.clone()); - view.push(dyn_type); - } - mlua::Value::UserData(ud) => { - let reflect = ud - .call_function::(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!(), - } - } - - let iter = view.into_iter(); - let mut reflected_iter = ReflectedIterator { - // SAFETY: bypassing the borrow checker here to get a pointer of the world - // is required since we mutably borrow below. Its safe to do so since - // only the entity ticks are updated. They are accessing different things - // from the world. - world: unsafe { NonNull::from(&*world).as_ref() }, - dyn_view: DynamicViewStateIter::from(iter), - reflected_components: None, - }; - - let current = world.current_tick(); - - // drop read lock and acquire the write lock. - // dropping must be done to avoid mutex deadlock - drop(world); - let mut world = this.write(); - - while let Some(row) = reflected_iter.next_lua(lua) { - let r = row - .row - .into_iter() - .into_iter() - .map(|r| (r.comp_val, r.comp_ptr.cast::<()>())) - .collect::>(); - let (values, ptrs) = - itertools::multiunzip::<(Vec, Vec>), _>(r); - let mult_val = mlua::MultiValue::from_iter(values.into_iter()); - 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() <= ptrs.len() { - for (comp, ptr) in res.into_iter().zip(ptrs) { - let lua_typeid = match &comp { - mlua::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() - } - mlua::Value::Table(tbl) => { - let name: String = tbl.get(mlua::MetaMethod::Type.name())?; - - let lookup = world.get_resource::().unwrap(); - *lookup.typeid_from_name.get(&name).unwrap() - } - _ => { - panic!("A userdata or table value was not returned!"); - // TODO: Handle properly - } - }; - - // update the component tick - let arch = world.entity_archetype_mut(row.entity).unwrap(); - let idx = arch.entity_indexes().get(&row.entity).unwrap().clone(); - let c = arch.get_column_mut(lua_typeid).unwrap(); - c.entity_ticks[idx.0 as usize] = current; - - // apply the new component data - let reg = world.get_resource::().unwrap(); - let reg_type = reg.get_type(lua_typeid).unwrap(); - - let proxy = reg_type - .get_data::() - // 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(mlua::Error::runtime(msg)); - } - } - - Ok(()) - }, - ); methods.add_method_mut("resource", |lua, this, (ty,): (mlua::Value,)| { let reflect = match ty { mlua::Value::UserData(ud) => ud @@ -266,7 +123,9 @@ impl mlua::UserData for ScriptWorldPtr { .get_data::() .expect("Type does not have ReflectLuaProxy as a TypeData"); - (proxy.fn_as_lua)(lua, res_ptr.cast()).and_then(|ud| ud.into_lua(lua)) + proxy + .as_lua(lua, res_ptr.cast()) + .and_then(|ud| ud.into_lua(lua)) } else { // if the resource is not found in the world, return nil Ok(mlua::Value::Nil) @@ -353,5 +212,18 @@ impl mlua::UserData for ScriptWorldPtr { data.reader(&mut world).into_lua(lua) }, ); + methods.add_method("view", |_, this, view: mlua::UserDataRef| { + ViewResult::new(this.clone(), &view) + }); + methods.add_method("get_tick", |_, this, ()| { + let w = this.read(); + Ok(LuaTick(w.current_tick())) + }); + methods.add_method( + "view_one", + |_, this, (entity, view): (ScriptEntity, mlua::UserDataRef)| { + ViewOneResult::new(this.clone(), *entity, &view) + }, + ); } } diff --git a/lyra-scripting/src/lua/wrappers/mod.rs b/lyra-scripting/src/lua/wrappers/mod.rs index f6e32ad..bc7f297 100644 --- a/lyra-scripting/src/lua/wrappers/mod.rs +++ b/lyra-scripting/src/lua/wrappers/mod.rs @@ -23,4 +23,7 @@ mod events; pub use events::*; mod world_transform; -pub use world_transform::*; \ No newline at end of file +pub use world_transform::*; + +mod tick; +pub use tick::*; \ No newline at end of file diff --git a/lyra-scripting/src/lua/wrappers/tick.rs b/lyra-scripting/src/lua/wrappers/tick.rs new file mode 100644 index 0000000..7b4530f --- /dev/null +++ b/lyra-scripting/src/lua/wrappers/tick.rs @@ -0,0 +1,55 @@ + +use std::any::TypeId; + +use lyra_ecs::Tick; +use crate::lua::LuaWrapper; + +#[derive(Clone, Default)] +pub struct LuaTick(pub(crate) Tick); + +impl std::ops::Deref for LuaTick { + type Target = Tick; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::DerefMut for LuaTick { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl mlua::FromLua for LuaTick { + fn from_lua(v: mlua::Value, _: &mlua::Lua) -> mlua::Result { + let tyname = v.type_name(); + let num = v.as_number() + .ok_or(mlua::Error::FromLuaConversionError { from: tyname, to: "Tick".into(), message: None })?; + Ok(Self(Tick::from(num as u64))) + } +} + +impl mlua::IntoLua for LuaTick { + fn into_lua(self, _: &mlua::Lua) -> mlua::Result { + Ok(mlua::Value::Number(*self.0 as f64)) + } +} + +impl LuaWrapper for LuaTick { + type Wrap = Tick; + + #[inline(always)] + fn wrapped_type_id() -> std::any::TypeId { + TypeId::of::() + } + + #[inline(always)] + fn into_wrapped(self) -> Self::Wrap { + self.0 + } + + fn from_wrapped(wrap: Self::Wrap) -> Option { + Some(Self(wrap)) + } +}