lua: implement ecs queries that work with the new Views

This commit is contained in:
SeanOMik 2024-10-19 20:42:28 -04:00
parent 2ffdd4085b
commit 380b15e560
Signed by: SeanOMik
GPG Key ID: FEC9E2FC15235964
10 changed files with 230 additions and 40 deletions

View File

@ -90,37 +90,15 @@ function on_update()
end end
end, Transform) ]] end, Transform) ]]
---@type number local view = View.new(Transform, WorldTransform, Res(DeltaTime))
local dt = world:resource(DeltaTime)
local view = View.new(Transform, WorldTransform)
local res = world:view_query(view) local res = world:view_query(view)
for entity, transform, world_tran in res:iter() do for entity, transform, world_tran, dt in res:iter() do
print("Entity is at: " .. tostring(world_tran)) 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
--[[ world:view(
---@param t Transform
function (t)
t:translate(0, 0.15 * dt, 0)
return t
end, Transform
) ]]
--[[ world:view(
---@param c Camera
function (c)
c.transform:translate(0, 0.15 * dt, 0)
print("Moving camera to: " .. tostring(c.transform))
return c
end, Camera
) ]]
end end
--[[ function on_post_update() --[[ function on_post_update()

View File

@ -0,0 +1,6 @@
---Create a Resource Query
---@param resource table|userdata
---@return ResQuery
function Res(resource)
return ResQuery.new(resource)
end

View File

@ -0,0 +1,4 @@
mod view;
pub use view::*;
pub mod query;

View File

@ -0,0 +1,26 @@
mod res;
use mlua::ObjectLike;
pub use res::*;
use crate::{lua::{LuaComponent, FN_NAME_INTERNAL_ECS_QUERY_RESULT}, ScriptWorldPtr};
#[derive(Clone)]
pub struct LuaQuery {
query: LuaComponent
}
impl LuaQuery {
pub fn new(query: LuaComponent) -> Self {
Self {
query
}
}
/// 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)
}
}

View File

@ -0,0 +1,57 @@
use lyra_reflect::{ReflectWorldExt, RegisteredType};
use mlua::IntoLua;
use crate::{
lua::{LuaComponent, ReflectLuaProxy, WorldError, FN_NAME_INTERNAL_ECS_QUERY_RESULT},
ScriptWorldPtr,
};
#[derive(Clone)]
pub struct LuaResQuery {
ty: LuaComponent,
}
impl mlua::FromLua for LuaResQuery {
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: "ResQuery".into(),
message: None,
})
.and_then(|ud| ud.borrow::<Self>())
.map(|ud| ud.clone())
}
}
impl mlua::UserData for LuaResQuery {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
methods.add_function("new", |_, comp: LuaComponent| {
Ok(Self {
ty: comp
})
});
methods.add_method(FN_NAME_INTERNAL_ECS_QUERY_RESULT, |lua, this, world: ScriptWorldPtr| {
let mut world = world.write();
let reflect = this.ty.reflect_type()?;
let res = reflect.reflect_branch.as_resource_unchecked();
if let Some(res_ptr) = res.reflect_ptr(&mut world) {
let reg_type = world
.get_type::<RegisteredType>(reflect.reflect_branch.reflect_type_id())
.expect("Resource is not type registered!");
let proxy = reg_type
.get_data::<ReflectLuaProxy>()
.expect("Type does not have ReflectLuaProxy as a TypeData");
(proxy.fn_as_lua)(lua, res_ptr.cast()).and_then(|ud| ud.into_lua(lua))
} else {
// if the resource is not found in the world, return nil
Ok(mlua::Value::Nil)
}
});
}
}

View File

