lua: implement world:view_one for lua

This commit is contained in:
SeanOMik 2024-10-29 21:56:07 -04:00
parent 964c4ec423
commit 0e613bd216
Signed by: SeanOMik
GPG Key ID: FEC9E2FC15235964
7 changed files with 280 additions and 42 deletions

View File

@ -1,4 +1,5 @@
local is_window_setup = false
local cube_entity = nil
---Return the userdata's name from its metatable.
---
@ -40,6 +41,7 @@ function on_init()
local pos = Transform.from_translation(Vec3.new(0, 0, -8.0))
local e = world:spawn(pos, cube_scene)
cube_entity = e
print("spawned entity " .. tostring(e))
end
@ -100,6 +102,14 @@ function on_update()
for _, tick in tick_res:iter() do
print("Entity transform last changed on tick " .. tostring(tick))
end
local pos_view = View.new(Transform)
local vone = world:view_one(cube_entity, pos_view)
local r = vone()
if r ~= nil then
local pos = r[1]
print("Found cube entity at '" .. tostring(pos) .. "'")
end
end
--[[ function on_post_update()

View File

@ -1,3 +1,5 @@
use std::ops::{Deref, DerefMut};
use crate::{query::Fetch, Entity, World};
use super::{DynamicType, FetchDynamicTypeUnsafe, QueryDynamicType};
@ -9,16 +11,28 @@ use super::{DynamicType, FetchDynamicTypeUnsafe, QueryDynamicType};
/// since Rust doesn't actually need to know the types of what its iterating over.
pub struct DynamicViewOne<'a> {
world: &'a World,
pub entity: Entity,
pub queries: Vec<QueryDynamicType>
inner: DynamicViewOneOwned,
}
impl<'a> Deref for DynamicViewOne<'a> {
type Target = DynamicViewOneOwned;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<'a> DerefMut for DynamicViewOne<'a> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
impl<'a> DynamicViewOne<'a> {
pub fn new(world: &'a World, entity: Entity) -> Self {
Self {
world,
entity,
queries: vec![],
inner: DynamicViewOneOwned::new(entity)
}
}
@ -26,36 +40,67 @@ impl<'a> DynamicViewOne<'a> {
pub fn new_with(world: &'a World, entity: Entity, queries: Vec<QueryDynamicType>) -> Self {
Self {
world,
inner: DynamicViewOneOwned::new_with(entity, queries)
}
}
pub fn get(self) -> Option<Vec<DynamicType>> {
self.inner.get(&self.world)
}
}
/// A variant of [`DynamicViewOne`] that doesn't store a borrow of the world.
#[derive(Clone)]
pub struct DynamicViewOneOwned {
pub entity: Entity,
pub queries: Vec<QueryDynamicType>
}
impl DynamicViewOneOwned {
pub fn new(entity: Entity) -> Self {
Self {
entity,
queries: vec![],
}
}
/// Create a new [`DynamicViewOne`] with queries.
pub fn new_with(entity: Entity, queries: Vec<QueryDynamicType>) -> Self {
Self {
entity,
queries
}
}
pub fn get(self) -> Option<Vec<DynamicType>> {
let arch = self.world.entity_archetype(self.entity)?;
let aid = arch.entity_indexes().get(&self.entity)?;
// get all fetchers for the queries
let mut fetchers: Vec<FetchDynamicTypeUnsafe> = self.queries.iter()
.map(|q| unsafe { q.fetch(self.world, arch.id(), arch) } )
.collect();
pub fn get(self, world: &World) -> Option<Vec<DynamicType>> {
dynamic_view_one_get_impl(world, &self.queries, self.entity)
}
}
let mut fetch_res = vec![];
for fetcher in fetchers.iter_mut() {
if !fetcher.can_visit_item(*aid) {
return None;
} else {
let i = unsafe { fetcher.get_item(*aid) };
fetch_res.push(i);
}
}
if fetch_res.is_empty() {
None
fn dynamic_view_one_get_impl(world: &World, queries: &Vec<QueryDynamicType>, entity: Entity) -> Option<Vec<DynamicType>> {
let arch = world.entity_archetype(entity)?;
let aid = arch.entity_indexes().get(&entity)?;
// get all fetchers for the queries
let mut fetchers: Vec<FetchDynamicTypeUnsafe> = queries.iter()
.map(|q| unsafe { q.fetch(world, arch.id(), arch) } )
.collect();
let mut fetch_res = vec![];
for fetcher in fetchers.iter_mut() {
if !fetcher.can_visit_item(*aid) {
return None;
} else {
Some(fetch_res)
let i = unsafe { fetcher.get_item(*aid) };
fetch_res.push(i);
}
}
if fetch_res.is_empty() {
None
} else {
Some(fetch_res)
}
}
#[cfg(test)]

View File

@ -10,7 +10,6 @@ use super::ReflectLuaProxy;
#[cfg(feature = "lua")]
pub struct ReflectedItem {
//pub proxy: &'a ReflectLuaProxy,
pub comp_ptr: NonNull<u8>,
pub comp_val: mlua::Value,
}
@ -24,7 +23,6 @@ pub struct ReflectedRow {
pub struct ReflectedIteratorOwned {
pub world_ptr: ScriptWorldPtr,
pub dyn_view: DynamicViewStateIter,
//pub reflected_components: Option<NonNull<TypeRegistry>>
}
impl ReflectedIteratorOwned {

View File

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

View File

@ -18,7 +18,7 @@ use crate::{
use super::query::{LuaQuery, LuaQueryResult};
#[derive(Clone)]
enum ViewQueryItem {
pub(crate) enum ViewQueryItem {
UserData(mlua::AnyUserData),
Table(mlua::Table),
Function(mlua::Function),
@ -76,7 +76,7 @@ impl ViewQueryItem {
#[derive(Clone)]
pub struct View {
items: Vec<ViewQueryItem>,
pub(crate) items: Vec<ViewQueryItem>,
}
impl mlua::FromLua for View {
@ -104,11 +104,15 @@ impl mlua::UserData for View {
}
}
/// Results of queries in a View.
///
/// Represents the results of multiple queries.
#[derive(Debug, Clone)]
enum QueryRowResult {
pub(crate) enum ViewQueryResult {
None,
AlwaysNone,
FilterDeny,
/// The results of the queries and the index they should be inserted at in the resulting row.
Some(Vec<(mlua::Value, u32)>),
}
@ -211,7 +215,7 @@ impl ViewResult {
///
/// 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<QueryRowResult> {
fn get_query_results(&self, entity: Entity) -> mlua::Result<ViewQueryResult> {
let mut query_vals = vec![];
// A modifier is used that will be incremented every time a filter allowed the query.
@ -221,13 +225,13 @@ impl ViewResult {
let qres = query.get_query_result(self.world.clone(), entity)?;
match qres {
LuaQueryResult::None => return Ok(QueryRowResult::None),
LuaQueryResult::AlwaysNone => return Ok(QueryRowResult::AlwaysNone),
LuaQueryResult::None => return Ok(ViewQueryResult::None),
LuaQueryResult::AlwaysNone => return Ok(ViewQueryResult::AlwaysNone),
LuaQueryResult::FilterPass => {
// do not push a boolean to values, its considered a filter
index_mod += 1;
},
LuaQueryResult::FilterDeny => return Ok(QueryRowResult::FilterDeny),
LuaQueryResult::FilterDeny => return Ok(ViewQueryResult::FilterDeny),
LuaQueryResult::Some(value) => {
let idx = (*i - index_mod).max(0);
query_vals.push((value, idx));
@ -235,7 +239,7 @@ impl ViewResult {
}
}
Ok(QueryRowResult::Some(query_vals))
Ok(ViewQueryResult::Some(query_vals))
}
}
@ -261,11 +265,11 @@ impl mlua::UserData for ViewResult {
Some((en, mut vals)) => {
loop {
let query_vals = match this.get_query_results(en)? {
QueryRowResult::Some(v) => v,
QueryRowResult::AlwaysNone => {
ViewQueryResult::Some(v) => v,
ViewQueryResult::AlwaysNone => {
return mlua::Value::Nil.into_lua_multi(lua);
},
QueryRowResult::None | QueryRowResult::FilterDeny => {
ViewQueryResult::None | ViewQueryResult::FilterDeny => {
// try to get it next loop
continue;
},
@ -302,11 +306,11 @@ impl mlua::UserData for ViewResult {
LuaEntityRef::new(this.world.clone(), en).into_lua(lua)?;
let query_vals = match this.get_query_results(en)? {
QueryRowResult::Some(v) => v,
QueryRowResult::AlwaysNone => {
ViewQueryResult::Some(v) => v,
ViewQueryResult::AlwaysNone => {
return mlua::Value::Nil.into_lua_multi(lua);
},
QueryRowResult::None | QueryRowResult::FilterDeny => {
ViewQueryResult::None | ViewQueryResult::FilterDeny => {
// try to get it next loop
continue;
},

View File

@ -0,0 +1,175 @@
use std::sync::Arc;
use lyra_ecs::{query::dynamic::{DynamicViewOneOwned, QueryDynamicType}, Entity};
use lyra_reflect::TypeRegistry;
use mlua::{IntoLua, IntoLuaMulti, ObjectLike};
use crate::{lua::{ReflectLuaProxy, TypeLookup, WorldError, FN_NAME_INTERNAL_REFLECT_TYPE}, ScriptBorrow, ScriptWorldPtr};
use super::{query::{LuaQuery, LuaQueryResult}, View, ViewQueryItem, ViewQueryResult};
/// The result of an ecs world View of a single entity.
#[derive(Clone)]
pub struct ViewOneResult {
world: ScriptWorldPtr,
dynamic_view: DynamicViewOneOwned,
/// The queries and the index they would be inserted in the result.
queries: Vec<(LuaQuery, u32)>,
}
impl mlua::FromLua for ViewOneResult {
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: "View".into(),
message: None,
})
.and_then(|ud| ud.borrow::<Self>())
.map(|ud| ud.clone())
}
}
impl ViewOneResult {
pub fn new(world: ScriptWorldPtr, entity: Entity, view: &View) -> Result<Self, mlua::Error> {
let items = view.items.clone();
let w = world.read();
let mut view = DynamicViewOneOwned::new(entity);
let mut queries = vec![];
for (idx, comp) in items.iter().enumerate() {
if comp.is_query()? {
queries.push((comp.as_query(), idx as u32));
continue;
}
match comp {
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 info = lookup.comp_info_from_name.get(&name).ok_or_else(|| {
mlua::Error::BadArgument {
to: Some("ViewOneResult.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),
))),
}
})?;
let dyn_type = QueryDynamicType::from_info(info.clone());
view.queries.push(dyn_type);
}
ViewQueryItem::UserData(ud) => {
let reflect = ud
.call_function::<ScriptBorrow>(FN_NAME_INTERNAL_REFLECT_TYPE, ())
.expect("Type does not implement 'reflect_type' properly");
let refl_comp = reflect.reflect_branch.as_component()
.expect("`self` is not an instance of `ReflectBranch::Component`");
let dyn_type = QueryDynamicType::from_info(refl_comp.info);
view.queries.push(dyn_type);
}
// functions are queries, the if statement at the start would cause this to
// be unreachable.
ViewQueryItem::Function(_) => unreachable!()
}
}
drop(w);
Ok(Self {
world,
dynamic_view: view,
queries,
})
}
/// 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<ViewQueryResult> {
let mut query_vals = vec![];
// A modifier is used that will be incremented every time a filter allowed the query.
// this is used to remove the value of a filter without leaving a gap in the results.
let mut index_mod = 0;
for (query, i) in &self.queries {
let qres = query.get_query_result(self.world.clone(), entity)?;
match qres {
LuaQueryResult::None => return Ok(ViewQueryResult::None),
LuaQueryResult::AlwaysNone => return Ok(ViewQueryResult::AlwaysNone),
LuaQueryResult::FilterPass => {
// do not push a boolean to values, its considered a filter
index_mod += 1;
},
LuaQueryResult::FilterDeny => return Ok(ViewQueryResult::FilterDeny),
LuaQueryResult::Some(value) => {
let idx = (*i - index_mod).max(0);
query_vals.push((value, idx));
},
}
}
Ok(ViewQueryResult::Some(query_vals))
}
fn get_res_impl(&self, lua: &mlua::Lua) -> mlua::Result<mlua::MultiValue> {
let world = self.world.read();
let qresults = self.get_query_results(self.dynamic_view.entity)?;
let qvals = match qresults {
ViewQueryResult::None => return mlua::Value::Nil.into_lua_multi(lua),
ViewQueryResult::AlwaysNone => return mlua::Value::Nil.into_lua_multi(lua),
ViewQueryResult::FilterDeny => return mlua::Value::Nil.into_lua_multi(lua),
ViewQueryResult::Some(vec) => vec,
};
let dv = self.dynamic_view.clone();
if let Some(row) = dv.get(&world) {
let reg = world.get_resource::<TypeRegistry>().unwrap();
let mut vals = vec![];
for d in row.iter() {
let id = d.info.type_id().as_rust();
let reg_type = reg.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.as_lua(lua, d.ptr.cast()).unwrap()
.into_lua(lua).unwrap();
vals.push(value);
}
// insert query values to the result row
for (v, i) in qvals {
vals.insert(i as _, v);
}
vals.into_lua_multi(lua)
} else {
mlua::Value::Nil.into_lua_multi(lua)
}
}
}
impl mlua::UserData for ViewOneResult {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
methods.add_method_mut("get", |lua, this, ()| {
this.get_res_impl(lua)
});
methods.add_meta_method(mlua::MetaMethod::Call, |lua, this, ()| {
this.get_res_impl(lua)
});
}
}

View File

@ -10,7 +10,7 @@ use lyra_resource::ResourceManager;
use mlua::{IntoLua, ObjectLike};
use super::{
ecs::{View, ViewResult}, reflect_user_data, wrappers::{LuaResHandleToComponent, LuaTick, LuaWrappedEventProxy}, Error, ReflectLuaProxy, ReflectedIterator, TypeLookup, FN_NAME_INTERNAL_AS_COMPONENT, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE
ecs::{View, ViewOneResult, ViewResult}, reflect_user_data, wrappers::{LuaResHandleToComponent, LuaTick, LuaWrappedEventProxy}, Error, ReflectLuaProxy, ReflectedIterator, TypeLookup, FN_NAME_INTERNAL_AS_COMPONENT, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE
};
impl mlua::FromLua for ScriptEntity {
@ -356,5 +356,8 @@ impl mlua::UserData for ScriptWorldPtr {
let w = this.read();
Ok(LuaTick(w.current_tick()))
});
methods.add_method("view_one", |_, this, (entity, view): (ScriptEntity, mlua::UserDataRef<View>)| {
ViewOneResult::new(this.clone(), *entity, &view)
});
}
}