lua: implement Changed query that supports components and resources
This commit is contained in:
parent
74465ce614
commit
2e33de5da2
|
@ -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()
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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| {
|
||||
|
|
|
@ -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
|
|
@ -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 {
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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)),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
|||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
|
|
|
@ -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))
|
||||
}
|
|
@ -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")?;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue