Improve Lua ECS #30

Merged
SeanOMik merged 15 commits from feat/improve-lua-ecs-29 into main 2024-10-30 03:22:50 +00:00
19 changed files with 381 additions and 156 deletions
Showing only changes of commit 2e33de5da2 - Show all commits

View File

@ -90,15 +90,19 @@ function on_update()
end end
end, Transform) ]] end, Transform) ]]
-- although WorldTransform isn't used, I only want to modify entities with that component.
local view = View.new(Transform, WorldTransform, Res(DeltaTime)) local view = View.new(Transform, WorldTransform, Res(DeltaTime))
local res = world:view_query(view) local res = world:view_query(view)
for entity, transform, _wt, dt in res:iter() do
for entity, transform, world_tran, dt in res:iter() do
print("Entity is at: " .. tostring(world_tran))
transform:translate(0, 0.15 * dt, 0) transform:translate(0, 0.15 * dt, 0)
entity:update(transform) entity:update(transform)
end end
local changed_view = View.new(Changed(Transform))
local changed_res = world:view_query(changed_view)
for _, transform in changed_res:iter() do
print("Entity transform changed to: " .. tostring(transform))
end
end end
--[[ function on_post_update() --[[ function on_post_update()

View File

@ -214,16 +214,34 @@ impl ComponentColumn {
moved_index moved_index
} }
/// Get the pointer of the component for the entity. /// 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 /// It is assumed that the component will be mutated, meaning the component's tick will be
/// updated. /// updated.
pub fn get_entity_ptr(&mut self, entity_index: usize, tick: &Tick) -> NonNull<u8> { pub fn component_ptr(&mut self, entity_index: usize, tick: &Tick) -> NonNull<u8> {
self.entity_ticks.insert(entity_index, *tick); self.entity_ticks[entity_index] = *tick;
let size = self.info.layout().size(); let size = self.info.layout().size();
unsafe { NonNull::new_unchecked(self.borrow_ptr().as_ptr().add(entity_index * 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<u8> {
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<Tick> {
self.entity_ticks.get(entity_index).cloned()
}
pub fn component_has_changed(&self, entity_index: usize, world_tick: Tick) -> Option<bool> {
self.component_tick(entity_index)
.map(|tick| *tick >= *world_tick - 1)
}
pub fn borrow_ptr(&self) -> Ref<NonNull<u8>> { pub fn borrow_ptr(&self) -> Ref<NonNull<u8>> {
self.data.borrow() self.data.borrow()
} }
@ -272,7 +290,7 @@ pub struct Archetype {
/// Can be used to map `ArchetypeEntityId` to an Entity since `ArchetypeEntityId` has /// Can be used to map `ArchetypeEntityId` to an Entity since `ArchetypeEntityId` has
/// the index that the entity is stored at. /// the index that the entity is stored at.
pub(crate) entities: Vec<Entity>, pub(crate) entities: Vec<Entity>,
pub(crate) columns: Vec<ComponentColumn>, pub columns: Vec<ComponentColumn>,
capacity: usize, capacity: usize,
} }

View File

@ -77,4 +77,4 @@ impl Entities {
pub(crate) fn insert_entity_record(&mut self, entity: Entity, record: Record) { pub(crate) fn insert_entity_record(&mut self, entity: Entity, record: Record) {
self.arch_index.insert(entity.id, record); self.arch_index.insert(entity.id, record);
} }
} }

View File

@ -24,6 +24,10 @@ impl DynamicViewState {
} }
} }
pub fn queries_num(&self) -> usize {
self.queries.len()
}
pub fn push(&mut self, dyn_query: QueryDynamicType) { pub fn push(&mut self, dyn_query: QueryDynamicType) {
self.queries.push(dyn_query); self.queries.push(dyn_query);
} }

View File

@ -112,7 +112,7 @@ impl<'a, T: ResourceObject> Res<'a, T> {
/// Returns a boolean indicating if the resource changed. /// Returns a boolean indicating if the resource changed.
pub fn changed(&self) -> bool { 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 /// The tick that this resource was last modified at

View File

@ -26,11 +26,11 @@ pub struct Record {
#[derive(Clone)] #[derive(Clone)]
pub struct World { pub struct World {
pub(crate) archetypes: HashMap<ArchetypeId, Archetype>, pub archetypes: HashMap<ArchetypeId, Archetype>,
next_archetype_id: ArchetypeId, next_archetype_id: ArchetypeId,
resources: HashMap<TypeId, ResourceData>, resources: HashMap<TypeId, ResourceData>,
tracker: TickTracker, tracker: TickTracker,
pub(crate) entities: Entities, pub entities: Entities,
} }
impl Default for World { impl Default for World {
@ -467,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<T: ResourceObject>(&self) -> Option<Tick> {
self.get_tracked_resource::<T>().map(|r| r.tick)
}
/// Gets a reference to a change tracked resource. /// Gets a reference to a change tracked resource.
/// ///
/// You will have to manually downcast the inner resource. Most people don't need this, see /// You will have to manually downcast the inner resource. Most people don't need this, see

View File

@ -1,6 +1,6 @@
use std::{any::{Any, TypeId}, cell::{Ref, RefMut}}; 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}; use crate::{Reflect, FromType};
@ -18,6 +18,8 @@ pub struct ReflectedComponent {
fn_bundle_insert: for<'a> fn (dynamic_bundle: &'a mut DynamicBundle, component: Box<dyn Reflect>), fn_bundle_insert: for<'a> fn (dynamic_bundle: &'a mut DynamicBundle, component: Box<dyn Reflect>),
fn_reflect: for<'a> fn (world: &'a World, entity: Entity) -> Option<Ref<'a, dyn Reflect>>, fn_reflect: for<'a> fn (world: &'a World, entity: Entity) -> Option<Ref<'a, dyn Reflect>>,
fn_reflect_mut: for<'a> fn (world: &'a mut World, entity: Entity) -> Option<RefMut<'a, dyn Reflect>>, fn_reflect_mut: for<'a> fn (world: &'a mut World, entity: Entity) -> Option<RefMut<'a, dyn Reflect>>,
fn_reflect_tick: for<'a> fn (world: &'a World, entity: Entity) -> Option<Tick>,
fn_reflect_is_changed: for<'a> fn (world: &'a World, entity: Entity) -> Option<bool>,
} }
impl ReflectedComponent { impl ReflectedComponent {
@ -40,6 +42,14 @@ impl ReflectedComponent {
pub fn reflect_mut<'a>(&'a mut self, world: &'a mut World, entity: Entity) -> Option<RefMut<'a, dyn Reflect>> { pub fn reflect_mut<'a>(&'a mut self, world: &'a mut World, entity: Entity) -> Option<RefMut<'a, dyn Reflect>> {
(self.fn_reflect_mut)(world, entity) (self.fn_reflect_mut)(world, entity)
} }
pub fn reflect_tick<'a>(&'a self, world: &'a World, entity: Entity) -> Option<Tick> {
(self.fn_reflect_tick)(world, entity)
}
pub fn reflect_is_changed<'a>(&'a self, world: &'a World, entity: Entity) -> Option<bool> {
(self.fn_reflect_is_changed)(world, entity)
}
} }
impl<C: Component + Reflect> FromType<C> for ReflectedComponent { impl<C: Component + Reflect> FromType<C> for ReflectedComponent {
@ -69,6 +79,12 @@ impl<C: Component + Reflect> FromType<C> for ReflectedComponent {
world.view_one::<&mut C>(entity) world.view_one::<&mut C>(entity)
.get().map(|c| c as RefMut<dyn Reflect>) .get().map(|c| c as RefMut<dyn Reflect>)
}, },
fn_reflect_tick: |world: &World, entity: Entity| {
world.view_one::<TickOf<C>>(entity).get()
},
fn_reflect_is_changed: |world: &World, entity: Entity| {
world.view_one::<Changed<C>>(entity).get()
}
} }
} }
} }

