Improve Lua ECS #30
|
@ -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))
|
||||
|
|
|
@ -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.
|
||||
---
|
||||
|
|
|
@ -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)
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue