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
3 changed files with 49 additions and 196 deletions
Showing only changes of commit fae2cdfadc - Show all commits

View File

@ -47,19 +47,20 @@ end
function on_first()
if not is_window_setup then
world:view(
---@param w Window
function (w)
if w.cursor_grab == CursorGrabMode.NONE then
w.cursor_grab = CursorGrabMode.LOCKED
w.cursor_visible = false
return w
else
is_window_setup = true
print("Window setup")
end
end, Window
)
local view = View.new(Window)
local res = world:view(view)
---@param w Window
for en, w in res:iter() do
if w.cursor_grab == CursorGrabMode.NONE then
w.cursor_grab = CursorGrabMode.LOCKED
w.cursor_visible = false
en:update(w)
else
is_window_setup = true
print("Window setup")
end
end
end
---@type EventReader
@ -81,7 +82,7 @@ end ]]
function on_update()
-- Get entities without WorldTransform
local view = View.new(Transform, Not(Has(WorldTransform)), Res(DeltaTime))
local res = world:view_query(view)
local res = world:view(view)
---@param transform Transform
---@param dt DeltaTime
for entity, transform, dt in res:iter() do
@ -90,14 +91,14 @@ function on_update()
end
local changed_view = View.new(Changed(Transform))
local changed_res = world:view_query(changed_view)
local changed_res = world:view(changed_view)
---@param transform Transform
for _, transform in changed_res:iter() do
print("Entity transform changed to: '" .. tostring(transform) .. "' on tick " .. tostring(world:get_tick()))
end
local tick_view = View.new(TickOf(Transform))
local tick_res = world:view_query(tick_view)
local tick_res = world:view(tick_view)
---@param tick number
for _, tick in tick_res:iter() do
print("Entity transform last changed on tick " .. tostring(tick))

View File

@ -9,35 +9,6 @@ World = {}
---@return Entity
function World:spawn(...) end
--- Query components from the world.
---
--- The `system` parameter is a function with the requested components. The function
--- is ran every time for an entity. If you modify a component and want the changes to be
--- stored, return it in the function. The order of the returned components do not matter.
---
--- Example:
--- ```lua
--- ---@type number
--- local dt = world:resource(DeltaTime)
---
--- world:view(
--- ---@param t Transform
--- function (t)
--- -- Move the transform of the entity a bit
--- t:translate(0, 0.15 * dt, 0)
--- -- Since the transform was modified, it must be returned so
--- -- the engine can store the changes.
--- return t
--- end,
--- -- Specify the requested components here
--- Transform
--- )
--- ```
---
---@param system fun(...): ...
---@param ... userdata
function World:view(system, ...) end
---Get an ECS resource.
---
---Returns `nil` if the resource was not found in the world. Many resources will
@ -91,9 +62,24 @@ function World:read_event(event) end
---View the world using the queries contained in a View.
---
---Example:
---```lua
----- Get entities without WorldTransform
---local view = View.new(Transform, Not(Has(WorldTransform)), Res(DeltaTime))
---local res = world:view_query(view)
------@param transform Transform
------@param dt DeltaTime
---for entity, transform, dt in res:iter() do
--- transform:translate(0, 0.15 * dt, 0)
--- entity:update(transform)
---end
---```
---
---@see View
---@see ViewResult
---@param view View
---@return ViewResult
function World:view_query(view) end
function World:view(view) end
---View a single entity in the world.
---

View File