View File

@ -1,6 +1,6 @@
use std::{any::{Any, TypeId}, ptr::NonNull}; 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}; use crate::{Reflect, FromType};
@ -9,6 +9,8 @@ pub struct ReflectedResource {
pub type_id: TypeId, pub type_id: TypeId,
fn_reflect: for<'a> fn (world: &'a World) -> Option<AtomicRef<'a, dyn Reflect>>, fn_reflect: for<'a> fn (world: &'a World) -> Option<AtomicRef<'a, dyn Reflect>>,
fn_reflect_tick: for<'a> fn (world: &'a World) -> Option<Tick>,
fn_reflect_is_changed: fn (world: &World) -> Option<bool>,
fn_reflect_mut: for<'a> fn (world: &'a mut World) -> Option<AtomicRefMut<'a, dyn Reflect>>, fn_reflect_mut: for<'a> fn (world: &'a mut World) -> Option<AtomicRefMut<'a, dyn Reflect>>,
fn_reflect_ptr: fn (world: &mut World) -> Option<NonNull<u8>>, fn_reflect_ptr: fn (world: &mut World) -> Option<NonNull<u8>>,
fn_refl_insert: fn (world: &mut World, this: Box<dyn Reflect>), fn_refl_insert: fn (world: &mut World, this: Box<dyn Reflect>),
@ -20,6 +22,14 @@ impl ReflectedResource {
(self.fn_reflect)(world) (self.fn_reflect)(world)
} }
pub fn reflect_tick(&self, world: &World) -> Option<Tick> {
(self.fn_reflect_tick)(world)
}
pub fn reflect_is_changed(&self, world: &World) -> Option<bool> {
(self.fn_reflect_is_changed)(world)
}
/// Retrieves a mutable reflected resource from the world. /// Retrieves a mutable reflected resource from the world.
pub fn reflect_mut<'a>(&self, world: &'a mut World) -> Option<AtomicRefMut<'a, dyn Reflect>> { pub fn reflect_mut<'a>(&self, world: &'a mut World) -> Option<AtomicRefMut<'a, dyn Reflect>> {
(self.fn_reflect_mut)(world) (self.fn_reflect_mut)(world)
@ -47,6 +57,13 @@ impl<T: ResourceObject + Reflect> FromType<T> for ReflectedResource {
AtomicRef::map(r, |r| r as &dyn Reflect) AtomicRef::map(r, |r| r as &dyn Reflect)
}) })
}, },
fn_reflect_tick: |world: &World| {
world.get_resource_tick::<T>()
},
fn_reflect_is_changed: |world: &World| {
world.get_resource::<T>()
.map(|r| r.changed())
},
fn_reflect_mut: |world: &mut World| { fn_reflect_mut: |world: &mut World| {
world.get_resource_mut::<T>() world.get_resource_mut::<T>()
.map(|r| { .map(|r| {

View File

@ -3,4 +3,11 @@
---@return ResQuery ---@return ResQuery
function Res(resource) function Res(resource)
return ResQuery.new(resource) return ResQuery.new(resource)
end
---Create a Changed Query of a resource or component.
---@param resource table|userdata
---@return ChangedQuery
function Changed(val)
return ChangedQuery.new(val)
end end

View File

@ -57,15 +57,13 @@ fn next_lua(lua: &mlua::Lua, world: &lyra_ecs::World, dyn_view: &mut DynamicView
let mut dynamic_row = vec![]; let mut dynamic_row = vec![];
for d in row.iter() { for d in row.iter() {
let id = d.info.type_id().as_rust(); let id = d.info.type_id().as_rust();
/* let reflected_components =
unsafe { reflected_components.as_ref().unwrap().as_ref() }; */
let reg_type = reflected_components.get_type(id) let reg_type = reflected_components.get_type(id)
.expect("Requested type was not found in TypeRegistry"); .expect("Requested type was not found in TypeRegistry");
let proxy = reg_type.get_data::<ReflectLuaProxy>() let proxy = reg_type.get_data::<ReflectLuaProxy>()
// TODO: properly handle this error // TODO: properly handle this error
.expect("Type does not have ReflectLuaProxy as a TypeData"); .expect("Type does not have ReflectLuaProxy as a TypeData");
let value = (proxy.fn_as_lua)(lua, d.ptr.cast()).unwrap() let value = proxy.as_lua(lua, d.ptr.cast()).unwrap()
.into_lua(lua).unwrap(); .into_lua(lua).unwrap();
dynamic_row.push(ReflectedItem { dynamic_row.push(ReflectedItem {

View File

@ -0,0 +1,98 @@
use lyra_reflect::{ReflectWorldExt, RegisteredType, TypeRegistry};
use mlua::IntoLua;
use crate::{
lua::{LuaComponent, ReflectLuaProxy, FN_NAME_INTERNAL_ECS_QUERY_RESULT},
ReflectBranch, ScriptEntity, ScriptWorldPtr,
};
#[derive(Clone)]
pub struct LuaChangedQuery(LuaComponent);
impl mlua::FromLua for LuaChangedQuery {
fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
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::<Self>())
.map(|ud| ud.clone())
}
}
impl mlua::UserData for LuaChangedQuery {
fn add_methods<M: mlua::UserDataMethods<Self>>(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(mlua::Value::Boolean(false));
}
// get the pointer of the component in the archetype column.
let arch = world.entity_archetype(*en).unwrap();
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(mlua::Value::Boolean(false));
}
};
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::<TypeRegistry>().unwrap();
let reg_type = reg.get_type(tyid).unwrap();
let proxy = reg_type
.get_data::<ReflectLuaProxy>()
// this should actually be safe since the ReflectedIterator
// attempts to get the type data before it is tried here
.expect("Type does not have ReflectLuaProxy as a TypeData");
proxy.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(mlua::Value::Boolean(false));
}
None => {
// the resource was not found
return Ok(mlua::Value::Nil);
}
_ => {}
}
// 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::<RegisteredType>(tyid)
.expect("Resource is not type registered!");
let proxy = reg_type
.get_data::<ReflectLuaProxy>()
.expect("Type does not have ReflectLuaProxy as a TypeData");
proxy.as_lua(lua, res_ptr.cast()).and_then(|ud| ud.into_lua(lua))
}
}
},
);
}
}

View File

@ -1,25 +1,40 @@
mod res; mod res;
pub use res::*; pub use res::*;
use crate::{lua::{LuaComponent, FN_NAME_INTERNAL_ECS_QUERY_RESULT}, ScriptWorldPtr}; mod changed;
pub use changed::*;
use lyra_ecs::Entity;
use crate::{lua::{LuaComponent, FN_NAME_INTERNAL_ECS_QUERY_RESULT}, ScriptEntity, ScriptWorldPtr};
#[derive(Clone)] #[derive(Clone)]
pub struct LuaQuery { enum QueryInner {
query: LuaComponent Component(LuaComponent),
Function(mlua::Function)
} }
#[derive(Clone)]
pub struct LuaQuery(QueryInner);
impl LuaQuery { impl LuaQuery {
pub fn new(query: LuaComponent) -> Self { pub fn new(query: LuaComponent) -> Self {
Self { Self(QueryInner::Component(query))
query }
}
pub fn from_function(f: mlua::Function) -> Self {
Self(QueryInner::Function(f))
} }
/// Get the result of the query /// Get the result of the query
/// ///
/// > WARNING: ensure that the world pointer is not locked. If its locked when you call this, /// > WARNING: ensure that the world pointer is not locked. If its locked when you call this,
/// you WILL cause a deadlock. /// you WILL cause a deadlock.
pub fn get_query_result(&self, world: ScriptWorldPtr) -> mlua::Result<mlua::Value> { pub fn get_query_result(&self, world: ScriptWorldPtr, entity: Entity) -> mlua::Result<mlua::Value> {
self.query.call_method(FN_NAME_INTERNAL_ECS_QUERY_RESULT, world) 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)),
}
} }
} }

