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, Transform) ]]
-- although WorldTransform isn't used, I only want to modify entities with that component.
local view = View.new(Transform, WorldTransform, Res(DeltaTime))
local res = world:view_query(view)
for entity, transform, world_tran, dt in res:iter() do
print("Entity is at: " .. tostring(world_tran))
for entity, transform, _wt, dt in res:iter() do
transform:translate(0, 0.15 * dt, 0)
entity:update(transform)
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
--[[ function on_post_update()

View File

@ -214,16 +214,34 @@ impl ComponentColumn {
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
/// updated.
pub fn get_entity_ptr(&mut self, entity_index: usize, tick: &Tick) -> NonNull<u8> {
self.entity_ticks.insert(entity_index, *tick);
pub fn component_ptr(&mut self, entity_index: usize, tick: &Tick) -> NonNull<u8> {
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<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>> {
self.data.borrow()
}
@ -272,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<Entity>,
pub(crate) columns: Vec<ComponentColumn>,
pub columns: Vec<ComponentColumn>,
capacity: usize,
}

View File

@ -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);
}
}
}

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) {
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.
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

View File

@ -26,11 +26,11 @@ pub struct Record {
#[derive(Clone)]
pub struct World {
pub(crate) archetypes: HashMap<ArchetypeId, Archetype>,
pub archetypes: HashMap<ArchetypeId, Archetype>,
next_archetype_id: ArchetypeId,
resources: HashMap<TypeId, ResourceData>,
tracker: TickTracker,
pub(crate) entities: Entities,
pub entities: Entities,
}
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.
///
/// 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 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<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_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 {
@ -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>> {
(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 {
@ -69,6 +79,12 @@ impl<C: Component + Reflect> FromType<C> for ReflectedComponent {
world.view_one::<&mut C>(entity)
.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 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<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_ptr: fn (world: &mut World) -> Option<NonNull<u8>>,
fn_refl_insert: fn (world: &mut World, this: Box<dyn Reflect>),
@ -20,6 +22,14 @@ impl ReflectedResource {
(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.
pub fn reflect_mut<'a>(&self, world: &'a mut World) -> Option<AtomicRefMut<'a, dyn Reflect>> {
(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)
})
},
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| {
world.get_resource_mut::<T>()
.map(|r| {

View File

@ -3,4 +3,11 @@
---@return ResQuery
function Res(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

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![];
for d in row.iter() {
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)
.expect("Requested type was not found in TypeRegistry");
let proxy = reg_type.get_data::<ReflectLuaProxy>()
// TODO: properly handle this error
.expect("Type does not have ReflectLuaProxy as a TypeData");
let value = (proxy.fn_as_lua)(lua, d.ptr.cast()).unwrap()
let value = proxy.as_lua(lua, d.ptr.cast()).unwrap()
.into_lua(lua).unwrap();
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;
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)]
pub struct LuaQuery {
query: LuaComponent
enum QueryInner {
Component(LuaComponent),
Function(mlua::Function)
}
#[derive(Clone)]
pub struct LuaQuery(QueryInner);
impl LuaQuery {
pub fn new(query: LuaComponent) -> Self {
Self {
query
}
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) -> mlua::Result<mlua::Value> {
self.query.call_method(FN_NAME_INTERNAL_ECS_QUERY_RESULT, world)
pub fn get_query_result(&self, world: ScriptWorldPtr, entity: Entity) -> mlua::Result<mlua::Value> {
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 crate::{
lua::{LuaComponent, ReflectLuaProxy, FN_NAME_INTERNAL_ECS_QUERY_RESULT},
ScriptWorldPtr,
lua::{LuaComponent, ReflectLuaProxy, FN_NAME_INTERNAL_ECS_QUERY_RESULT}, ScriptEntity, ScriptWorldPtr
};
#[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 reflect = this.ty.reflect_type()?;
@ -47,7 +46,7 @@ impl mlua::UserData for LuaResQuery {
.get_data::<ReflectLuaProxy>()
.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)

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 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;
@ -31,44 +41,37 @@ impl mlua::FromLua for 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 `false` if self is a function.
pub fn has_function(&self, name: &str) -> mlua::Result<bool> {
match self {
Self::UserData(ud) => {
ud.get::<mlua::Value>(name).map(|v| !v.is_nil())
},
Self::Table(t) => {
t.contains_key(name)
},
Self::UserData(ud) => ud.get::<mlua::Value>(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<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`].
pub fn as_component(&self) -> Option<LuaComponent> {
/// 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) => Some(LuaComponent::UserData(ud)),
ViewQueryItem::Table(t) => Some(LuaComponent::Table(t)),
ViewQueryItem::Function(_) => None
ViewQueryItem::UserData(ud) => LuaQuery::new(LuaComponent::UserData(ud)),
ViewQueryItem::Table(t) => LuaQuery::new(LuaComponent::Table(t)),
ViewQueryItem::Function(function) => LuaQuery::from_function(function),
}
}
/* pub fn get_lookup(&self) -> Option<Res<TypeLookup>> {
match self {
}
} */
}
#[derive(Clone)]
@ -79,7 +82,8 @@ pub struct View {
impl mlua::FromLua for View {
fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
let tyname = value.type_name();
value.as_userdata()
value
.as_userdata()
.ok_or(mlua::Error::FromLuaConversionError {
from: tyname,
to: "View".into(),
@ -100,13 +104,18 @@ impl mlua::UserData for View {
}
}
enum QueryResult {
None,
AlwaysNone,
Some(Vec<(mlua::Value, u32)>)
}
#[derive(Clone)]
pub struct ViewResult {
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.
queries: Vec<(LuaQuery, u32)>,
reg_key: Arc<atomic_refcell::AtomicRefCell<Option<mlua::RegistryKey>>>,
}
unsafe impl Send for ViewResult {}
@ -120,8 +129,7 @@ impl ViewResult {
for (idx, comp) in items.iter().enumerate() {
if comp.is_query()? {
// Unwrap is safe since `is_query` ensures that the component is convertible.
queries.push((LuaQuery::new(comp.as_component().unwrap()), idx as u32));
queries.push((comp.as_query(), idx as u32));
continue;
}
@ -129,23 +137,17 @@ impl ViewResult {
ViewQueryItem::Table(t) => {
let name: String = t.get(mlua::MetaMethod::Type.name())?;
let lookup =
w
.get_resource::<TypeLookup>()
.ok_or(mlua::Error::runtime(
"Unable to lookup table proxy, none were ever registered!",
))?;
let lookup = w.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(|| {
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
)),
)),
cause: Arc::new(mlua::Error::external(WorldError::LuaInvalidUsage(
format!("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);
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 {
world,
reflect_iter: Arc::new(atomic_refcell::AtomicRefCell::new(reflected_iter)),
reflected_iter: Arc::new(AtomicRefCell::new(reflected_iter)),
queries,
reg_key: Arc::new(atomic_refcell::AtomicRefCell::new(None)),
})
}
/// Get the next row of components
fn next_components(&mut self, lua: &mlua::Lua) -> Result<Option<(Entity, mlua::MultiValue)>, mlua::Error> {
let mut query_iter = self.reflect_iter.borrow_mut();
fn next_components(
&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) {
let r = row
let (values, _): (Vec<_>, Vec<_>) = row
.row
.into_iter()
.into_iter()
.map(|r| (r.comp_val, r.comp_ptr.cast::<()>()))
.collect::<Vec<_>>();
let (values, _) =
itertools::multiunzip::<(Vec<mlua::Value>, Vec<NonNull<()>>), _>(r);
.unzip();
let mult_val = mlua::MultiValue::from_iter(values.into_iter());
Ok(Some((row.entity, mult_val)))
} else {
@ -200,37 +204,36 @@ impl ViewResult {
}
}
/// Get the query results
fn get_query_results(&self) -> mlua::Result<Option<Vec<(mlua::Value, u32)>>> {
/// 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<QueryResult> {
let mut query_vals = vec![];
let mut query_allows = true;
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 val {
query_allows = false;
break;
// do not push a boolean to values, its considered a filter
if !val {
return Ok(QueryResult::None);
}
} else if qres.is_nil() {
query_allows = false;
break;
return Ok(QueryResult::AlwaysNone);
}
query_vals.push((qres, *i));
}
if !query_allows {
Ok(None)
} else {
Ok(Some(query_vals))
}
Ok(QueryResult::Some(query_vals))
}
}
impl mlua::FromLua for ViewResult {
fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
let tyname = value.type_name();
value.as_userdata()
value
.as_userdata()
.ok_or(mlua::Error::FromLuaConversionError {
from: tyname,
to: "View".into(),
@ -244,65 +247,78 @@ impl mlua::FromLua for ViewResult {
impl mlua::UserData for ViewResult {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
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)? {
Some((en, mut vals)) => {
// insert query values to the result row
for (qval, qi) in query_vals {
vals.insert(qi as _, qval);
}
loop {
let query_vals = match this.get_query_results(en)? {
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)?);
Ok(vals)
},
None => mlua::Value::Nil.into_lua_multi(lua)
// 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())?)));
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::<Self>(&key)?;
let mut this = lua.registry_value::<mlua::UserDataRefMut<Self>>(&key)?;
let query_vals = match this.get_query_results()? {
Some(v) => v,
None => {
return mlua::Value::Nil.into_lua_multi(lua);
}
};
loop {
match this.next_components(lua)? {
Some((en, mut vals)) => {
let lua_en =
LuaEntityRef::new(this.world.clone(), en).into_lua(lua)?;
match this.next_components(lua)? {
Some((en, mut vals)) => {
let lua_en = LuaEntityRef::new(this.world, en)
.into_lua(lua)?;
let query_vals = match this.get_query_results(en)? {
QueryResult::Some(v) => v,
QueryResult::AlwaysNone => {
return mlua::Value::Nil.into_lua_multi(lua);
},
QueryResult::None => {
continue;
}
};
// insert query values to the result row
for (qval, qi) in query_vals {
vals.insert(qi as _, qval);
// 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)?;
vals.push_front(lua_en);
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)
return mlua::Value::Nil.into_lua_multi(lua);
}
}
}
} 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 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)]
pub enum LuaComponent {
@ -49,7 +49,7 @@ impl LuaComponent {
lookup.typeid_from_name.get(&name).cloned()
}
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();
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_idx = *arch.entity_indexes().get(&this.en).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
let reg = world.get_resource::<TypeRegistry>().unwrap();
@ -149,7 +149,7 @@ impl mlua::UserData for LuaEntityRef {
// 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, col_ptr, &comp)?;
proxy.apply(lua, col_ptr, &comp)?;
}
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.
///
/// This function must return a Lua value and take in a single argument: [`ScriptWorldPtr`].
/// If `nil` is returned, the query in a [`View`](crate::lua::ecs::View) will not provide any
/// results.\
/// If it returns a boolean, the result will act as a filter, and the value will not be in
/// the result.\
/// 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";
@ -288,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::<String>(mlua::MetaMethod::Type))
.unwrap_or("Unknown".to_string());
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_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:
@ -46,6 +46,7 @@ impl ScriptApiProvider for LyraEcsApiProvider {
globals.set("Window", ctx.create_proxy::<LuaWindow>()?)?;
globals.set("View", ctx.create_proxy::<View>()?)?;
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::<LuaFreeFlyCamera>(&ctx, &globals, "FreeFlyCamera")?;

View File

@ -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<mlua::Value>,
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<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 {

View File

@ -222,7 +222,7 @@ impl mlua::UserData for ScriptWorldPtr {
// 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)?;
proxy.apply(lua, ptr, &comp)?;
}
} else {
let msg = format!(
@ -262,7 +262,7 @@ impl mlua::UserData for ScriptWorldPtr {
.get_data::<ReflectLuaProxy>()
.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)