@ -1,12 +1,13 @@
use std::{cell::RefCell, ptr::NonNull, rc::Rc, sync::Arc}; use std::{cell::RefCell, ptr::NonNull, rc::Rc, sync::Arc};
use lyra_ecs::{query::dynamic::{DynamicViewState, QueryDynamicType}, Entity}; use lyra_ecs::{query::dynamic::{DynamicViewState, QueryDynamicType}, Entity};
use lyra_reflect::List;
use mlua::{FromLuaMulti, IntoLua, IntoLuaMulti, ObjectLike}; use mlua::{FromLuaMulti, IntoLua, IntoLuaMulti, ObjectLike};
use tracing::debug; use tracing::debug;
use crate::{lua::{WorldError, 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::{LuaEntityRef, ReflectedIteratorOwned, TypeLookup}; use super::query::LuaQuery;
#[derive(Clone)] #[derive(Clone)]
enum ViewQueryItem { enum ViewQueryItem {
@ -37,6 +38,34 @@ impl ViewQueryItem {
matches!(self, ViewQueryItem::Function(_)) 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::Function(_) => Ok(false),
}
}
pub fn is_query(&self) -> mlua::Result<bool> {
self.has_function(FN_NAME_INTERNAL_ECS_QUERY_RESULT)
}
/// Clone self into a [`LuaComponent`].
pub fn as_component(&self) -> Option<LuaComponent> {
match self.clone() {
ViewQueryItem::UserData(ud) => Some(LuaComponent::UserData(ud)),
ViewQueryItem::Table(t) => Some(LuaComponent::Table(t)),
ViewQueryItem::Function(_) => None
}
}
/* pub fn get_lookup(&self) -> Option<Res<TypeLookup>> { /* pub fn get_lookup(&self) -> Option<Res<TypeLookup>> {
match self { match self {
@ -77,7 +106,9 @@ impl mlua::UserData for View {
pub struct ViewResult { pub struct ViewResult {
world: ScriptWorldPtr, world: ScriptWorldPtr,
items: Vec<ViewQueryItem>, items: Vec<ViewQueryItem>,
query_iter: Arc<atomic_refcell::AtomicRefCell<ReflectedIteratorOwned>>, reflect_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>>>, reg_key: Arc<atomic_refcell::AtomicRefCell<Option<mlua::RegistryKey>>>,
} }
@ -88,8 +119,15 @@ impl ViewResult {
let items = view.items.clone(); let items = view.items.clone();
let w = world.read(); let w = world.read();
let mut view = DynamicViewState::new(); let mut view = DynamicViewState::new();
let mut queries = vec![];
for (idx, comp) in items.iter().enumerate() { 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));
continue;
}
match comp { match comp {
ViewQueryItem::Table(t) => { ViewQueryItem::Table(t) => {
let name: String = t.get(mlua::MetaMethod::Type.name())?; let name: String = t.get(mlua::MetaMethod::Type.name())?;
@ -141,13 +179,14 @@ impl ViewResult {
Ok(Self { Ok(Self {
world, world,
items, items,
query_iter: Arc::new(atomic_refcell::AtomicRefCell::new(reflected_iter)), reflect_iter: Arc::new(atomic_refcell::AtomicRefCell::new(reflected_iter)),
queries,
reg_key: Arc::new(atomic_refcell::AtomicRefCell::new(None)), reg_key: Arc::new(atomic_refcell::AtomicRefCell::new(None)),
}) })
} }
fn next(&mut self, lua: &mlua::Lua) -> Result<Option<(Entity, mlua::MultiValue)>, mlua::Error> { fn next(&mut self, lua: &mlua::Lua) -> Result<Option<(Entity, mlua::MultiValue)>, mlua::Error> {
let mut query_iter = self.query_iter.borrow_mut(); let mut query_iter = self.reflect_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 r = row
.row .row
@ -196,12 +235,38 @@ impl mlua::UserData for ViewResult {
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::<Self>(&key)?;
let v = this.next(lua)?;
let mut query_vals = vec![];
match v { let mut query_allows = true;
for (query, i) in &this.queries {
let qres = query.get_query_result(this.world.clone())?;
if let Some(val) = qres.as_boolean() {
if val {
query_allows = false;
break;
}
} else if qres.is_nil() {
query_allows = false;
break;
}
query_vals.push((qres, *i));
}
if !query_allows {
return mlua::Value::Nil.into_lua_multi(lua);
}
match this.next(lua)? {
Some((en, mut vals)) => { Some((en, mut vals)) => {
let lua_en = LuaEntityRef::new(this.world, en) let lua_en = LuaEntityRef::new(this.world, en)
.into_lua(lua)?; .into_lua(lua)?;
// insert query values to the result row
for (qval, qi) in query_vals {
vals.insert(qi as _, qval);
}
vals.push_front(lua_en); vals.push_front(lua_en);
Ok(vals) Ok(vals)
}, },

View File

@ -2,11 +2,11 @@ use std::{any::TypeId, sync::Arc};
use lyra_ecs::{Entity, World}; use lyra_ecs::{Entity, World};
use lyra_reflect::TypeRegistry; use lyra_reflect::TypeRegistry;
use mlua::IntoLua; use mlua::{IntoLua, ObjectLike};
use crate::ScriptWorldPtr; use crate::{ScriptBorrow, ScriptWorldPtr};
use super::{reflect_user_data, ReflectLuaProxy, TypeLookup}; use super::{reflect_user_data, Error, ReflectLuaProxy, TypeLookup, FN_NAME_INTERNAL_REFLECT_TYPE};
#[derive(Clone)] #[derive(Clone)]
pub enum LuaComponent { pub enum LuaComponent {
@ -55,6 +55,46 @@ impl LuaComponent {
} }
} }
} }
/// Call the internal reflect type function and return the result.
///
/// This calls the [`FN_NAME_INTERNAL_REFLECT_TYPE`] function on the Component.
pub fn reflect_type(&self) -> Result<ScriptBorrow, Error> {
self.call_function(FN_NAME_INTERNAL_REFLECT_TYPE, ())
.map_err(|_| Error::Reflect)
}
/// Call a Lua function on the Component.
///
/// This is a helper function so you don't have to match on the component.
pub fn call_function<R: mlua::FromLuaMulti>(&self, name: &str, args: impl mlua::IntoLuaMulti) -> mlua::Result<R> {
match self {
LuaComponent::UserData(ud) => ud.call_function(name, args),
LuaComponent::Table(t) => t.call_function(name, args),
}
}
/// Call a Lua method on the Component.
///
/// This is a helper function so you don't have to match on the component.
pub fn call_method<R: mlua::FromLuaMulti>(&self, name: &str, args: impl mlua::IntoLuaMulti) -> mlua::Result<R> {
match self {
LuaComponent::UserData(ud) => ud.call_method(name, args),
LuaComponent::Table(t) => t.call_method(name, args),
}
}
/// Returns `true` if the Component has a function of `name`.
pub fn has_function(&self, name: &str) -> mlua::Result<bool> {
match self {
LuaComponent::UserData(ud) => {
ud.get::<mlua::Value>(name).map(|v| !v.is_nil())
},
LuaComponent::Table(t) => {
t.contains_key(name)
},
}
}
} }
/// Reference to an Entity for Lua /// Reference to an Entity for Lua

View File

@ -18,8 +18,7 @@ pub mod wrappers;
pub mod proxy; pub mod proxy;
pub use proxy::*; pub use proxy::*;
mod view; pub mod ecs;
pub use view::*;
mod entity_ref; mod entity_ref;
pub use entity_ref::*; pub use entity_ref::*;
@ -48,7 +47,9 @@ pub enum Error {
#[error("{0}")] #[error("{0}")]
Mlua(#[from] mlua::Error), Mlua(#[from] mlua::Error),
#[error("unimplemented: {0}")] #[error("unimplemented: {0}")]
Unimplemented(String) Unimplemented(String),
#[error("Error calling internal reflection type")]
Reflect,
} }
/* impl Into<mlua::Error> for Error { /* impl Into<mlua::Error> for Error {
@ -92,6 +93,16 @@ pub const FN_NAME_INTERNAL_REFLECT_TYPE: &str = "__lyra_internal_reflect_type";
/// method to return data**. /// method to return data**.
pub const FN_NAME_INTERNAL_REFLECT: &str = "__lyra_internal_reflect"; 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.\
/// Any other value will be included in the result.
pub const FN_NAME_INTERNAL_ECS_QUERY_RESULT: &str = "__lyra_internal_ecs_query_result";
/// Name of a Lua function implemented for Userdata types that can be made into components. /// Name of a Lua function implemented for Userdata types that can be made into components.
/// ///
/// This is used for types that can be converted into components. When implementing this function, /// This is used for types that can be converted into components. When implementing this function,

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::{wrappers::*, LuaContext, LuaWrapper, RegisterLuaType, View, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE}, ScriptApiProvider, ScriptBorrow, ScriptData, ScriptDynamicBundle, ScriptWorldPtr}; 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};
//fn register_lua_proxy::<T: //fn register_lua_proxy::<T:
@ -35,6 +35,8 @@ impl ScriptApiProvider for LyraEcsApiProvider {
// load enums // load enums
let bytes = include_str!("../../../scripts/lua/enums.lua"); let bytes = include_str!("../../../scripts/lua/enums.lua");
ctx.load(bytes).exec().unwrap(); ctx.load(bytes).exec().unwrap();
let bytes = include_str!("../../../scripts/lua/ecs.lua");
ctx.load(bytes).exec().unwrap();
let globals = ctx.globals(); let globals = ctx.globals();
globals.set("World", ctx.create_proxy::<ScriptWorldPtr>()?)?; globals.set("World", ctx.create_proxy::<ScriptWorldPtr>()?)?;
@ -43,6 +45,7 @@ impl ScriptApiProvider for LyraEcsApiProvider {
globals.set("ActionHandler", ctx.create_proxy::<LuaActionHandler>()?)?; globals.set("ActionHandler", ctx.create_proxy::<LuaActionHandler>()?)?;
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>()?)?;
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

@ -10,7 +10,7 @@ use lyra_resource::ResourceManager;
use mlua::{IntoLua, ObjectLike}; use mlua::{IntoLua, ObjectLike};
use super::{ use super::{
reflect_user_data, wrappers::{LuaResHandleToComponent, LuaWrappedEventProxy}, Error, ReflectLuaProxy, ReflectedIterator, TypeLookup, View, ViewResult, FN_NAME_INTERNAL_AS_COMPONENT, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE reflect_user_data, wrappers::{LuaResHandleToComponent, LuaWrappedEventProxy}, Error, ReflectLuaProxy, ReflectedIterator, TypeLookup, ecs::{View, ViewResult}, FN_NAME_INTERNAL_AS_COMPONENT, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE
}; };
impl mlua::FromLua for ScriptEntity { impl mlua::FromLua for ScriptEntity {