@ -1,16 +1,16 @@
use std::{ops::DerefMut, ptr::NonNull, sync::Arc};
use std::{ops::DerefMut, sync::Arc};
use crate::{ScriptBorrow, ScriptEntity, ScriptWorldPtr};
use lyra_ecs::{
query::dynamic::{DynamicViewState, DynamicViewStateIter, QueryDynamicType},
CommandQueue, Commands, DynamicBundle, World,
};
use lyra_ecs::{CommandQueue, Commands, DynamicBundle, World};
use lyra_reflect::{ReflectWorldExt, RegisteredType, TypeRegistry};
use lyra_resource::ResourceManager;
use mlua::{IntoLua, ObjectLike};
use super::{
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
ecs::{View, ViewOneResult, ViewResult},
wrappers::{LuaResHandleToComponent, LuaTick, LuaWrappedEventProxy},
Error, ReflectLuaProxy, TypeLookup, FN_NAME_INTERNAL_AS_COMPONENT, FN_NAME_INTERNAL_REFLECT,
FN_NAME_INTERNAL_REFLECT_TYPE,
};
impl mlua::FromLua for ScriptEntity {
@ -98,145 +98,6 @@ impl mlua::UserData for ScriptWorldPtr {
Ok(ScriptEntity(entity))
});
methods.add_method_mut(
"view",
|lua, this, (system, queries): (mlua::Function, mlua::MultiValue)| {
if queries.is_empty() {
return Err(mlua::Error::BadArgument {
to: Some("World:view".into()),
pos: 2,
name: Some("query...".into()),
cause: Arc::new(mlua::Error::external(WorldError::LuaInvalidUsage(
"no component types provided".into(),
))),
});
}
let world = this.read();
let mut view = DynamicViewState::new();
for (idx, comp) in queries.into_iter().enumerate() {
match comp {
mlua::Value::Table(t) => {
let name: String = t.get(mlua::MetaMethod::Type.name())?;
let lookup =
world
.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("World:view".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.push(dyn_type);
}
mlua::Value::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_unchecked();
let dyn_type = QueryDynamicType::from_info(refl_comp.info);
view.push(dyn_type);
}
_ => todo!(),
}
}
let iter = view.into_iter();
let mut reflected_iter = ReflectedIterator {
// SAFETY: bypassing the borrow checker here to get a pointer of the world
// is required since we mutably borrow below. Its safe to do so since
// only the entity ticks are updated. They are accessing different things
// from the world.
world: unsafe { NonNull::from(&*world).as_ref() },
dyn_view: DynamicViewStateIter::from(iter),
};
let current = world.current_tick();
// drop read lock and acquire the write lock.
// dropping must be done to avoid mutex deadlock
drop(world);
let mut world = this.write();
while let Some(row) = reflected_iter.next_lua(lua) {
let r = row
.row
.into_iter()
.into_iter()
.map(|r| (r.comp_val, r.comp_ptr.cast::<()>()))
.collect::<Vec<_>>();
let (values, ptrs) =
itertools::multiunzip::<(Vec<mlua::Value>, Vec<NonNull<()>>), _>(r);
let mult_val = mlua::MultiValue::from_iter(values.into_iter());
let res: mlua::MultiValue = system.call(mult_val)?;
// if values were returned, find the type in the type registry, and apply the new values
if res.len() <= ptrs.len() {
for (comp, ptr) in res.into_iter().zip(ptrs) {
let lua_typeid = match &comp {
mlua::Value::UserData(ud) => {
let lua_comp = reflect_user_data(ud);
let refl_comp =
lua_comp.reflect_branch.as_component_unchecked();
refl_comp.info.type_id().as_rust()
}
mlua::Value::Table(tbl) => {
let name: String = tbl.get(mlua::MetaMethod::Type.name())?;
let lookup = world.get_resource::<TypeLookup>().unwrap();
*lookup.typeid_from_name.get(&name).unwrap()
}
_ => {
panic!("A userdata or table value was not returned!");
// TODO: Handle properly
}
};
// update the component tick
let arch = world.entity_archetype_mut(row.entity).unwrap();
let idx = arch.entity_indexes().get(&row.entity).unwrap().clone();
let c = arch.get_column_mut(lua_typeid).unwrap();
c.entity_ticks[idx.0 as usize] = current;
// apply the new component data
let reg = world.get_resource::<TypeRegistry>().unwrap();
let reg_type = reg.get_type(lua_typeid).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.apply(lua, ptr, &comp)?;
}
} else {
let msg = format!(
"Too many arguments were returned from the World view!
At most, the expected number of results is {}.",
ptrs.len()
);
return Err(mlua::Error::runtime(msg));
}
}
Ok(())
},
);
methods.add_method_mut("resource", |lua, this, (ty,): (mlua::Value,)| {
let reflect = match ty {
mlua::Value::UserData(ud) => ud
@ -262,7 +123,9 @@ impl mlua::UserData for ScriptWorldPtr {
.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))
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)
@ -349,15 +212,18 @@ impl mlua::UserData for ScriptWorldPtr {
data.reader(&mut world).into_lua(lua)
},
);
methods.add_method("view_query", |_, this, view: mlua::UserDataRef<View>| {
methods.add_method("view", |_, this, view: mlua::UserDataRef<View>| {
ViewResult::new(this.clone(), &view)
});
methods.add_method("get_tick", |_, this, ()| {
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)
});
methods.add_method(
"view_one",
|_, this, (entity, view): (ScriptEntity, mlua::UserDataRef<View>)| {
ViewOneResult::new(this.clone(), *entity, &view)
},
);
}
}