lua: implement ecs queries that work with the new Views
This commit is contained in:
parent
2ffdd4085b
commit
380b15e560
|
@ -90,37 +90,15 @@ function on_update()
|
|||
end
|
||||
end, Transform) ]]
|
||||
|
||||
---@type number
|
||||
local dt = world:resource(DeltaTime)
|
||||
|
||||
local view = View.new(Transform, WorldTransform)
|
||||
local view = View.new(Transform, WorldTransform, Res(DeltaTime))
|
||||
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))
|
||||
|
||||
transform:translate(0, 0.15 * dt, 0)
|
||||
entity:update(transform)
|
||||
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
|
||||
|
||||
--[[ function on_post_update()
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
---Create a Resource Query
|
||||
---@param resource table|userdata
|
||||
---@return ResQuery
|
||||
function Res(resource)
|
||||
return ResQuery.new(resource)
|
||||
end
|
|
@ -0,0 +1,4 @@
|
|||
mod view;
|
||||
pub use view::*;
|
||||
|
||||
pub mod query;
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,12 +1,13 @@
|
|||
use std::{cell::RefCell, ptr::NonNull, rc::Rc, sync::Arc};
|
||||
|
||||
use lyra_ecs::{query::dynamic::{DynamicViewState, QueryDynamicType}, Entity};
|
||||
use lyra_reflect::List;
|
||||
use mlua::{FromLuaMulti, IntoLua, IntoLuaMulti, ObjectLike};
|
||||
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)]
|
||||
enum ViewQueryItem {
|
||||
|
@ -37,6 +38,34 @@ impl ViewQueryItem {
|
|||
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>> {
|
||||
match self {
|
||||
|
||||
|
@ -77,7 +106,9 @@ impl mlua::UserData for View {
|
|||
pub struct ViewResult {
|
||||
world: ScriptWorldPtr,
|
||||
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>>>,
|
||||
}
|
||||
|
||||
|
@ -88,8 +119,15 @@ impl ViewResult {
|
|||
let items = view.items.clone();
|
||||
let w = world.read();
|
||||
let mut view = DynamicViewState::new();
|
||||
let mut queries = vec![];
|
||||
|
||||
for (idx, comp) in items.iter().enumerate() {
|
||||
if comp.is_query()? {
|
||||
// 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 {
|
||||
ViewQueryItem::Table(t) => {
|
||||
let name: String = t.get(mlua::MetaMethod::Type.name())?;
|
||||
|
@ -141,13 +179,14 @@ impl ViewResult {
|
|||
Ok(Self {
|
||||
world,
|
||||
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)),
|
||||
})
|
||||
}
|
||||
|
||||
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) {
|
||||
let r = row
|
||||
.row
|
||||
|
@ -196,12 +235,38 @@ impl mlua::UserData for ViewResult {
|
|||
|
||||
if let Some(key) = key_mut.as_ref() {
|
||||
let mut this = lua.registry_value::<Self>(&key)?;
|
||||
let v = this.next(lua)?;
|
||||
|
||||
match v {
|
||||
|
||||
let mut query_vals = vec![];
|
||||
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)) => {
|
||||
let lua_en = LuaEntityRef::new(this.world, en)
|
||||
.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);
|
||||
Ok(vals)
|
||||
},
|
|
@ -2,11 +2,11 @@ use std::{any::TypeId, sync::Arc};
|
|||
|
||||
use lyra_ecs::{Entity, World};
|
||||
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)]
|
||||
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
|
||||
|
|
|
@ -18,8 +18,7 @@ pub mod wrappers;
|
|||
pub mod proxy;
|
||||
pub use proxy::*;
|
||||
|
||||
mod view;
|
||||
pub use view::*;
|
||||
pub mod ecs;
|
||||
|
||||
mod entity_ref;
|
||||
pub use entity_ref::*;
|
||||
|
@ -48,7 +47,9 @@ pub enum Error {
|
|||
#[error("{0}")]
|
||||
Mlua(#[from] mlua::Error),
|
||||
#[error("unimplemented: {0}")]
|
||||
Unimplemented(String)
|
||||
Unimplemented(String),
|
||||
#[error("Error calling internal reflection type")]
|
||||
Reflect,
|
||||
}
|
||||
|
||||
/* impl Into<mlua::Error> for Error {
|
||||
|
@ -92,6 +93,16 @@ pub const FN_NAME_INTERNAL_REFLECT_TYPE: &str = "__lyra_internal_reflect_type";
|
|||
/// method to return data**.
|
||||
pub const FN_NAME_INTERNAL_REFLECT: &str = "__lyra_internal_reflect";
|
||||
|
||||
/// Name of a Lua function to retrieve the query result from a Userdata, or Table.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// This is used for types that can be converted into components. When implementing this function,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use lyra_ecs::ResourceObject;
|
||||
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:
|
||||
|
||||
|
@ -35,6 +35,8 @@ impl ScriptApiProvider for LyraEcsApiProvider {
|
|||
// load enums
|
||||
let bytes = include_str!("../../../scripts/lua/enums.lua");
|
||||
ctx.load(bytes).exec().unwrap();
|
||||
let bytes = include_str!("../../../scripts/lua/ecs.lua");
|
||||
ctx.load(bytes).exec().unwrap();
|
||||
|
||||
let globals = ctx.globals();
|
||||
globals.set("World", ctx.create_proxy::<ScriptWorldPtr>()?)?;
|
||||
|
@ -43,6 +45,7 @@ impl ScriptApiProvider for LyraEcsApiProvider {
|
|||
globals.set("ActionHandler", ctx.create_proxy::<LuaActionHandler>()?)?;
|
||||
globals.set("Window", ctx.create_proxy::<LuaWindow>()?)?;
|
||||
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::<LuaFreeFlyCamera>(&ctx, &globals, "FreeFlyCamera")?;
|
||||
|
|
|
@ -10,7 +10,7 @@ use lyra_resource::ResourceManager;
|
|||
use mlua::{IntoLua, ObjectLike};
|
||||
|
||||
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 {
|
||||
|
|
Loading…
Reference in New Issue