View File

@ -2,8 +2,7 @@ use lyra_reflect::{ReflectWorldExt, RegisteredType};
use mlua::IntoLua; use mlua::IntoLua;
use crate::{ use crate::{
lua::{LuaComponent, ReflectLuaProxy, FN_NAME_INTERNAL_ECS_QUERY_RESULT}, lua::{LuaComponent, ReflectLuaProxy, FN_NAME_INTERNAL_ECS_QUERY_RESULT}, ScriptEntity, ScriptWorldPtr
ScriptWorldPtr,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -34,7 +33,7 @@ impl mlua::UserData for LuaResQuery {
}) })
}); });
methods.add_method(FN_NAME_INTERNAL_ECS_QUERY_RESULT, |lua, this, world: ScriptWorldPtr| { methods.add_method(FN_NAME_INTERNAL_ECS_QUERY_RESULT, |lua, this, (world, _): (ScriptWorldPtr, ScriptEntity)| {
let mut world = world.write(); let mut world = world.write();
let reflect = this.ty.reflect_type()?; let reflect = this.ty.reflect_type()?;
@ -47,7 +46,7 @@ impl mlua::UserData for LuaResQuery {
.get_data::<ReflectLuaProxy>() .get_data::<ReflectLuaProxy>()
.expect("Type does not have ReflectLuaProxy as a TypeData"); .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 { } else {
// if the resource is not found in the world, return nil // if the resource is not found in the world, return nil
Ok(mlua::Value::Nil) Ok(mlua::Value::Nil)

View File

@ -1,9 +1,19 @@
use std::{ptr::NonNull, sync::Arc}; use std::sync::Arc;
use lyra_ecs::{query::dynamic::{DynamicViewState, QueryDynamicType}, Entity}; use atomic_refcell::AtomicRefCell;
use lyra_ecs::{
query::dynamic::{DynamicViewState, QueryDynamicType},
Entity,
};
use mlua::{IntoLua, IntoLuaMulti, ObjectLike}; 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 crate::{
lua::{
LuaComponent, LuaEntityRef, ReflectedIteratorOwned, TypeLookup, WorldError,
FN_NAME_INTERNAL_ECS_QUERY_RESULT, FN_NAME_INTERNAL_REFLECT_TYPE,
},
ScriptBorrow, ScriptWorldPtr,
};
use super::query::LuaQuery; use super::query::LuaQuery;
@ -31,44 +41,37 @@ impl mlua::FromLua for ViewQueryItem {
} }
impl ViewQueryItem { impl ViewQueryItem {
pub fn is_filter(&self) -> bool {
// TODO: check if UserData is a rust implemented filter
matches!(self, ViewQueryItem::Function(_))
}
/// Returns `true` if the QueryItem has a function of `name`. /// Returns `true` if the QueryItem has a function of `name`.
/// ///
/// Returns `false` if self is a function. /// Returns `false` if self is a function.
pub fn has_function(&self, name: &str) -> mlua::Result<bool> { pub fn has_function(&self, name: &str) -> mlua::Result<bool> {
match self { match self {
Self::UserData(ud) => { Self::UserData(ud) => ud.get::<mlua::Value>(name).map(|v| !v.is_nil()),
ud.get::<mlua::Value>(name).map(|v| !v.is_nil()) Self::Table(t) => t.contains_key(name),
},
Self::Table(t) => {
t.contains_key(name)
},
Self::Function(_) => Ok(false), 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<bool> { pub fn is_query(&self) -> mlua::Result<bool> {
self.has_function(FN_NAME_INTERNAL_ECS_QUERY_RESULT) Ok(matches!(self, ViewQueryItem::Function(_))
|| self.has_function(FN_NAME_INTERNAL_ECS_QUERY_RESULT)?)
} }
/// Clone self into a [`LuaComponent`]. /// Get self as a [`LuaQuery`].
pub fn as_component(&self) -> Option<LuaComponent> { ///
/// If self is a function, it assumes that it is a filter.
pub fn as_query(&self) -> LuaQuery {
match self.clone() { match self.clone() {
ViewQueryItem::UserData(ud) => Some(LuaComponent::UserData(ud)), ViewQueryItem::UserData(ud) => LuaQuery::new(LuaComponent::UserData(ud)),
ViewQueryItem::Table(t) => Some(LuaComponent::Table(t)), ViewQueryItem::Table(t) => LuaQuery::new(LuaComponent::Table(t)),
ViewQueryItem::Function(_) => None ViewQueryItem::Function(function) => LuaQuery::from_function(function),
} }
} }
/* pub fn get_lookup(&self) -> Option<Res<TypeLookup>> {
match self {
}
} */
} }
#[derive(Clone)] #[derive(Clone)]
@ -79,7 +82,8 @@ pub struct View {
impl mlua::FromLua for View { impl mlua::FromLua for View {
fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> { fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
let tyname = value.type_name(); let tyname = value.type_name();
value.as_userdata() value
.as_userdata()
.ok_or(mlua::Error::FromLuaConversionError { .ok_or(mlua::Error::FromLuaConversionError {
from: tyname, from: tyname,
to: "View".into(), to: "View".into(),
@ -100,13 +104,18 @@ impl mlua::UserData for View {
} }
} }
enum QueryResult {
None,
AlwaysNone,
Some(Vec<(mlua::Value, u32)>)
}
#[derive(Clone)] #[derive(Clone)]
pub struct ViewResult { pub struct ViewResult {
world: ScriptWorldPtr, world: ScriptWorldPtr,
reflect_iter: Arc<atomic_refcell::AtomicRefCell<ReflectedIteratorOwned>>, reflected_iter: Arc<atomic_refcell::AtomicRefCell<ReflectedIteratorOwned>>,
/// The queries and the index they would be inserted in the result. /// The queries and the index they would be inserted in the result.
queries: Vec<(LuaQuery, u32)>, queries: Vec<(LuaQuery, u32)>,
reg_key: Arc<atomic_refcell::AtomicRefCell<Option<mlua::RegistryKey>>>,
} }
unsafe impl Send for ViewResult {} unsafe impl Send for ViewResult {}
@ -120,8 +129,7 @@ impl ViewResult {
for (idx, comp) in items.iter().enumerate() { for (idx, comp) in items.iter().enumerate() {
if comp.is_query()? { if comp.is_query()? {
// Unwrap is safe since `is_query` ensures that the component is convertible. queries.push((comp.as_query(), idx as u32));
queries.push((LuaQuery::new(comp.as_component().unwrap()), idx as u32));
continue; continue;
} }
@ -129,23 +137,17 @@ impl ViewResult {
ViewQueryItem::Table(t) => { ViewQueryItem::Table(t) => {
let name: String = t.get(mlua::MetaMethod::Type.name())?; let name: String = t.get(mlua::MetaMethod::Type.name())?;
let lookup = let lookup = w.get_resource::<TypeLookup>().ok_or(mlua::Error::runtime(
w "Unable to lookup table proxy, none were ever registered!",
.get_resource::<TypeLookup>() ))?;
.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(|| { let info = lookup.comp_info_from_name.get(&name).ok_or_else(|| {
mlua::Error::BadArgument { mlua::Error::BadArgument {
to: Some("ViewResult.new".into()), to: Some("ViewResult.new".into()),
pos: 2 + idx, pos: 2 + idx,
name: Some("query...".into()), name: Some("query...".into()),
cause: Arc::new(mlua::Error::external( cause: Arc::new(mlua::Error::external(WorldError::LuaInvalidUsage(
WorldError::LuaInvalidUsage(format!( format!("the 'Table' with name {} is unknown to the engine!", name),
"the 'Table' with name {} is unknown to the engine!", ))),
name
)),
)),
} }
})?; })?;
@ -161,7 +163,9 @@ impl ViewResult {
let dyn_type = QueryDynamicType::from_info(refl_comp.info); let dyn_type = QueryDynamicType::from_info(refl_comp.info);
view.push(dyn_type); view.push(dyn_type);
} }
_ => todo!(), // functions are queries, the if statement at the start would cause this to
// be unreachable.
ViewQueryItem::Function(_) => unreachable!()
} }
} }
@ -175,24 +179,24 @@ impl ViewResult {
Ok(Self { Ok(Self {
world, world,
reflect_iter: Arc::new(atomic_refcell::AtomicRefCell::new(reflected_iter)), reflected_iter: Arc::new(AtomicRefCell::new(reflected_iter)),
queries, queries,
reg_key: Arc::new(atomic_refcell::AtomicRefCell::new(None)),
}) })
} }
/// Get the next row of components /// Get the next row of components
fn next_components(&mut self, lua: &mlua::Lua) -> Result<Option<(Entity, mlua::MultiValue)>, mlua::Error> { fn next_components(
let mut query_iter = self.reflect_iter.borrow_mut(); &mut self,
lua: &mlua::Lua,
) -> Result<Option<(Entity, mlua::MultiValue)>, mlua::Error> {
let mut query_iter = self.reflected_iter.borrow_mut();
if let Some(row) = query_iter.next_lua(lua) { if let Some(row) = query_iter.next_lua(lua) {
let r = row let (values, _): (Vec<_>, Vec<_>) = row
.row .row
.into_iter() .into_iter()
.into_iter() .into_iter()
.map(|r| (r.comp_val, r.comp_ptr.cast::<()>())) .map(|r| (r.comp_val, r.comp_ptr.cast::<()>()))
.collect::<Vec<_>>(); .unzip();
let (values, _) =
itertools::multiunzip::<(Vec<mlua::Value>, Vec<NonNull<()>>), _>(r);
let mult_val = mlua::MultiValue::from_iter(values.into_iter()); let mult_val = mlua::MultiValue::from_iter(values.into_iter());
Ok(Some((row.entity, mult_val))) Ok(Some((row.entity, mult_val)))
} else { } else {
@ -200,37 +204,36 @@ impl ViewResult {
} }
} }
/// Get the query results /// Get the query results and the indexes that they were provided in.
fn get_query_results(&self) -> mlua::Result<Option<Vec<(mlua::Value, u32)>>> { ///
/// 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<QueryResult> {
let mut query_vals = vec![]; let mut query_vals = vec![];
let mut query_allows = true;
for (query, i) in &self.queries { for (query, i) in &self.queries {
let qres = query.get_query_result(self.world.clone())?; let qres = query.get_query_result(self.world.clone(), entity)?;
if let Some(val) = qres.as_boolean() { if let Some(val) = qres.as_boolean() {
if val { // do not push a boolean to values, its considered a filter
query_allows = false; if !val {
break; return Ok(QueryResult::None);
} }
} else if qres.is_nil() { } else if qres.is_nil() {
query_allows = false; return Ok(QueryResult::AlwaysNone);
break;
} }
query_vals.push((qres, *i)); query_vals.push((qres, *i));
} }
if !query_allows { Ok(QueryResult::Some(query_vals))
Ok(None)
} else {
Ok(Some(query_vals))
}
} }
} }
impl mlua::FromLua for ViewResult { impl mlua::FromLua for ViewResult {
fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> { fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
let tyname = value.type_name(); let tyname = value.type_name();
value.as_userdata() value
.as_userdata()
.ok_or(mlua::Error::FromLuaConversionError { .ok_or(mlua::Error::FromLuaConversionError {
from: tyname, from: tyname,
to: "View".into(), to: "View".into(),
@ -244,65 +247,78 @@ impl mlua::FromLua for ViewResult {
impl mlua::UserData for ViewResult { impl mlua::UserData for ViewResult {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) { fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
methods.add_method_mut("next", |lua, this, ()| { methods.add_method_mut("next", |lua, this, ()| {
let query_vals = match this.get_query_results()? {
Some(v) => v,
None => {
return mlua::Value::Nil.into_lua_multi(lua);
}
};
match this.next_components(lua)? { match this.next_components(lua)? {
Some((en, mut vals)) => { Some((en, mut vals)) => {
// insert query values to the result row loop {
for (qval, qi) in query_vals { let query_vals = match this.get_query_results(en)? {
vals.insert(qi as _, qval); QueryResult::Some(v) => v,
} QueryResult::AlwaysNone => {
return mlua::Value::Nil.into_lua_multi(lua);
},
QueryResult::None => {
// try to get it next loop
continue;
}
};
vals.push_front(LuaEntityRef::new(this.world.clone(), en).into_lua(lua)?); // insert query values to the result row
Ok(vals) for (qval, qi) in query_vals {
}, vals.insert(qi as _, qval);
None => mlua::Value::Nil.into_lua_multi(lua) }
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, ()| { methods.add_method("iter", |lua, this, ()| {
let key_arc = Arc::new(atomic_refcell::AtomicRefCell::new(Some(lua.create_registry_value(this.clone())?))); let key_arc = Arc::new(atomic_refcell::AtomicRefCell::new(Some(
lua.create_registry_value(this.clone())?,
)));
lua.create_function(move |lua, ()| { lua.create_function(move |lua, ()| {
let mut key_mut = key_arc.borrow_mut(); let mut key_mut = key_arc.borrow_mut();
if let Some(key) = key_mut.as_ref() { if let Some(key) = key_mut.as_ref() {
let mut this = lua.registry_value::<Self>(&key)?; let mut this = lua.registry_value::<mlua::UserDataRefMut<Self>>(&key)?;
let query_vals = match this.get_query_results()? { loop {
Some(v) => v, match this.next_components(lua)? {
None => { Some((en, mut vals)) => {
return mlua::Value::Nil.into_lua_multi(lua); let lua_en =
} LuaEntityRef::new(this.world.clone(), en).into_lua(lua)?;
};
match this.next_components(lua)? { let query_vals = match this.get_query_results(en)? {
Some((en, mut vals)) => { QueryResult::Some(v) => v,
let lua_en = LuaEntityRef::new(this.world, en) QueryResult::AlwaysNone => {
.into_lua(lua)?; return mlua::Value::Nil.into_lua_multi(lua);
},
QueryResult::None => {
continue;
}
};
// insert query values to the result row // insert query values to the result row
for (qval, qi) in query_vals { for (qval, qi) in query_vals {
vals.insert(qi as _, qval); 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)?;
vals.push_front(lua_en); return mlua::Value::Nil.into_lua_multi(lua);
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)?;
mlua::Value::Nil.into_lua_multi(lua)
} }
} }
} else { } else {
@ -311,4 +327,4 @@ impl mlua::UserData for ViewResult {
}) })
}); });
} }
} }

View File

@ -6,7 +6,7 @@ use mlua::{IntoLua, ObjectLike};
use crate::{ScriptBorrow, ScriptWorldPtr}; use crate::{ScriptBorrow, ScriptWorldPtr};
use super::{reflect_user_data, Error, ReflectLuaProxy, TypeLookup, FN_NAME_INTERNAL_REFLECT_TYPE}; use super::{reflect_type_user_data, Error, ReflectLuaProxy, TypeLookup, FN_NAME_INTERNAL_REFLECT_TYPE};
#[derive(Clone)] #[derive(Clone)]
pub enum LuaComponent { pub enum LuaComponent {
@ -49,7 +49,7 @@ impl LuaComponent {
lookup.typeid_from_name.get(&name).cloned() lookup.typeid_from_name.get(&name).cloned()
} }
Self::UserData(ud) => { Self::UserData(ud) => {
let lua_comp = reflect_user_data(ud); let lua_comp = reflect_type_user_data(ud);
let refl_comp = lua_comp.reflect_branch.as_component_unchecked(); let refl_comp = lua_comp.reflect_branch.as_component_unchecked();
Some(refl_comp.info.type_id().as_rust()) Some(refl_comp.info.type_id().as_rust())
} }
@ -138,7 +138,7 @@ impl mlua::UserData for LuaEntityRef {
let arch = world.entity_archetype_mut(this.en).unwrap(); let arch = world.entity_archetype_mut(this.en).unwrap();
let arch_idx = *arch.entity_indexes().get(&this.en).unwrap(); let arch_idx = *arch.entity_indexes().get(&this.en).unwrap();
let col = arch.get_column_mut(tid).unwrap(); let col = arch.get_column_mut(tid).unwrap();
let col_ptr = col.get_entity_ptr(*arch_idx as usize, &world_tick).cast(); let col_ptr = col.component_ptr(*arch_idx as usize, &world_tick).cast();
// get the type registry to apply the new value // get the type registry to apply the new value
let reg = world.get_resource::<TypeRegistry>().unwrap(); let reg = world.get_resource::<TypeRegistry>().unwrap();
@ -149,7 +149,7 @@ impl mlua::UserData for LuaEntityRef {
// this should actually be safe since the ReflectedIterator // this should actually be safe since the ReflectedIterator
// attempts to get the type data before it is tried here // attempts to get the type data before it is tried here
.expect("Type does not have ReflectLuaProxy as a TypeData"); .expect("Type does not have ReflectLuaProxy as a TypeData");
(proxy.fn_apply)(lua, col_ptr, &comp)?; proxy.apply(lua, col_ptr, &comp)?;
} }
Ok(()) Ok(())

View File

@ -95,11 +95,16 @@ 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. /// Name of a Lua function to retrieve the query result from a Userdata, or Table.
/// ///
/// This function must return a Lua value and take in a single argument: [`ScriptWorldPtr`]. /// The function must match the following definition: `fn(ScriptWorldPtr, Entity) -> LuaValue`.
/// If `nil` is returned, the query in a [`View`](crate::lua::ecs::View) will not provide any ///
/// results.\ /// When `nil` is returned, its considered that the query will not result in anything for this
/// If it returns a boolean, the result will act as a filter, and the value will not be in /// [`View`], **no matter the entity**. When the query is used in a [`View`] and returns `nil`,
/// the result.\ /// 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. /// Any other value will be included in the result.
pub const FN_NAME_INTERNAL_ECS_QUERY_RESULT: &str = "__lyra_internal_ecs_query_result"; pub const FN_NAME_INTERNAL_ECS_QUERY_RESULT: &str = "__lyra_internal_ecs_query_result";
@ -288,6 +293,16 @@ impl mlua::UserData for ScriptBorrow {
/// Helper function used for reflecting userdata as a ScriptBorrow /// Helper function used for reflecting userdata as a ScriptBorrow
pub fn reflect_user_data(ud: &mlua::AnyUserData) -> ScriptBorrow { pub fn reflect_user_data(ud: &mlua::AnyUserData) -> ScriptBorrow {
let ud_name = ud.metatable().and_then(|mt| mt.get::<String>(mlua::MetaMethod::Type))
.unwrap_or("Unknown".to_string());
ud.call_method::<ScriptBorrow>(FN_NAME_INTERNAL_REFLECT, ()) ud.call_method::<ScriptBorrow>(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::<String>(mlua::MetaMethod::Type))
.unwrap_or("Unknown".to_string());
ud.call_function::<ScriptBorrow>(FN_NAME_INTERNAL_REFLECT_TYPE, ())
.unwrap_or_else(|_| panic!("UserData of name '{}' does not implement internal reflect type function properly", ud_name))
} }

View File

@ -1,7 +1,7 @@
use lyra_ecs::ResourceObject; use lyra_ecs::ResourceObject;
use lyra_reflect::Reflect; use lyra_reflect::Reflect;
use crate::{lua::{ecs::{query::LuaResQuery, View}, wrappers::*, LuaContext, LuaWrapper, RegisterLuaType, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE}, ScriptApiProvider, ScriptBorrow, ScriptData, ScriptDynamicBundle, ScriptWorldPtr}; use crate::{lua::{ecs::{query::{LuaChangedQuery, LuaResQuery}, View}, wrappers::*, LuaContext, LuaWrapper, RegisterLuaType, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE}, ScriptApiProvider, ScriptBorrow, ScriptData, ScriptDynamicBundle, ScriptWorldPtr};
//fn register_lua_proxy::<T: //fn register_lua_proxy::<T:
@ -46,6 +46,7 @@ impl ScriptApiProvider for LyraEcsApiProvider {
globals.set("Window", ctx.create_proxy::<LuaWindow>()?)?; globals.set("Window", ctx.create_proxy::<LuaWindow>()?)?;
globals.set("View", ctx.create_proxy::<View>()?)?; globals.set("View", ctx.create_proxy::<View>()?)?;
globals.set("ResQuery", ctx.create_proxy::<LuaResQuery>()?)?; globals.set("ResQuery", ctx.create_proxy::<LuaResQuery>()?)?;
globals.set("ChangedQuery", ctx.create_proxy::<LuaChangedQuery>()?)?;
expose_comp_table_wrapper::<LuaCamera>(&ctx, &globals, "Camera")?; expose_comp_table_wrapper::<LuaCamera>(&ctx, &globals, "Camera")?;
expose_comp_table_wrapper::<LuaFreeFlyCamera>(&ctx, &globals, "FreeFlyCamera")?; expose_comp_table_wrapper::<LuaFreeFlyCamera>(&ctx, &globals, "FreeFlyCamera")?;

View File

@ -78,9 +78,9 @@ pub struct TypeLookup {
/// A struct used for Proxying types to and from Lua. /// A struct used for Proxying types to and from Lua.
#[derive(Clone)] #[derive(Clone)]
pub struct ReflectLuaProxy { pub struct ReflectLuaProxy {
pub fn_as_lua: fn_as_lua:
for<'a> fn(lua: &'a mlua::Lua, this_ptr: NonNull<()>) -> mlua::Result<mlua::Value>, for<'a> fn(lua: &'a mlua::Lua, this_ptr: NonNull<()>) -> mlua::Result<mlua::Value>,
pub fn_apply: for<'a> fn( fn_apply: for<'a> fn(
lua: &'a mlua::Lua, lua: &'a mlua::Lua,
this_ptr: NonNull<()>, this_ptr: NonNull<()>,
value: &'a mlua::Value, 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<mlua::Value> {
(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 { impl mlua::FromLua for ScriptDynamicBundle {

View File

@ -222,7 +222,7 @@ impl mlua::UserData for ScriptWorldPtr {
// this should actually be safe since the ReflectedIterator // this should actually be safe since the ReflectedIterator
// attempts to get the type data before it is tried here // attempts to get the type data before it is tried here
.expect("Type does not have ReflectLuaProxy as a TypeData"); .expect("Type does not have ReflectLuaProxy as a TypeData");
(proxy.fn_apply)(lua, ptr, &comp)?; proxy.apply(lua, ptr, &comp)?;
} }
} else { } else {
let msg = format!( let msg = format!(
@ -262,7 +262,7 @@ impl mlua::UserData for ScriptWorldPtr {
.get_data::<ReflectLuaProxy>() .get_data::<ReflectLuaProxy>()
.expect("Type does not have ReflectLuaProxy as a TypeData"); .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 { } else {
// if the resource is not found in the world, return nil // if the resource is not found in the world, return nil
Ok(mlua::Value::Nil) Ok(mlua::Value::Nil)