Merge pull request 'Improve Lua ECS' (#30) from feat/improve-lua-ecs-29 into main
CI / build (push) Failing after 3m52s
Details
CI / build (push) Failing after 3m52s
Details
Reviewed-on: #30
This commit is contained in:
commit
7ae0eae6ac
|
@ -1,4 +1,5 @@
|
||||||
local is_window_setup = false
|
local is_window_setup = false
|
||||||
|
local cube_entity = nil
|
||||||
|
|
||||||
---Return the userdata's name from its metatable.
|
---Return the userdata's name from its metatable.
|
||||||
---
|
---
|
||||||
|
@ -40,24 +41,26 @@ function on_init()
|
||||||
local pos = Transform.from_translation(Vec3.new(0, 0, -8.0))
|
local pos = Transform.from_translation(Vec3.new(0, 0, -8.0))
|
||||||
|
|
||||||
local e = world:spawn(pos, cube_scene)
|
local e = world:spawn(pos, cube_scene)
|
||||||
|
cube_entity = e
|
||||||
print("spawned entity " .. tostring(e))
|
print("spawned entity " .. tostring(e))
|
||||||
end
|
end
|
||||||
|
|
||||||
function on_first()
|
function on_first()
|
||||||
if not is_window_setup then
|
if not is_window_setup then
|
||||||
world:view(
|
local view = View.new(Window)
|
||||||
---@param w Window
|
local res = world:view(view)
|
||||||
function (w)
|
|
||||||
if w.cursor_grab == CursorGrabMode.NONE then
|
---@param w Window
|
||||||
w.cursor_grab = CursorGrabMode.LOCKED
|
for en, w in res:iter() do
|
||||||
w.cursor_visible = false
|
if w.cursor_grab == CursorGrabMode.NONE then
|
||||||
return w
|
w.cursor_grab = CursorGrabMode.LOCKED
|
||||||
else
|
w.cursor_visible = false
|
||||||
is_window_setup = true
|
en:update(w)
|
||||||
print("Window setup")
|
else
|
||||||
end
|
is_window_setup = true
|
||||||
end, Window
|
print("Window setup")
|
||||||
)
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@type EventReader
|
---@type EventReader
|
||||||
|
@ -77,42 +80,38 @@ end
|
||||||
end ]]
|
end ]]
|
||||||
|
|
||||||
function on_update()
|
function on_update()
|
||||||
--[[ ---@type number
|
-- Get entities without WorldTransform
|
||||||
local dt = world:resource(DeltaTime)
|
local view = View.new(Transform, Not(Has(WorldTransform)), Res(DeltaTime))
|
||||||
local act = world:resource(ActionHandler)
|
local res = world:view(view)
|
||||||
---@type number
|
---@param transform Transform
|
||||||
local move_objs = act:get_axis("ObjectsMoveUpDown")
|
---@param dt DeltaTime
|
||||||
|
for entity, transform, dt in res:iter() do
|
||||||
|
transform:translate(0, 0.15 * dt, 0)
|
||||||
|
entity:update(transform)
|
||||||
|
end
|
||||||
|
|
||||||
world:view(function (t)
|
local changed_view = View.new(Changed(Transform))
|
||||||
if move_objs ~= nil then
|
local changed_res = world:view(changed_view)
|
||||||
t:translate(0, move_objs * 0.35 * dt, 0)
|
---@param transform Transform
|
||||||
return t
|
for _, transform in changed_res:iter() do
|
||||||
end
|
print("Entity transform changed to: '" .. tostring(transform) .. "' on tick " .. tostring(world:get_tick()))
|
||||||
end, Transform) ]]
|
end
|
||||||
|
|
||||||
---@type number
|
local tick_view = View.new(TickOf(Transform))
|
||||||
local dt = world:resource(DeltaTime)
|
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))
|
||||||
|
end
|
||||||
|
|
||||||
world:view(
|
local pos_view = View.new(Transform)
|
||||||
---@param t Transform
|
local vone = world:view_one(cube_entity --[[@as Entity]], pos_view)
|
||||||
---@param wt WorldTransform
|
local r = vone() -- short hand for 'vone:get()'
|
||||||
function (t, wt)
|
if r then
|
||||||
print("Entity is at: " .. tostring(wt))
|
---@type Transform
|
||||||
t:translate(0, 0.15 * dt, 0)
|
local pos = r[1]
|
||||||
return t
|
print("Found cube entity at '" .. tostring(pos) .. "'")
|
||||||
end, Transform, WorldTransform
|
end
|
||||||
)
|
|
||||||
|
|
||||||
--[[ 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()
|
||||||
|
|
|
@ -1,6 +1,16 @@
|
||||||
use std::{ptr::{NonNull, self}, alloc::{self, Layout, alloc, dealloc}, mem, collections::HashMap, cell::{RefCell, Ref, RefMut, BorrowError, BorrowMutError}, ops::DerefMut};
|
use std::{
|
||||||
|
alloc::{self, alloc, dealloc, Layout},
|
||||||
|
cell::{BorrowError, BorrowMutError, Ref, RefCell, RefMut},
|
||||||
|
collections::HashMap,
|
||||||
|
mem,
|
||||||
|
ops::DerefMut,
|
||||||
|
ptr::{self, NonNull},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{bundle::Bundle, component_info::ComponentInfo, world::ArchetypeEntityId, DynTypeId, Entity, Tick};
|
use crate::{
|
||||||
|
bundle::Bundle, component_info::ComponentInfo, world::ArchetypeEntityId, DynTypeId, Entity,
|
||||||
|
Tick,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ComponentColumn {
|
pub struct ComponentColumn {
|
||||||
|
@ -32,8 +42,9 @@ impl ComponentColumn {
|
||||||
pub unsafe fn alloc(component_layout: Layout, capacity: usize) -> NonNull<u8> {
|
pub unsafe fn alloc(component_layout: Layout, capacity: usize) -> NonNull<u8> {
|
||||||
let new_layout = Layout::from_size_align(
|
let new_layout = Layout::from_size_align(
|
||||||
component_layout.size().checked_mul(capacity).unwrap(),
|
component_layout.size().checked_mul(capacity).unwrap(),
|
||||||
component_layout.align()
|
component_layout.align(),
|
||||||
).unwrap();
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
if let Some(data) = NonNull::new(alloc(new_layout)) {
|
if let Some(data) = NonNull::new(alloc(new_layout)) {
|
||||||
data
|
data
|
||||||
|
@ -44,7 +55,7 @@ impl ComponentColumn {
|
||||||
|
|
||||||
pub unsafe fn new(info: ComponentInfo, capacity: usize) -> Self {
|
pub unsafe fn new(info: ComponentInfo, capacity: usize) -> Self {
|
||||||
let data = ComponentColumn::alloc(info.layout(), capacity);
|
let data = ComponentColumn::alloc(info.layout(), capacity);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
data: RefCell::new(data),
|
data: RefCell::new(data),
|
||||||
capacity,
|
capacity,
|
||||||
|
@ -55,7 +66,7 @@ impl ComponentColumn {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a component from pointer at an entity index.
|
/// Set a component from pointer at an entity index.
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// This column must have space to fit the component, if it does not have room it will panic.
|
/// This column must have space to fit the component, if it does not have room it will panic.
|
||||||
pub unsafe fn set_at(&mut self, entity_index: usize, comp_src: NonNull<u8>, tick: Tick) {
|
pub unsafe fn set_at(&mut self, entity_index: usize, comp_src: NonNull<u8>, tick: Tick) {
|
||||||
|
@ -63,7 +74,7 @@ impl ComponentColumn {
|
||||||
|
|
||||||
let mut data = self.data.borrow_mut();
|
let mut data = self.data.borrow_mut();
|
||||||
let data = data.deref_mut();
|
let data = data.deref_mut();
|
||||||
|
|
||||||
let size = self.info.layout().size();
|
let size = self.info.layout().size();
|
||||||
let dest = NonNull::new_unchecked(data.as_ptr().add(entity_index * size));
|
let dest = NonNull::new_unchecked(data.as_ptr().add(entity_index * size));
|
||||||
ptr::copy_nonoverlapping(comp_src.as_ptr(), dest.as_ptr(), size);
|
ptr::copy_nonoverlapping(comp_src.as_ptr(), dest.as_ptr(), size);
|
||||||
|
@ -83,7 +94,7 @@ impl ComponentColumn {
|
||||||
|
|
||||||
let mut data = self.data.borrow_mut();
|
let mut data = self.data.borrow_mut();
|
||||||
let data = data.deref_mut();
|
let data = data.deref_mut();
|
||||||
|
|
||||||
let size = self.info.layout().size();
|
let size = self.info.layout().size();
|
||||||
let dest = NonNull::new_unchecked(data.as_ptr().add(entity_index * size));
|
let dest = NonNull::new_unchecked(data.as_ptr().add(entity_index * size));
|
||||||
ptr::copy_nonoverlapping(comp_src.as_ptr(), dest.as_ptr(), size);
|
ptr::copy_nonoverlapping(comp_src.as_ptr(), dest.as_ptr(), size);
|
||||||
|
@ -95,25 +106,25 @@ impl ComponentColumn {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a component at an entities index.
|
/// Get a component at an entities index.
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
///
|
///
|
||||||
/// This column MUST have the entity. If it does not, it WILL NOT panic and will cause UB.
|
/// This column MUST have the entity. If it does not, it WILL NOT panic and will cause UB.
|
||||||
pub unsafe fn get<T>(&self, entity_index: usize) -> Ref<T> {
|
pub unsafe fn get<T>(&self, entity_index: usize) -> Ref<T> {
|
||||||
let data = self.data.borrow();
|
let data = self.data.borrow();
|
||||||
|
|
||||||
Ref::map(data, |data| {
|
Ref::map(data, |data| {
|
||||||
let ptr = NonNull::new_unchecked(data.as_ptr()
|
let ptr =
|
||||||
.add(entity_index * self.info.layout().size()))
|
NonNull::new_unchecked(data.as_ptr().add(entity_index * self.info.layout().size()))
|
||||||
.cast();
|
.cast();
|
||||||
&*ptr.as_ptr()
|
&*ptr.as_ptr()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a mutable borrow to the component at an entities index, ticking the entity.
|
/// Get a mutable borrow to the component at an entities index, ticking the entity.
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
///
|
///
|
||||||
/// This column must have the entity.
|
/// This column must have the entity.
|
||||||
pub unsafe fn get_mut<T>(&mut self, entity_index: usize, tick: &Tick) -> RefMut<T> {
|
pub unsafe fn get_mut<T>(&mut self, entity_index: usize, tick: &Tick) -> RefMut<T> {
|
||||||
self.entity_ticks[entity_index].tick_to(tick);
|
self.entity_ticks[entity_index].tick_to(tick);
|
||||||
|
@ -121,22 +132,22 @@ impl ComponentColumn {
|
||||||
let data = self.data.borrow_mut();
|
let data = self.data.borrow_mut();
|
||||||
|
|
||||||
RefMut::map(data, |data| {
|
RefMut::map(data, |data| {
|
||||||
let ptr = NonNull::new_unchecked(data.as_ptr()
|
let ptr =
|
||||||
.add(entity_index * self.info.layout().size()))
|
NonNull::new_unchecked(data.as_ptr().add(entity_index * self.info.layout().size()))
|
||||||
.cast();
|
.cast();
|
||||||
&mut *ptr.as_ptr()
|
&mut *ptr.as_ptr()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Grow the column to fit `new_capacity` amount of components.
|
/// Grow the column to fit `new_capacity` amount of components.
|
||||||
///
|
///
|
||||||
/// Parameters:
|
/// Parameters:
|
||||||
/// * `new_capacity` - The new capacity of components that can fit in this column.
|
/// * `new_capacity` - The new capacity of components that can fit in this column.
|
||||||
///
|
///
|
||||||
/// Note: This does not modify the Tick of this column, since no components were actually modified.
|
/// Note: This does not modify the Tick of this column, since no components were actually modified.
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
///
|
///
|
||||||
/// Will panic if `new_capacity` is less than the current capacity of the column.
|
/// Will panic if `new_capacity` is less than the current capacity of the column.
|
||||||
pub unsafe fn grow(&mut self, new_capacity: usize) {
|
pub unsafe fn grow(&mut self, new_capacity: usize) {
|
||||||
assert!(new_capacity > self.capacity);
|
assert!(new_capacity > self.capacity);
|
||||||
|
@ -155,7 +166,7 @@ impl ComponentColumn {
|
||||||
// create a layout with the same alignment, but expand the size of the buffer.
|
// create a layout with the same alignment, but expand the size of the buffer.
|
||||||
let old_layout = Layout::from_size_align_unchecked(
|
let old_layout = Layout::from_size_align_unchecked(
|
||||||
layout.size().checked_mul(self.capacity).unwrap(),
|
layout.size().checked_mul(self.capacity).unwrap(),
|
||||||
layout.align()
|
layout.align(),
|
||||||
);
|
);
|
||||||
|
|
||||||
mem::swap(data.deref_mut(), &mut new_ptr);
|
mem::swap(data.deref_mut(), &mut new_ptr);
|
||||||
|
@ -163,7 +174,7 @@ impl ComponentColumn {
|
||||||
} else {
|
} else {
|
||||||
*data = new_ptr;
|
*data = new_ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.capacity = new_capacity;
|
self.capacity = new_capacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,19 +182,20 @@ impl ComponentColumn {
|
||||||
pub unsafe fn remove_component(&mut self, entity_index: usize, tick: &Tick) -> Option<usize> {
|
pub unsafe fn remove_component(&mut self, entity_index: usize, tick: &Tick) -> Option<usize> {
|
||||||
let _ = tick; // may be used at some point
|
let _ = tick; // may be used at some point
|
||||||
|
|
||||||
debug_assert!(self.len > 0, "There are no entities in the Archetype to remove from!");
|
debug_assert!(
|
||||||
|
self.len > 0,
|
||||||
|
"There are no entities in the Archetype to remove from!"
|
||||||
|
);
|
||||||
|
|
||||||
let mut data = self.data.borrow_mut();
|
let mut data = self.data.borrow_mut();
|
||||||
let data = data.deref_mut();
|
let data = data.deref_mut();
|
||||||
|
|
||||||
let size = self.info.layout().size();
|
let size = self.info.layout().size();
|
||||||
let mut old_comp_ptr = NonNull::new_unchecked(data.as_ptr()
|
let mut old_comp_ptr = NonNull::new_unchecked(data.as_ptr().add(entity_index * size));
|
||||||
.add(entity_index * size));
|
|
||||||
|
|
||||||
let moved_index = if entity_index != self.len - 1 {
|
let moved_index = if entity_index != self.len - 1 {
|
||||||
let moved_index = self.len - 1;
|
let moved_index = self.len - 1;
|
||||||
let mut new_comp_ptr = NonNull::new_unchecked(data.as_ptr()
|
let mut new_comp_ptr = NonNull::new_unchecked(data.as_ptr().add(moved_index * size));
|
||||||
.add(moved_index * size));
|
|
||||||
|
|
||||||
ptr::copy_nonoverlapping(new_comp_ptr.as_ptr(), old_comp_ptr.as_ptr(), size);
|
ptr::copy_nonoverlapping(new_comp_ptr.as_ptr(), old_comp_ptr.as_ptr(), size);
|
||||||
|
|
||||||
|
@ -193,13 +205,43 @@ impl ComponentColumn {
|
||||||
self.entity_ticks.swap_remove(entity_index);
|
self.entity_ticks.swap_remove(entity_index);
|
||||||
|
|
||||||
Some(moved_index)
|
Some(moved_index)
|
||||||
} else { None };
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
self.len -= 1;
|
self.len -= 1;
|
||||||
|
|
||||||
moved_index
|
moved_index
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the pointer of the component for an entity.
|
||||||
|
///
|
||||||
|
/// It is assumed that the component will be mutated, meaning the component's tick will be
|
||||||
|
/// updated.
|
||||||
|
pub fn component_ptr(&mut self, entity_index: usize, tick: &Tick) -> NonNull<u8> {
|
||||||
|
self.entity_ticks[entity_index] = *tick;
|
||||||
|
let size = self.info.layout().size();
|
||||||
|
unsafe { NonNull::new_unchecked(self.borrow_ptr().as_ptr().add(entity_index * size)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the pointer of the component for an entity without ticking.
|
||||||
|
///
|
||||||
|
/// Since this does not tick, only use this if you know the pointer will not be mutated.
|
||||||
|
pub fn component_ptr_non_tick(&self, entity_index: usize) -> NonNull<u8> {
|
||||||
|
let size = self.info.layout().size();
|
||||||
|
unsafe { NonNull::new_unchecked(self.borrow_ptr().as_ptr().add(entity_index * size)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the tick of a component for an entity.
|
||||||
|
pub fn component_tick(&self, entity_index: usize) -> Option<Tick> {
|
||||||
|
self.entity_ticks.get(entity_index).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn component_has_changed(&self, entity_index: usize, world_tick: Tick) -> Option<bool> {
|
||||||
|
self.component_tick(entity_index)
|
||||||
|
.map(|tick| *tick >= *world_tick - 1)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn borrow_ptr(&self) -> Ref<NonNull<u8>> {
|
pub fn borrow_ptr(&self) -> Ref<NonNull<u8>> {
|
||||||
self.data.borrow()
|
self.data.borrow()
|
||||||
}
|
}
|
||||||
|
@ -226,13 +268,13 @@ impl ArchetypeId {
|
||||||
pub(crate) fn increment(&mut self) -> Self {
|
pub(crate) fn increment(&mut self) -> Self {
|
||||||
let v = self.0;
|
let v = self.0;
|
||||||
self.0 += 1;
|
self.0 += 1;
|
||||||
|
|
||||||
ArchetypeId(v)
|
ArchetypeId(v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stores a group of entities with matching components.
|
/// Stores a group of entities with matching components.
|
||||||
///
|
///
|
||||||
/// An Archetype can be thought of as a table, with entities as the rows and the entity's
|
/// An Archetype can be thought of as a table, with entities as the rows and the entity's
|
||||||
/// components as each column. This means you can have tightly packed components of entities and
|
/// components as each column. This means you can have tightly packed components of entities and
|
||||||
/// quickly iterate through entities with the same components.
|
/// quickly iterate through entities with the same components.
|
||||||
|
@ -248,7 +290,7 @@ pub struct Archetype {
|
||||||
/// Can be used to map `ArchetypeEntityId` to an Entity since `ArchetypeEntityId` has
|
/// Can be used to map `ArchetypeEntityId` to an Entity since `ArchetypeEntityId` has
|
||||||
/// the index that the entity is stored at.
|
/// the index that the entity is stored at.
|
||||||
pub(crate) entities: Vec<Entity>,
|
pub(crate) entities: Vec<Entity>,
|
||||||
pub(crate) columns: Vec<ComponentColumn>,
|
pub columns: Vec<ComponentColumn>,
|
||||||
capacity: usize,
|
capacity: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,9 +309,10 @@ impl Archetype {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_bundle_info(new_id: ArchetypeId, bundle_info: Vec<ComponentInfo>) -> Archetype {
|
pub fn from_bundle_info(new_id: ArchetypeId, bundle_info: Vec<ComponentInfo>) -> Archetype {
|
||||||
let columns = bundle_info.into_iter().map(|i| {
|
let columns = bundle_info
|
||||||
unsafe { ComponentColumn::new(i, DEFAULT_CAPACITY) }
|
.into_iter()
|
||||||
}).collect();
|
.map(|i| unsafe { ComponentColumn::new(i, DEFAULT_CAPACITY) })
|
||||||
|
.collect();
|
||||||
|
|
||||||
Archetype {
|
Archetype {
|
||||||
id: new_id,
|
id: new_id,
|
||||||
|
@ -281,13 +324,18 @@ impl Archetype {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add an entity and its component bundle to the Archetype
|
/// Add an entity and its component bundle to the Archetype
|
||||||
///
|
///
|
||||||
/// # Safety:
|
/// # Safety:
|
||||||
///
|
///
|
||||||
/// Archetype must contain all of the components
|
/// Archetype must contain all of the components
|
||||||
pub(crate) fn add_entity<B>(&mut self, entity: Entity, bundle: B, tick: &Tick) -> ArchetypeEntityId
|
pub(crate) fn add_entity<B>(
|
||||||
|
&mut self,
|
||||||
|
entity: Entity,
|
||||||
|
bundle: B,
|
||||||
|
tick: &Tick,
|
||||||
|
) -> ArchetypeEntityId
|
||||||
where
|
where
|
||||||
B: Bundle
|
B: Bundle,
|
||||||
{
|
{
|
||||||
if self.capacity == self.entity_ids.len() {
|
if self.capacity == self.entity_ids.len() {
|
||||||
let new_cap = self.capacity * 2;
|
let new_cap = self.capacity * 2;
|
||||||
|
@ -301,28 +349,49 @@ impl Archetype {
|
||||||
self.entities.push(entity);
|
self.entities.push(entity);
|
||||||
|
|
||||||
bundle.take(|data, type_id, info| {
|
bundle.take(|data, type_id, info| {
|
||||||
self.put_component_at(tick, data, type_id, info.layout().size(), entity_index.0 as _);
|
self.put_component_at(
|
||||||
|
tick,
|
||||||
|
data,
|
||||||
|
type_id,
|
||||||
|
info.layout().size(),
|
||||||
|
entity_index.0 as _,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
entity_index
|
entity_index
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn put_component_at(&mut self, tick: &Tick, ptr: NonNull<u8>, type_id: DynTypeId, size: usize, index: usize) {
|
pub(crate) fn put_component_at(
|
||||||
|
&mut self,
|
||||||
|
tick: &Tick,
|
||||||
|
ptr: NonNull<u8>,
|
||||||
|
type_id: DynTypeId,
|
||||||
|
size: usize,
|
||||||
|
index: usize,
|
||||||
|
) {
|
||||||
let _ = size;
|
let _ = size;
|
||||||
|
|
||||||
let col = self.get_column_mut(type_id).unwrap();
|
let col = self.get_column_mut(type_id).unwrap();
|
||||||
//unsafe { col.set_at(index, ptr, *tick) };
|
//unsafe { col.set_at(index, ptr, *tick) };
|
||||||
unsafe { col.insert_entity(index, ptr, *tick); }
|
unsafe {
|
||||||
|
col.insert_entity(index, ptr, *tick);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes an entity from the Archetype and frees its components.
|
/// Removes an entity from the Archetype and frees its components.
|
||||||
///
|
///
|
||||||
/// Inside the component columns, the entities are swap-removed. Meaning that the last
|
/// Inside the component columns, the entities are swap-removed. Meaning that the last
|
||||||
/// entity in the column is moved in the position of the entity that was removed.
|
/// entity in the column is moved in the position of the entity that was removed.
|
||||||
/// If there was an entity that was swapped, this function returns the entity, and its
|
/// If there was an entity that was swapped, this function returns the entity, and its
|
||||||
/// new index in the archetype that was put in place of the removed entity.
|
/// new index in the archetype that was put in place of the removed entity.
|
||||||
pub(crate) fn remove_entity(&mut self, entity: Entity, tick: &Tick) -> Option<(Entity, ArchetypeEntityId)> {
|
pub(crate) fn remove_entity(
|
||||||
let entity_index = self.entity_ids.remove(&entity)
|
&mut self,
|
||||||
|
entity: Entity,
|
||||||
|
tick: &Tick,
|
||||||
|
) -> Option<(Entity, ArchetypeEntityId)> {
|
||||||
|
let entity_index = self
|
||||||
|
.entity_ids
|
||||||
|
.remove(&entity)
|
||||||
.expect("The entity is not in this Archetype!");
|
.expect("The entity is not in this Archetype!");
|
||||||
let mut removed_entity: Option<(Entity, ArchetypeEntityId)> = None;
|
let mut removed_entity: Option<(Entity, ArchetypeEntityId)> = None;
|
||||||
|
|
||||||
|
@ -339,19 +408,19 @@ impl Archetype {
|
||||||
removed_entity = Some((just_removed, ArchetypeEntityId(moved_idx as u64)));
|
removed_entity = Some((just_removed, ArchetypeEntityId(moved_idx as u64)));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If there wasn't a moved entity, make sure no other columns moved something.
|
// If there wasn't a moved entity, make sure no other columns moved something.
|
||||||
assert!(removed_entity.is_none());
|
assert!(removed_entity.is_none());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// safe from the .expect at the start of this method.
|
// safe from the .expect at the start of this method.
|
||||||
//self.entity_ids.remove(&entity).unwrap();
|
//self.entity_ids.remove(&entity).unwrap();
|
||||||
|
|
||||||
// update the archetype index of the moved entity
|
// update the archetype index of the moved entity
|
||||||
if let Some((moved, _old_idx)) = removed_entity {
|
if let Some((moved, _old_idx)) = removed_entity {
|
||||||
self.entity_ids.insert(moved, entity_index);
|
self.entity_ids.insert(moved, entity_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
let removed = self.entities.swap_remove(entity_index.0 as _);
|
let removed = self.entities.swap_remove(entity_index.0 as _);
|
||||||
assert_eq!(removed, entity);
|
assert_eq!(removed, entity);
|
||||||
|
|
||||||
|
@ -362,8 +431,12 @@ impl Archetype {
|
||||||
/// Returns a boolean indicating whether this archetype can store the TypeIds given
|
/// Returns a boolean indicating whether this archetype can store the TypeIds given
|
||||||
pub fn is_archetype_for(&self, types: &Vec<DynTypeId>) -> bool {
|
pub fn is_archetype_for(&self, types: &Vec<DynTypeId>) -> bool {
|
||||||
if types.len() == self.columns.len() {
|
if types.len() == self.columns.len() {
|
||||||
self.columns.iter().all(|c| types.contains(&c.info.type_id()))
|
self.columns
|
||||||
} else { false }
|
.iter()
|
||||||
|
.all(|c| types.contains(&c.info.type_id()))
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a boolean indicating whether this archetype has a column for `comp_type`
|
/// Returns a boolean indicating whether this archetype has a column for `comp_type`
|
||||||
|
@ -383,18 +456,20 @@ impl Archetype {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Grows columns in the archetype
|
/// Grows columns in the archetype
|
||||||
///
|
///
|
||||||
/// Parameters:
|
/// Parameters:
|
||||||
/// * `new_capacity` - The new capacity of components that can fit in this column.
|
/// * `new_capacity` - The new capacity of components that can fit in this column.
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
///
|
///
|
||||||
/// Will panic if new_capacity is less than the current capacity
|
/// Will panic if new_capacity is less than the current capacity
|
||||||
fn grow_columns(&mut self, new_capacity: usize) {
|
fn grow_columns(&mut self, new_capacity: usize) {
|
||||||
assert!(new_capacity > self.capacity);
|
assert!(new_capacity > self.capacity);
|
||||||
|
|
||||||
for c in self.columns.iter_mut() {
|
for c in self.columns.iter_mut() {
|
||||||
unsafe { c.grow(new_capacity); }
|
unsafe {
|
||||||
|
c.grow(new_capacity);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.capacity = new_capacity;
|
self.capacity = new_capacity;
|
||||||
|
@ -412,11 +487,16 @@ impl Archetype {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a mutable borrow to a component column for `type_id`.
|
/// Returns a mutable borrow to a component column for `type_id`.
|
||||||
///
|
///
|
||||||
/// Note: This does not modify the tick for the column!
|
/// Note: This does not modify the tick for the column!
|
||||||
pub fn get_column_mut<I: Into<DynTypeId>>(&mut self, type_id: I) -> Option<&mut ComponentColumn> {
|
pub fn get_column_mut<I: Into<DynTypeId>>(
|
||||||
|
&mut self,
|
||||||
|
type_id: I,
|
||||||
|
) -> Option<&mut ComponentColumn> {
|
||||||
let type_id = type_id.into();
|
let type_id = type_id.into();
|
||||||
self.columns.iter_mut().find(|c| c.info.type_id() == type_id)
|
self.columns
|
||||||
|
.iter_mut()
|
||||||
|
.find(|c| c.info.type_id() == type_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reserves a slot in the columns for an entity and returns the index of that reserved spot
|
/// Reserves a slot in the columns for an entity and returns the index of that reserved spot
|
||||||
|
@ -427,8 +507,11 @@ impl Archetype {
|
||||||
self.capacity = new_cap;
|
self.capacity = new_cap;
|
||||||
}
|
}
|
||||||
|
|
||||||
debug_assert_eq!(self.entity_ids.len(), self.entities.len(),
|
debug_assert_eq!(
|
||||||
"Somehow the Archetype's entity storage got unsynced");
|
self.entity_ids.len(),
|
||||||
|
self.entities.len(),
|
||||||
|
"Somehow the Archetype's entity storage got unsynced"
|
||||||
|
);
|
||||||
let entity_index = ArchetypeEntityId(self.entity_ids.len() as u64);
|
let entity_index = ArchetypeEntityId(self.entity_ids.len() as u64);
|
||||||
self.entity_ids.insert(entity, entity_index);
|
self.entity_ids.insert(entity, entity_index);
|
||||||
self.entities.push(entity);
|
self.entities.push(entity);
|
||||||
|
@ -442,12 +525,15 @@ impl Archetype {
|
||||||
|
|
||||||
/// Ensure that the internal entity lists are synced in length
|
/// Ensure that the internal entity lists are synced in length
|
||||||
pub(crate) fn ensure_synced(&self) {
|
pub(crate) fn ensure_synced(&self) {
|
||||||
debug_assert_eq!(self.entity_ids.len(), self.entities.len(),
|
debug_assert_eq!(
|
||||||
"Somehow the Archetype's entity storage got unsynced");
|
self.entity_ids.len(),
|
||||||
|
self.entities.len(),
|
||||||
|
"Somehow the Archetype's entity storage got unsynced"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Moves the entity from this archetype into another one.
|
/// Moves the entity from this archetype into another one.
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// The entity IS NOT removed from the old archetype. You must manually call [`Archetype::remove_entity`](crate::Archetype).
|
/// The entity IS NOT removed from the old archetype. You must manually call [`Archetype::remove_entity`](crate::Archetype).
|
||||||
/// It was done this way because I had some borrow check issues when writing [`World::insert`](crate::World)
|
/// It was done this way because I had some borrow check issues when writing [`World::insert`](crate::World)
|
||||||
|
@ -463,7 +549,7 @@ impl Archetype {
|
||||||
// move the existing components into the new archetype
|
// move the existing components into the new archetype
|
||||||
for col in self.columns.iter() {
|
for col in self.columns.iter() {
|
||||||
let into_col = into_arch.get_column_mut(col.info.type_id()).unwrap();
|
let into_col = into_arch.get_column_mut(col.info.type_id()).unwrap();
|
||||||
|
|
||||||
// copy from the old column into the new column, then remove it from the old one
|
// copy from the old column into the new column, then remove it from the old one
|
||||||
unsafe {
|
unsafe {
|
||||||
let ptr = col.borrow_ptr();
|
let ptr = col.borrow_ptr();
|
||||||
|
@ -514,19 +600,20 @@ impl Archetype {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extend the Archetype by adding more columns.
|
/// Extend the Archetype by adding more columns.
|
||||||
///
|
///
|
||||||
/// In order to extend the Archetype, the archetype needs the components for the entities
|
/// In order to extend the Archetype, the archetype needs the components for the entities
|
||||||
/// it already has. These are provided through the `new_columns` parameter. **If the Vec
|
/// it already has. These are provided through the `new_columns` parameter. **If the Vec
|
||||||
/// does not have the same amount of bundles in it as the amount of entities in the
|
/// does not have the same amount of bundles in it as the amount of entities in the
|
||||||
/// Archetype, it will panic!**
|
/// Archetype, it will panic!**
|
||||||
pub fn extend<B: Bundle>(&mut self, tick: &Tick, new_columns: Vec<B>) {
|
pub fn extend<B: Bundle>(&mut self, tick: &Tick, new_columns: Vec<B>) {
|
||||||
debug_assert_eq!(new_columns.len(), self.len(), "The amount of provided column does not \
|
debug_assert_eq!(
|
||||||
match the amount of entities");
|
new_columns.len(),
|
||||||
|
self.len(),
|
||||||
let column_info = new_columns.iter()
|
"The amount of provided column does not \
|
||||||
.next()
|
match the amount of entities"
|
||||||
.unwrap()
|
);
|
||||||
.info();
|
|
||||||
|
let column_info = new_columns.iter().next().unwrap().info();
|
||||||
|
|
||||||
for coli in column_info.into_iter() {
|
for coli in column_info.into_iter() {
|
||||||
let col = unsafe { ComponentColumn::new(coli, self.capacity) };
|
let col = unsafe { ComponentColumn::new(coli, self.capacity) };
|
||||||
|
@ -534,11 +621,9 @@ impl Archetype {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (eid, bundle) in new_columns.into_iter().enumerate() {
|
for (eid, bundle) in new_columns.into_iter().enumerate() {
|
||||||
bundle.take(|ptr, tyid, _size| {
|
bundle.take(|ptr, tyid, _size| unsafe {
|
||||||
unsafe {
|
let col = self.get_column_mut(tyid).unwrap();
|
||||||
let col = self.get_column_mut(tyid).unwrap();
|
col.insert_entity(eid, ptr, tick.clone());
|
||||||
col.insert_entity(eid, ptr, tick.clone());
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -550,7 +635,11 @@ mod tests {
|
||||||
|
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
|
||||||
use crate::{bundle::Bundle, tests::{Vec2, Vec3}, ComponentInfo, DynTypeId, DynamicBundle, Entity, EntityId, Tick};
|
use crate::{
|
||||||
|
bundle::Bundle,
|
||||||
|
tests::{Vec2, Vec3},
|
||||||
|
ComponentInfo, DynTypeId, DynamicBundle, Entity, EntityId, Tick,
|
||||||
|
};
|
||||||
|
|
||||||
use super::Archetype;
|
use super::Archetype;
|
||||||
|
|
||||||
|
@ -559,7 +648,7 @@ mod tests {
|
||||||
let bundle = (Vec2::new(10.0, 20.0),);
|
let bundle = (Vec2::new(10.0, 20.0),);
|
||||||
let entity = Entity {
|
let entity = Entity {
|
||||||
id: EntityId(0),
|
id: EntityId(0),
|
||||||
generation: 0
|
generation: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut a = Archetype::from_bundle_info(super::ArchetypeId(0), bundle.info());
|
let mut a = Archetype::from_bundle_info(super::ArchetypeId(0), bundle.info());
|
||||||
|
@ -572,10 +661,10 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn one_entity_two_component() {
|
fn one_entity_two_component() {
|
||||||
let bundle = (Vec2::new(10.0, 20.0),Vec3::new(15.0, 54.0, 84.0));
|
let bundle = (Vec2::new(10.0, 20.0), Vec3::new(15.0, 54.0, 84.0));
|
||||||
let entity = Entity {
|
let entity = Entity {
|
||||||
id: EntityId(0),
|
id: EntityId(0),
|
||||||
generation: 0
|
generation: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut a = Archetype::from_bundle_info(super::ArchetypeId(0), bundle.info());
|
let mut a = Archetype::from_bundle_info(super::ArchetypeId(0), bundle.info());
|
||||||
|
@ -595,12 +684,12 @@ mod tests {
|
||||||
let b1 = (Vec2::new(10.0, 20.0),);
|
let b1 = (Vec2::new(10.0, 20.0),);
|
||||||
let e1 = Entity {
|
let e1 = Entity {
|
||||||
id: EntityId(0),
|
id: EntityId(0),
|
||||||
generation: 0
|
generation: 0,
|
||||||
};
|
};
|
||||||
let b2 = (Vec2::new(19.0, 43.0),);
|
let b2 = (Vec2::new(19.0, 43.0),);
|
||||||
let e2 = Entity {
|
let e2 = Entity {
|
||||||
id: EntityId(1),
|
id: EntityId(1),
|
||||||
generation: 0
|
generation: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut a = Archetype::from_bundle_info(super::ArchetypeId(0), b1.info());
|
let mut a = Archetype::from_bundle_info(super::ArchetypeId(0), b1.info());
|
||||||
|
@ -619,12 +708,12 @@ mod tests {
|
||||||
let b1 = (Vec2::new(10.0, 20.0), Vec3::new(84.0, 283.0, 28.0));
|
let b1 = (Vec2::new(10.0, 20.0), Vec3::new(84.0, 283.0, 28.0));
|
||||||
let e1 = Entity {
|
let e1 = Entity {
|
||||||
id: EntityId(0),
|
id: EntityId(0),
|
||||||
generation: 0
|
generation: 0,
|
||||||
};
|
};
|
||||||
let b2 = (Vec2::new(19.0, 43.0), Vec3::new(74.0, 28.0, 93.0));
|
let b2 = (Vec2::new(19.0, 43.0), Vec3::new(74.0, 28.0, 93.0));
|
||||||
let e2 = Entity {
|
let e2 = Entity {
|
||||||
id: EntityId(1),
|
id: EntityId(1),
|
||||||
generation: 0
|
generation: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut a = Archetype::from_bundle_info(super::ArchetypeId(0), b1.info());
|
let mut a = Archetype::from_bundle_info(super::ArchetypeId(0), b1.info());
|
||||||
|
@ -659,7 +748,7 @@ mod tests {
|
||||||
fn auto_archetype_growth() {
|
fn auto_archetype_growth() {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
let bundle_count = rng.gen_range(50..150);
|
let bundle_count = rng.gen_range(50..150);
|
||||||
|
|
||||||
let mut bundles: Vec<(Vec2,)> = vec![];
|
let mut bundles: Vec<(Vec2,)> = vec![];
|
||||||
bundles.reserve(bundle_count);
|
bundles.reserve(bundle_count);
|
||||||
|
|
||||||
|
@ -669,14 +758,14 @@ mod tests {
|
||||||
for i in 0..bundle_count {
|
for i in 0..bundle_count {
|
||||||
let c = (Vec2::rand(),);
|
let c = (Vec2::rand(),);
|
||||||
bundles.push(c);
|
bundles.push(c);
|
||||||
|
|
||||||
a.add_entity(
|
a.add_entity(
|
||||||
Entity {
|
Entity {
|
||||||
id: EntityId(i as u64),
|
id: EntityId(i as u64),
|
||||||
generation: 0
|
generation: 0,
|
||||||
},
|
},
|
||||||
c,
|
c,
|
||||||
&Tick::default()
|
&Tick::default(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
println!("Inserted {} entities", bundle_count);
|
println!("Inserted {} entities", bundle_count);
|
||||||
|
@ -701,25 +790,27 @@ mod tests {
|
||||||
a.add_entity(
|
a.add_entity(
|
||||||
Entity {
|
Entity {
|
||||||
id: EntityId(i as u64),
|
id: EntityId(i as u64),
|
||||||
generation: 0
|
generation: 0,
|
||||||
},
|
},
|
||||||
(bundles[i],),
|
(bundles[i],),
|
||||||
&Tick::default()
|
&Tick::default(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the 'middle' entity in the column
|
// Remove the 'middle' entity in the column
|
||||||
let moved_entity = a.remove_entity(
|
let moved_entity = a
|
||||||
Entity {
|
.remove_entity(
|
||||||
id: EntityId(1u64),
|
Entity {
|
||||||
generation: 0,
|
id: EntityId(1u64),
|
||||||
},
|
generation: 0,
|
||||||
&Tick::default()
|
},
|
||||||
).expect("No entity was moved");
|
&Tick::default(),
|
||||||
|
)
|
||||||
|
.expect("No entity was moved");
|
||||||
|
|
||||||
// The last entity in the column should have been moved
|
// The last entity in the column should have been moved
|
||||||
assert!(moved_entity.0.id.0 == 2);
|
assert!(moved_entity.0.id.0 == 2);
|
||||||
assert!(moved_entity.1.0 == 1);
|
assert!(moved_entity.1 .0 == 1);
|
||||||
|
|
||||||
// make sure that the entities' component was actually moved in the column
|
// make sure that the entities' component was actually moved in the column
|
||||||
let col = &a.columns[0];
|
let col = &a.columns[0];
|
||||||
|
@ -731,7 +822,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn dynamic_archetype() {
|
fn dynamic_archetype() {
|
||||||
let layout = Layout::new::<u32>();
|
let layout = Layout::new::<u32>();
|
||||||
let info = ComponentInfo::new_unknown(Some("u32".to_string()), DynTypeId::Unknown(100), layout);
|
let info =
|
||||||
|
ComponentInfo::new_unknown(Some("u32".to_string()), DynTypeId::Unknown(100), layout);
|
||||||
let infos = vec![info.clone()];
|
let infos = vec![info.clone()];
|
||||||
|
|
||||||
let mut a = Archetype::from_bundle_info(super::ArchetypeId(0), infos);
|
let mut a = Archetype::from_bundle_info(super::ArchetypeId(0), infos);
|
||||||
|
@ -744,10 +836,10 @@ mod tests {
|
||||||
a.add_entity(
|
a.add_entity(
|
||||||
Entity {
|
Entity {
|
||||||
id: EntityId(0),
|
id: EntityId(0),
|
||||||
generation: 0
|
generation: 0,
|
||||||
},
|
},
|
||||||
dynamic_bundle,
|
dynamic_bundle,
|
||||||
&Tick::default()
|
&Tick::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let col = a.columns.iter().next().unwrap();
|
let col = a.columns.iter().next().unwrap();
|
||||||
|
@ -764,15 +856,11 @@ mod tests {
|
||||||
|
|
||||||
let ae = Entity {
|
let ae = Entity {
|
||||||
id: EntityId(0),
|
id: EntityId(0),
|
||||||
generation: 0
|
generation: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
a.add_entity(
|
a.add_entity(ae, Vec2::new(10.0, 50.0), &Tick::default());
|
||||||
ae,
|
|
||||||
Vec2::new(10.0, 50.0),
|
|
||||||
&Tick::default()
|
|
||||||
);
|
|
||||||
|
|
||||||
a.remove_entity(ae, &Tick::default());
|
a.remove_entity(ae, &Tick::default());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,4 +77,4 @@ impl Entities {
|
||||||
pub(crate) fn insert_entity_record(&mut self, entity: Entity, record: Record) {
|
pub(crate) fn insert_entity_record(&mut self, entity: Entity, record: Record) {
|
||||||
self.arch_index.insert(entity.id, record);
|
self.arch_index.insert(entity.id, record);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -24,6 +24,10 @@ impl DynamicViewState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn queries_num(&self) -> usize {
|
||||||
|
self.queries.len()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn push(&mut self, dyn_query: QueryDynamicType) {
|
pub fn push(&mut self, dyn_query: QueryDynamicType) {
|
||||||
self.queries.push(dyn_query);
|
self.queries.push(dyn_query);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
use crate::{query::Fetch, Entity, World};
|
use crate::{query::Fetch, Entity, World};
|
||||||
|
|
||||||
use super::{DynamicType, FetchDynamicTypeUnsafe, QueryDynamicType};
|
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.
|
/// since Rust doesn't actually need to know the types of what its iterating over.
|
||||||
pub struct DynamicViewOne<'a> {
|
pub struct DynamicViewOne<'a> {
|
||||||
world: &'a World,
|
world: &'a World,
|
||||||
pub entity: Entity,
|
inner: DynamicViewOneOwned,
|
||||||
pub queries: Vec<QueryDynamicType>
|
}
|
||||||
|
|
||||||
|
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> {
|
impl<'a> DynamicViewOne<'a> {
|
||||||
pub fn new(world: &'a World, entity: Entity) -> Self {
|
pub fn new(world: &'a World, entity: Entity) -> Self {
|
||||||
Self {
|
Self {
|
||||||
world,
|
world,
|
||||||
entity,
|
inner: DynamicViewOneOwned::new(entity)
|
||||||
queries: vec![],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,36 +40,67 @@ impl<'a> DynamicViewOne<'a> {
|
||||||
pub fn new_with(world: &'a World, entity: Entity, queries: Vec<QueryDynamicType>) -> Self {
|
pub fn new_with(world: &'a World, entity: Entity, queries: Vec<QueryDynamicType>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
world,
|
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,
|
entity,
|
||||||
queries
|
queries
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(self) -> Option<Vec<DynamicType>> {
|
pub fn get(self, world: &World) -> Option<Vec<DynamicType>> {
|
||||||
let arch = self.world.entity_archetype(self.entity)?;
|
dynamic_view_one_get_impl(world, &self.queries, 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();
|
|
||||||
|
|
||||||
let mut fetch_res = vec![];
|
fn dynamic_view_one_get_impl(world: &World, queries: &Vec<QueryDynamicType>, entity: Entity) -> Option<Vec<DynamicType>> {
|
||||||
for fetcher in fetchers.iter_mut() {
|
let arch = world.entity_archetype(entity)?;
|
||||||
if !fetcher.can_visit_item(*aid) {
|
let aid = arch.entity_indexes().get(&entity)?;
|
||||||
return None;
|
|
||||||
} else {
|
// get all fetchers for the queries
|
||||||
let i = unsafe { fetcher.get_item(*aid) };
|
let mut fetchers: Vec<FetchDynamicTypeUnsafe> = queries.iter()
|
||||||
fetch_res.push(i);
|
.map(|q| unsafe { q.fetch(world, arch.id(), arch) } )
|
||||||
}
|
.collect();
|
||||||
}
|
|
||||||
|
let mut fetch_res = vec![];
|
||||||
if fetch_res.is_empty() {
|
for fetcher in fetchers.iter_mut() {
|
||||||
None
|
if !fetcher.can_visit_item(*aid) {
|
||||||
|
return None;
|
||||||
} else {
|
} 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)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -112,7 +112,7 @@ impl<'a, T: ResourceObject> Res<'a, T> {
|
||||||
|
|
||||||
/// Returns a boolean indicating if the resource changed.
|
/// Returns a boolean indicating if the resource changed.
|
||||||
pub fn changed(&self) -> bool {
|
pub fn changed(&self) -> bool {
|
||||||
*self.inner.tick - 1 >= *self.world_tick - 1
|
*self.inner.tick >= *self.world_tick - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The tick that this resource was last modified at
|
/// The tick that this resource was last modified at
|
||||||
|
|
|
@ -72,6 +72,12 @@ impl TickTracker {
|
||||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Default, PartialOrd, Ord)]
|
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Default, PartialOrd, Ord)]
|
||||||
pub struct Tick(u64);
|
pub struct Tick(u64);
|
||||||
|
|
||||||
|
impl From<u64> for Tick {
|
||||||
|
fn from(value: u64) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl std::ops::Deref for Tick {
|
impl std::ops::Deref for Tick {
|
||||||
type Target = u64;
|
type Target = u64;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{any::TypeId, collections::HashMap, ptr::NonNull};
|
use std::{any::TypeId, collections::HashMap, ops::Deref, ptr::NonNull};
|
||||||
|
|
||||||
use atomic_refcell::{AtomicRef, AtomicRefMut};
|
use atomic_refcell::{AtomicRef, AtomicRefMut};
|
||||||
|
|
||||||
|
@ -10,6 +10,14 @@ use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{dynamic
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct ArchetypeEntityId(pub u64);
|
pub struct ArchetypeEntityId(pub u64);
|
||||||
|
|
||||||
|
impl Deref for ArchetypeEntityId {
|
||||||
|
type Target = u64;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct Record {
|
pub struct Record {
|
||||||
pub id: ArchetypeId,
|
pub id: ArchetypeId,
|
||||||
|
@ -18,11 +26,11 @@ pub struct Record {
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct World {
|
pub struct World {
|
||||||
pub(crate) archetypes: HashMap<ArchetypeId, Archetype>,
|
pub archetypes: HashMap<ArchetypeId, Archetype>,
|
||||||
next_archetype_id: ArchetypeId,
|
next_archetype_id: ArchetypeId,
|
||||||
resources: HashMap<TypeId, ResourceData>,
|
resources: HashMap<TypeId, ResourceData>,
|
||||||
tracker: TickTracker,
|
tracker: TickTracker,
|
||||||
pub(crate) entities: Entities,
|
pub entities: Entities,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for World {
|
impl Default for World {
|
||||||
|
@ -459,6 +467,13 @@ impl World {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the tick of a resource.
|
||||||
|
///
|
||||||
|
/// This tick represents the last time the resource was mutated.
|
||||||
|
pub fn get_resource_tick<T: ResourceObject>(&self) -> Option<Tick> {
|
||||||
|
self.get_tracked_resource::<T>().map(|r| r.tick)
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets a reference to a change tracked resource.
|
/// Gets a reference to a change tracked resource.
|
||||||
///
|
///
|
||||||
/// You will have to manually downcast the inner resource. Most people don't need this, see
|
/// You will have to manually downcast the inner resource. Most people don't need this, see
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{any::{Any, TypeId}, cell::{Ref, RefMut}};
|
use std::{any::{Any, TypeId}, cell::{Ref, RefMut}};
|
||||||
|
|
||||||
use lyra_ecs::{Component, ComponentInfo, World, Entity, DynamicBundle};
|
use lyra_ecs::{query::{filter::Changed, TickOf}, Component, ComponentInfo, DynamicBundle, Entity, Tick, World};
|
||||||
|
|
||||||
use crate::{Reflect, FromType};
|
use crate::{Reflect, FromType};
|
||||||
|
|
||||||
|
@ -18,6 +18,8 @@ pub struct ReflectedComponent {
|
||||||
fn_bundle_insert: for<'a> fn (dynamic_bundle: &'a mut DynamicBundle, component: Box<dyn Reflect>),
|
fn_bundle_insert: for<'a> fn (dynamic_bundle: &'a mut DynamicBundle, component: Box<dyn Reflect>),
|
||||||
fn_reflect: for<'a> fn (world: &'a World, entity: Entity) -> Option<Ref<'a, dyn Reflect>>,
|
fn_reflect: for<'a> fn (world: &'a World, entity: Entity) -> Option<Ref<'a, dyn Reflect>>,
|
||||||
fn_reflect_mut: for<'a> fn (world: &'a mut World, entity: Entity) -> Option<RefMut<'a, dyn Reflect>>,
|
fn_reflect_mut: for<'a> fn (world: &'a mut World, entity: Entity) -> Option<RefMut<'a, dyn Reflect>>,
|
||||||
|
fn_reflect_tick: for<'a> fn (world: &'a World, entity: Entity) -> Option<Tick>,
|
||||||
|
fn_reflect_is_changed: for<'a> fn (world: &'a World, entity: Entity) -> Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReflectedComponent {
|
impl ReflectedComponent {
|
||||||
|
@ -40,6 +42,14 @@ impl ReflectedComponent {
|
||||||
pub fn reflect_mut<'a>(&'a mut self, world: &'a mut World, entity: Entity) -> Option<RefMut<'a, dyn Reflect>> {
|
pub fn reflect_mut<'a>(&'a mut self, world: &'a mut World, entity: Entity) -> Option<RefMut<'a, dyn Reflect>> {
|
||||||
(self.fn_reflect_mut)(world, entity)
|
(self.fn_reflect_mut)(world, entity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn reflect_tick<'a>(&'a self, world: &'a World, entity: Entity) -> Option<Tick> {
|
||||||
|
(self.fn_reflect_tick)(world, entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reflect_is_changed<'a>(&'a self, world: &'a World, entity: Entity) -> Option<bool> {
|
||||||
|
(self.fn_reflect_is_changed)(world, entity)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C: Component + Reflect> FromType<C> for ReflectedComponent {
|
impl<C: Component + Reflect> FromType<C> for ReflectedComponent {
|
||||||
|
@ -69,6 +79,12 @@ impl<C: Component + Reflect> FromType<C> for ReflectedComponent {
|
||||||
world.view_one::<&mut C>(entity)
|
world.view_one::<&mut C>(entity)
|
||||||
.get().map(|c| c as RefMut<dyn Reflect>)
|
.get().map(|c| c as RefMut<dyn Reflect>)
|
||||||
},
|
},
|
||||||
|
fn_reflect_tick: |world: &World, entity: Entity| {
|
||||||
|
world.view_one::<TickOf<C>>(entity).get()
|
||||||
|
},
|
||||||
|
fn_reflect_is_changed: |world: &World, entity: Entity| {
|
||||||
|
world.view_one::<Changed<C>>(entity).get()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{any::{Any, TypeId}, ptr::NonNull};
|
use std::{any::{Any, TypeId}, ptr::NonNull};
|
||||||
|
|
||||||
use lyra_ecs::{AtomicRef, AtomicRefMut, ResourceObject, World};
|
use lyra_ecs::{AtomicRef, AtomicRefMut, ResourceObject, Tick, World};
|
||||||
|
|
||||||
use crate::{Reflect, FromType};
|
use crate::{Reflect, FromType};
|
||||||
|
|
||||||
|
@ -9,6 +9,8 @@ pub struct ReflectedResource {
|
||||||
pub type_id: TypeId,
|
pub type_id: TypeId,
|
||||||
|
|
||||||
fn_reflect: for<'a> fn (world: &'a World) -> Option<AtomicRef<'a, dyn Reflect>>,
|
fn_reflect: for<'a> fn (world: &'a World) -> Option<AtomicRef<'a, dyn Reflect>>,
|
||||||
|
fn_reflect_tick: for<'a> fn (world: &'a World) -> Option<Tick>,
|
||||||
|
fn_reflect_is_changed: fn (world: &World) -> Option<bool>,
|
||||||
fn_reflect_mut: for<'a> fn (world: &'a mut World) -> Option<AtomicRefMut<'a, dyn Reflect>>,
|
fn_reflect_mut: for<'a> fn (world: &'a mut World) -> Option<AtomicRefMut<'a, dyn Reflect>>,
|
||||||
fn_reflect_ptr: fn (world: &mut World) -> Option<NonNull<u8>>,
|
fn_reflect_ptr: fn (world: &mut World) -> Option<NonNull<u8>>,
|
||||||
fn_refl_insert: fn (world: &mut World, this: Box<dyn Reflect>),
|
fn_refl_insert: fn (world: &mut World, this: Box<dyn Reflect>),
|
||||||
|
@ -20,6 +22,14 @@ impl ReflectedResource {
|
||||||
(self.fn_reflect)(world)
|
(self.fn_reflect)(world)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn reflect_tick(&self, world: &World) -> Option<Tick> {
|
||||||
|
(self.fn_reflect_tick)(world)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reflect_is_changed(&self, world: &World) -> Option<bool> {
|
||||||
|
(self.fn_reflect_is_changed)(world)
|
||||||
|
}
|
||||||
|
|
||||||
/// Retrieves a mutable reflected resource from the world.
|
/// Retrieves a mutable reflected resource from the world.
|
||||||
pub fn reflect_mut<'a>(&self, world: &'a mut World) -> Option<AtomicRefMut<'a, dyn Reflect>> {
|
pub fn reflect_mut<'a>(&self, world: &'a mut World) -> Option<AtomicRefMut<'a, dyn Reflect>> {
|
||||||
(self.fn_reflect_mut)(world)
|
(self.fn_reflect_mut)(world)
|
||||||
|
@ -47,6 +57,13 @@ impl<T: ResourceObject + Reflect> FromType<T> for ReflectedResource {
|
||||||
AtomicRef::map(r, |r| r as &dyn Reflect)
|
AtomicRef::map(r, |r| r as &dyn Reflect)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
fn_reflect_tick: |world: &World| {
|
||||||
|
world.get_resource_tick::<T>()
|
||||||
|
},
|
||||||
|
fn_reflect_is_changed: |world: &World| {
|
||||||
|
world.get_resource::<T>()
|
||||||
|
.map(|r| r.changed())
|
||||||
|
},
|
||||||
fn_reflect_mut: |world: &mut World| {
|
fn_reflect_mut: |world: &mut World| {
|
||||||
world.get_resource_mut::<T>()
|
world.get_resource_mut::<T>()
|
||||||
.map(|r| {
|
.map(|r| {
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
---Create a Resource query that will return the specific ECS world resource.
|
||||||
|
---
|
||||||
|
---@see ResQuery
|
||||||
|
---@param resource table|userdata
|
||||||
|
---@return ResQuery
|
||||||
|
function Res(resource)
|
||||||
|
return ResQuery.new(resource)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@alias Query function|table|userdata
|
||||||
|
|
||||||
|
---Create a `ChangedQuery` query that will return only if the resource or component has changed
|
||||||
|
---since last tick.
|
||||||
|
---
|
||||||
|
---@see ChangedQuery
|
||||||
|
---@param val table|userdata
|
||||||
|
---@return ChangedQuery
|
||||||
|
function Changed(val)
|
||||||
|
return ChangedQuery.new(val)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Create a `HasQuery` filter that will return only if the entity has a specific component.
|
||||||
|
---
|
||||||
|
---@see HasQuery
|
||||||
|
---@param val table|userdata
|
||||||
|
---@return HasQuery
|
||||||
|
function Has(val)
|
||||||
|
return HasQuery.new(val)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Create a `NotQuery` filter that will allow results if the query returns nothing or
|
||||||
|
---filter denies.
|
||||||
|
---
|
||||||
|
---@see NotQuery
|
||||||
|
---@param val Query
|
||||||
|
---@return NotQuery
|
||||||
|
function Not(val)
|
||||||
|
return NotQuery.new(val)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Create a `AnyQuery` filter that will allow results if any of the queries return something.
|
||||||
|
---
|
||||||
|
---The queries are evaluated in the order they were provided.
|
||||||
|
---
|
||||||
|
---@see AnyQuery
|
||||||
|
---@param ... Query
|
||||||
|
---@return AnyQuery
|
||||||
|
function Any(...)
|
||||||
|
return AnyQuery.new(...)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Create a `TickOfQuery` for retrieving the tick of the resource or component on the entity.
|
||||||
|
---
|
||||||
|
---@see TickOfQuery
|
||||||
|
---@param ... table|userdata
|
||||||
|
---@return TickOfQuery
|
||||||
|
function TickOf(...)
|
||||||
|
return TickOfQuery.new(...)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Create any `OptionalQuery` that allows for a query to return nothing.
|
||||||
|
---
|
||||||
|
---If the query is a filter, its result will essentially be ignored. If the query returns `None`
|
||||||
|
---or `AlwaysNone`, this query will return `Nil`. If the query results in a value, its value
|
||||||
|
---will be the result of this query.
|
||||||
|
---
|
||||||
|
---@see OptionalQuery
|
||||||
|
---@param q Query
|
||||||
|
---@return OptionalQuery
|
||||||
|
function Optional(q)
|
||||||
|
return OptionalQuery.new(q)
|
||||||
|
end
|
|
@ -1,4 +1,29 @@
|
||||||
---@meta
|
---@meta
|
||||||
|
|
||||||
|
---An entity handle.
|
||||||
---@class Entity: userdata
|
---@class Entity: userdata
|
||||||
Entity = {}
|
Entity = {}
|
||||||
|
|
||||||
|
---Get the id of the Entity.
|
||||||
|
---@return number
|
||||||
|
function Entity:id() end
|
||||||
|
|
||||||
|
---Get the generation number of the Entity.
|
||||||
|
---
|
||||||
|
---Entity handles are reused by the ECS World, the generation is used to tell reused Entity
|
||||||
|
---id's apart from previous generations.
|
||||||
|
---
|
||||||
|
---@return number
|
||||||
|
function Entity:generation() end
|
||||||
|
|
||||||
|
---A reference to an entity in the world.
|
||||||
|
---
|
||||||
|
---Can be used to insert and update components on the entity.
|
||||||
|
---
|
||||||
|
---@class EntityRef: userdata
|
||||||
|
EntityRef = {}
|
||||||
|
|
||||||
|
---Update components that are **already** on an Entity.
|
||||||
|
---
|
||||||
|
---@param ... any The components to update on the entity.
|
||||||
|
function EntityRef:update(...) end
|
|
@ -0,0 +1,9 @@
|
||||||
|
---@meta
|
||||||
|
|
||||||
|
---@class EventReader<T>: userdata
|
||||||
|
EventReader = {}
|
||||||
|
|
||||||
|
---Get an iterator for reading the event.
|
||||||
|
---@generic T
|
||||||
|
---@return fun(): T? iterator An iterator for reading the events.
|
||||||
|
function EventReader:read() end
|
|
@ -0,0 +1,9 @@
|
||||||
|
require "action_handler"
|
||||||
|
require "camera"
|
||||||
|
require "delta_time"
|
||||||
|
require "entity"
|
||||||
|
require "event_reader"
|
||||||
|
require "free_fly_camera"
|
||||||
|
require "window"
|
||||||
|
require "world_transform"
|
||||||
|
require "world"
|
|
@ -0,0 +1,25 @@
|
||||||
|
---@meta
|
||||||
|
|
||||||
|
---An ECS filter that will return if any of the provided queries return.
|
||||||
|
---
|
||||||
|
---The queries are evaluated in the order they were provided. When a query or filter returns a value,
|
||||||
|
---that value will be returned.
|
||||||
|
---
|
||||||
|
---Use the utility function `Any(...)` to create a new query since its faster to
|
||||||
|
---write than this.
|
||||||
|
---
|
||||||
|
---@see Any
|
||||||
|
---@class AnyQuery: userdata
|
||||||
|
AnyQuery = {}
|
||||||
|
|
||||||
|
---Create a new AnyQuery.
|
||||||
|
---
|
||||||
|
---Use the utility function `Any(...)` to create a new query since its faster to
|
||||||
|
---write than this.
|
||||||
|
---
|
||||||
|
---@see Any
|
||||||
|
---@param ... Query The query to invert.
|
||||||
|
function AnyQuery:new(...) end
|
||||||
|
|
||||||
|
---An internal function used by the engine to retrieve the query result.
|
||||||
|
function AnyQuery:__lyra_internal_ecs_query_result(world, entity) end
|
|
@ -0,0 +1,22 @@
|
||||||
|
---@meta
|
||||||
|
|
||||||
|
---An ECS query used for obtaining **changed** resources or components from the world.
|
||||||
|
---
|
||||||
|
---Use the utility function `Changed(...)` to create a new query since its faster to
|
||||||
|
---write than this.
|
||||||
|
---
|
||||||
|
---This query will not return if the resource or component has not changed since the last tick.
|
||||||
|
---
|
||||||
|
---@class ChangedQuery: userdata
|
||||||
|
ChangedQuery = {}
|
||||||
|
|
||||||
|
---Create a new ChangedQuery.
|
||||||
|
---
|
||||||
|
---Use the utility function `Changed(...)` to create a new query since its faster to
|
||||||
|
---write than this.
|
||||||
|
---
|
||||||
|
---@param val table|userdata The component or resource to detect changed of.
|
||||||
|
function ChangedQuery:new(val) end
|
||||||
|
|
||||||
|
---An internal function used by the engine to retrieve the query result.
|
||||||
|
function ChangedQuery:__lyra_internal_ecs_query_result(world, entity) end
|
|
@ -0,0 +1,22 @@
|
||||||
|
---@meta
|
||||||
|
|
||||||
|
---An ECS filter that allows the query if the entity has the Component.
|
||||||
|
---
|
||||||
|
---Use the utility function `Has(...)` to create a new query since its faster to
|
||||||
|
---write than this.
|
||||||
|
---
|
||||||
|
---@see Has
|
||||||
|
---@class HasQuery: userdata
|
||||||
|
HasQuery = {}
|
||||||
|
|
||||||
|
---Create a new HasQuery.
|
||||||
|
---
|
||||||
|
---Use the utility function `Has(...)` to create a new query since its faster to
|
||||||
|
---write than this.
|
||||||
|
---
|
||||||
|
---@see Has
|
||||||
|
---@param val table|userdata The component to look for on the entity.
|
||||||
|
function HasQuery:new(val) end
|
||||||
|
|
||||||
|
---An internal function used by the engine to retrieve the query result.
|
||||||
|
function HasQuery:__lyra_internal_ecs_query_result(world, entity) end
|
|
@ -0,0 +1,9 @@
|
||||||
|
require "view"
|
||||||
|
require "view_one"
|
||||||
|
require "changed"
|
||||||
|
require "res"
|
||||||
|
require "has"
|
||||||
|
require "any"
|
||||||
|
require "not"
|
||||||
|
require "optional"
|
||||||
|
require "tick_of"
|
|
@ -0,0 +1,22 @@
|
||||||
|
---@meta
|
||||||
|
|
||||||
|
---An ECS filter that inverts the provided filter/query result.
|
||||||
|
---
|
||||||
|
---Use the utility function `Not(...)` to create a new query since its faster to
|
||||||
|
---write than this.
|
||||||
|
---
|
||||||
|
---@see Not
|
||||||
|
---@class NotQuery: userdata
|
||||||
|
NotQuery = {}
|
||||||
|
|
||||||
|
---Create a new NotQuery.
|
||||||
|
---
|
||||||
|
---Use the utility function `Not(...)` to create a new query since its faster to
|
||||||
|
---write than this.
|
||||||
|
---
|
||||||
|
---@see Not
|
||||||
|
---@param val Query The query to invert.
|
||||||
|
function NotQuery:new(val) end
|
||||||
|
|
||||||
|
---An internal function used by the engine to retrieve the query result.
|
||||||
|
function NotQuery:__lyra_internal_ecs_query_result(world, entity) end
|
|
@ -0,0 +1,26 @@
|
||||||
|
---@meta
|
||||||
|
|
||||||
|
---An ECS query that ignores filters and queries that dont return anything.
|
||||||
|
---
|
||||||
|
---If the provided query returns nothing, this query will provide a `nil` value.
|
||||||
|
---The results of filters are essentially ignored, since it doesn't matter the result, this query
|
||||||
|
---will return. If the provided query has a result, this query will also return it.
|
||||||
|
---
|
||||||
|
---Use the utility function `Optional(...)` to create a new query since its faster to
|
||||||
|
---write than this.
|
||||||
|
---
|
||||||
|
---@see Optional
|
||||||
|
---@class OptionalQuery: userdata
|
||||||
|
OptionalQuery = {}
|
||||||
|
|
||||||
|
---Create a new OptionalQuery.
|
||||||
|
---
|
||||||
|
---Use the utility function `Optional(...)` to create a new query since its faster to
|
||||||
|
---write than this.
|
||||||
|
---
|
||||||
|
---@see Optional
|
||||||
|
---@param val Query The query to invert.
|
||||||
|
function OptionalQuery:new(val) end
|
||||||
|
|
||||||
|
---An internal function used by the engine to retrieve the query result.
|
||||||
|
function OptionalQuery:__lyra_internal_ecs_query_result(world, entity) end
|
|
@ -0,0 +1,16 @@
|
||||||
|
---@meta
|
||||||
|
|
||||||
|
---An ECS query used for obtaining Resources from the `World`.
|
||||||
|
---@class ResQuery: userdata
|
||||||
|
ResQuery = {}
|
||||||
|
|
||||||
|
---Create a new ResQuery for getting a Resource from the `World`.
|
||||||
|
---
|
||||||
|
---Use the utility function `Res(...)` to create a new query since its faster to
|
||||||
|
---write than this.
|
||||||
|
---
|
||||||
|
---@param val table|userdata The resource type to obtain.
|
||||||
|
function ResQuery:new(val) end
|
||||||
|
|
||||||
|
---An internal function used by the engine to retrieve the query result.
|
||||||
|
function ResQuery:__lyra_internal_ecs_query_result(world, entity) end
|
|
@ -0,0 +1,22 @@
|
||||||
|
---@meta
|
||||||
|
|
||||||
|
---An ECS query that returns the tick of the resource or component provided.
|
||||||
|
---
|
||||||
|
---Use the utility function `TickOf(...)` to create a new query since its faster to
|
||||||
|
---write than this.
|
||||||
|
---
|
||||||
|
---@see TickOf
|
||||||
|
---@class TickOfQuery: userdata
|
||||||
|
TickOfQuery = {}
|
||||||
|
|
||||||
|
---Create a new TickOfQuery.
|
||||||
|
---
|
||||||
|
---Use the utility function `TickOf(...)` to create a new query since its faster to
|
||||||
|
---write than this.
|
||||||
|
---
|
||||||
|
---@see TickOf
|
||||||
|
---@param val table|userdata The component or resource to retrieve the tick of.
|
||||||
|
function TickOfQuery:new(val) end
|
||||||
|
|
||||||
|
---An internal function used by the engine to retrieve the query result.
|
||||||
|
function TickOfQuery:__lyra_internal_ecs_query_result(world, entity) end
|
|
@ -0,0 +1,23 @@
|
||||||
|
---@meta
|
||||||
|
|
||||||
|
---@class View: userdata
|
||||||
|
View = {}
|
||||||
|
|
||||||
|
---Create a new view to query for components and world resources.
|
||||||
|
---
|
||||||
|
---Each parameter is a query. If you want to query entities with components, you would just use
|
||||||
|
---the component names.
|
||||||
|
---There are other queries, like `Changed` for querying for changed resources and components,
|
||||||
|
---and `Res` for querying for resources.
|
||||||
|
---
|
||||||
|
---@return View
|
||||||
|
function View.new(...) end
|
||||||
|
|
||||||
|
---@class ViewResult: userdata
|
||||||
|
ViewResult = {}
|
||||||
|
|
||||||
|
---Returns an interator over the results of the View.
|
||||||
|
---
|
||||||
|
---@generic T...
|
||||||
|
---@return fun(): EntityRef, T... iterator An iterator over the results. In the same order of the created View.
|
||||||
|
function ViewResult:iter() end
|
|
@ -0,0 +1,19 @@
|
||||||
|
---@meta
|
||||||
|
|
||||||
|
---Results of a View over a single entity.
|
||||||
|
---@class ViewOneResult: userdata
|
||||||
|
ViewOneResult = {}
|
||||||
|
|
||||||
|
---Returns the results of the view over a single entity.
|
||||||
|
---
|
||||||
|
---@see ViewOneResult.__call
|
||||||
|
---@generic T...
|
||||||
|
---@return T...
|
||||||
|
function ViewOneResult:get() end
|
||||||
|
|
||||||
|
---Returns the results of the view over a single entity.
|
||||||
|
---
|
||||||
|
---@see ViewOneResult.get
|
||||||
|
---@generic T...
|
||||||
|
---@return T...
|
||||||
|
function ViewOneResult:__call() end
|
|
@ -9,35 +9,6 @@ World = {}
|
||||||
---@return Entity
|
---@return Entity
|
||||||
function World:spawn(...) end
|
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.
|
---Get an ECS resource.
|
||||||
---
|
---
|
||||||
---Returns `nil` if the resource was not found in the world. Many resources will
|
---Returns `nil` if the resource was not found in the world. Many resources will
|
||||||
|
@ -72,4 +43,53 @@ function World:add_resource(resource) end
|
||||||
---
|
---
|
||||||
---@param path string
|
---@param path string
|
||||||
---@return Handle asset An asset handle to the requested resource type.
|
---@return Handle asset An asset handle to the requested resource type.
|
||||||
function World:request_asset(path) end
|
function World:request_asset(path) end
|
||||||
|
|
||||||
|
---Get the current tick of the world.
|
||||||
|
---
|
||||||
|
---The tick is used to drive changed detection of resources and components.
|
||||||
|
---The world tick is iterated every frame.
|
||||||
|
---
|
||||||
|
---@return number
|
||||||
|
function World:get_tick() end
|
||||||
|
|
||||||
|
---Get an event reader of a specific event.
|
||||||
|
---
|
||||||
|
---@generic T
|
||||||
|
---@param event T
|
||||||
|
---@return EventReader<T>
|
||||||
|
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(view) end
|
||||||
|
|
||||||
|
---View a single entity in the world.
|
||||||
|
---
|
||||||
|
---The view can contain queries and filters, but they will only be evaluated for a single entity.
|
||||||
|
---
|
||||||
|
---@param en Entity
|
||||||
|
---@param view View
|
||||||
|
---@return ViewOneResult
|
||||||
|
function World:view_one(en, view) end
|
||||||
|
|
||||||
|
--World global
|
||||||
|
---@type World
|
||||||
|
world = nil
|
|
@ -1,10 +1,4 @@
|
||||||
require "math.vec2"
|
require "math.init"
|
||||||
require "math.vec3"
|
require "ecs.init"
|
||||||
require "math.vec4"
|
|
||||||
require "math.quat"
|
|
||||||
require "math.transform"
|
|
||||||
|
|
||||||
require "ecs.window"
|
|
||||||
require "ecs.delta_time"
|
|
||||||
|
|
||||||
require "asset.handle"
|
require "asset.handle"
|
|
@ -0,0 +1,6 @@
|
||||||
|
require "math.vec2"
|
||||||
|
require "math.vec3"
|
||||||
|
require "math.vec4"
|
||||||
|
require "math.quat"
|
||||||
|
require "math.transform"
|
||||||
|
require "math.angle"
|
|
@ -38,6 +38,7 @@ impl ReflectBranch {
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
/// If `self` is not a variant of [`ReflectBranch::Component`].
|
/// If `self` is not a variant of [`ReflectBranch::Component`].
|
||||||
|
#[deprecated(note = "use ReflectBranch::as_component instead")]
|
||||||
pub fn as_component_unchecked(&self) -> &ReflectedComponent {
|
pub fn as_component_unchecked(&self) -> &ReflectedComponent {
|
||||||
match self {
|
match self {
|
||||||
ReflectBranch::Component(c) => c,
|
ReflectBranch::Component(c) => c,
|
||||||
|
@ -45,6 +46,16 @@ impl ReflectBranch {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets self as a [`ReflectedComponent`].
|
||||||
|
///
|
||||||
|
/// Returns `None` if `self` is not a component.
|
||||||
|
pub fn as_component(&self) -> Option<&ReflectedComponent> {
|
||||||
|
match self {
|
||||||
|
ReflectBranch::Component(c) => Some(c),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a boolean indicating if `self` is a reflection of a Component.
|
/// Returns a boolean indicating if `self` is a reflection of a Component.
|
||||||
pub fn is_component(&self) -> bool {
|
pub fn is_component(&self) -> bool {
|
||||||
matches!(self, ReflectBranch::Component(_))
|
matches!(self, ReflectBranch::Component(_))
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
use std::{ptr::NonNull, ops::Deref};
|
use std::ptr::NonNull;
|
||||||
|
|
||||||
use lyra_ecs::{query::dynamic::DynamicViewStateIter, Entity};
|
use lyra_ecs::{query::dynamic::DynamicViewStateIter, Entity};
|
||||||
use lyra_reflect::TypeRegistry;
|
use lyra_reflect::TypeRegistry;
|
||||||
|
|
||||||
|
use crate::ScriptWorldPtr;
|
||||||
|
|
||||||
#[cfg(feature = "lua")]
|
#[cfg(feature = "lua")]
|
||||||
use super::ReflectLuaProxy;
|
use super::ReflectLuaProxy;
|
||||||
|
|
||||||
#[cfg(feature = "lua")]
|
#[cfg(feature = "lua")]
|
||||||
pub struct ReflectedItem {
|
pub struct ReflectedItem {
|
||||||
//pub proxy: &'a ReflectLuaProxy,
|
|
||||||
pub comp_ptr: NonNull<u8>,
|
pub comp_ptr: NonNull<u8>,
|
||||||
pub comp_val: mlua::Value,
|
pub comp_val: mlua::Value,
|
||||||
}
|
}
|
||||||
|
@ -19,54 +20,63 @@ pub struct ReflectedRow {
|
||||||
pub row: Vec<ReflectedItem>,
|
pub row: Vec<ReflectedItem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct ReflectedIteratorOwned {
|
||||||
|
pub world_ptr: ScriptWorldPtr,
|
||||||
|
pub dyn_view: DynamicViewStateIter,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReflectedIteratorOwned {
|
||||||
|
pub fn next_lua(&mut self, lua: &mlua::Lua) -> Option<ReflectedRow> {
|
||||||
|
let world = self.world_ptr.read();
|
||||||
|
next_lua(lua, &world, &mut self.dyn_view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ReflectedIterator<'a> {
|
pub struct ReflectedIterator<'a> {
|
||||||
pub world: &'a lyra_ecs::World,
|
pub world: &'a lyra_ecs::World,
|
||||||
pub dyn_view: DynamicViewStateIter,
|
pub dyn_view: DynamicViewStateIter,
|
||||||
pub reflected_components: Option<NonNull<TypeRegistry>>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ReflectedIterator<'a> {
|
impl<'a> ReflectedIterator<'a> {
|
||||||
#[cfg(feature = "lua")]
|
|
||||||
pub fn next_lua(&mut self, lua: &mlua::Lua) -> Option<ReflectedRow> {
|
pub fn next_lua(&mut self, lua: &mlua::Lua) -> Option<ReflectedRow> {
|
||||||
use mlua::IntoLua;
|
next_lua(lua, &self.world, &mut self.dyn_view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//let world = self.world.read();
|
fn next_lua(lua: &mlua::Lua, world: &lyra_ecs::World, dyn_view: &mut DynamicViewStateIter) -> Option<ReflectedRow> {
|
||||||
let n = self.dyn_view.next(&self.world);
|
use mlua::IntoLua;
|
||||||
|
|
||||||
if let Some((en, row)) = n {
|
|
||||||
if self.reflected_components.is_none() {
|
|
||||||
self.reflected_components = self.world.get_resource::<TypeRegistry>()
|
|
||||||
.map(|r| NonNull::from(r.deref()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut dynamic_row = vec![];
|
//let world = world.read();
|
||||||
for d in row.iter() {
|
let n = dyn_view.next(&world);
|
||||||
let id = d.info.type_id().as_rust();
|
|
||||||
let reflected_components =
|
if let Some((en, row)) = n {
|
||||||
unsafe { self.reflected_components.as_ref().unwrap().as_ref() };
|
let reflected_components = world.get_resource::<TypeRegistry>().unwrap();
|
||||||
|
|
||||||
let reg_type = reflected_components.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.fn_as_lua)(lua, d.ptr.cast()).unwrap()
|
|
||||||
.into_lua(lua).unwrap();
|
|
||||||
|
|
||||||
dynamic_row.push(ReflectedItem {
|
let mut dynamic_row = vec![];
|
||||||
comp_ptr: d.ptr,
|
for d in row.iter() {
|
||||||
comp_val: value
|
let id = d.info.type_id().as_rust();
|
||||||
});
|
|
||||||
}
|
let reg_type = reflected_components.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();
|
||||||
|
|
||||||
let row = ReflectedRow {
|
dynamic_row.push(ReflectedItem {
|
||||||
entity: en,
|
comp_ptr: d.ptr,
|
||||||
row: dynamic_row
|
comp_val: value
|
||||||
};
|
});
|
||||||
|
|
||||||
Some(row)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let row = ReflectedRow {
|
||||||
|
entity: en,
|
||||||
|
row: dynamic_row
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(row)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
mod view;
|
||||||
|
pub use view::*;
|
||||||
|
|
||||||
|
mod view_one;
|
||||||
|
pub use view_one::*;
|
||||||
|
|
||||||
|
pub mod query;
|
|
@ -0,0 +1,107 @@
|
||||||
|
use lyra_reflect::{ReflectWorldExt, RegisteredType, TypeRegistry};
|
||||||
|
use mlua::IntoLua;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
lua::{LuaComponent, ReflectLuaProxy, FN_NAME_INTERNAL_ECS_QUERY_RESULT},
|
||||||
|
ReflectBranch, ScriptEntity, ScriptWorldPtr,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::LuaQueryResult;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct LuaChangedQuery(LuaComponent);
|
||||||
|
|
||||||
|
impl mlua::FromLua for LuaChangedQuery {
|
||||||
|
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: "ChangedQuery".into(),
|
||||||
|
message: None,
|
||||||
|
})
|
||||||
|
.and_then(|ud| ud.borrow::<Self>())
|
||||||
|
.map(|ud| ud.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl mlua::UserData for LuaChangedQuery {
|
||||||
|
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
||||||
|
methods.add_function("new", |_, comp: LuaComponent| Ok(Self(comp)));
|
||||||
|
|
||||||
|
methods.add_method(
|
||||||
|
FN_NAME_INTERNAL_ECS_QUERY_RESULT,
|
||||||
|
|lua, this, (world, en): (ScriptWorldPtr, ScriptEntity)| {
|
||||||
|
let mut world = world.write();
|
||||||
|
let reflect = this.0.reflect_type()?;
|
||||||
|
|
||||||
|
let tyid = reflect.reflect_branch.reflect_type_id();
|
||||||
|
match &reflect.reflect_branch {
|
||||||
|
ReflectBranch::Component(comp) => {
|
||||||
|
if !comp.reflect_is_changed(&world, *en).unwrap_or(false) {
|
||||||
|
return Ok(LuaQueryResult::FilterDeny);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the pointer of the component in the archetype column.
|
||||||
|
let arch = match world.entity_archetype(*en) {
|
||||||
|
Some(a) => a,
|
||||||
|
None => return Ok(LuaQueryResult::FilterDeny),
|
||||||
|
};
|
||||||
|
let arch_idx = *arch.entity_indexes().get(&en).unwrap();
|
||||||
|
|
||||||
|
let col = match arch.get_column(tyid) {
|
||||||
|
Some(col) => col,
|
||||||
|
None => {
|
||||||
|
// the entity doesn't have the component
|
||||||
|
return Ok(LuaQueryResult::FilterDeny);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let col_ptr = col.component_ptr_non_tick(*arch_idx as usize).cast();
|
||||||
|
|
||||||
|
// get the type registry to apply the new value
|
||||||
|
let reg = world.get_resource::<TypeRegistry>().unwrap();
|
||||||
|
let reg_type = reg.get_type(tyid).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");
|
||||||
|
Ok(LuaQueryResult::Some(proxy.as_lua(lua, col_ptr)?))
|
||||||
|
}
|
||||||
|
ReflectBranch::Resource(res) => {
|
||||||
|
// Check if the resource was changed. Per API spec, must return false.
|
||||||
|
match res.reflect_is_changed(&world) {
|
||||||
|
Some(false) => {
|
||||||
|
return Ok(LuaQueryResult::FilterDeny);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// the resource was not found
|
||||||
|
return Ok(LuaQueryResult::AlwaysNone);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// unwrap is safe here since the match above would verify that the
|
||||||
|
// resource exists.
|
||||||
|
let res_ptr = res.reflect_ptr(&mut world).unwrap();
|
||||||
|
let reg_type = world
|
||||||
|
.get_type::<RegisteredType>(tyid)
|
||||||
|
.expect("Resource is not type registered!");
|
||||||
|
let proxy = reg_type
|
||||||
|
.get_data::<ReflectLuaProxy>()
|
||||||
|
.expect("Type does not have ReflectLuaProxy as a TypeData");
|
||||||
|
|
||||||
|
Ok(LuaQueryResult::Some(
|
||||||
|
proxy
|
||||||
|
.as_lua(lua, res_ptr.cast())
|
||||||
|
.and_then(|ud| ud.into_lua(lua))?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
use crate::{
|
||||||
|
lua::{LuaComponent, FN_NAME_INTERNAL_ECS_QUERY_RESULT},
|
||||||
|
ScriptEntity, ScriptWorldPtr,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::LuaQueryResult;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct LuaHasQuery(LuaComponent);
|
||||||
|
|
||||||
|
impl mlua::FromLua for LuaHasQuery {
|
||||||
|
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: "HasQuery".into(),
|
||||||
|
message: None,
|
||||||
|
})
|
||||||
|
.and_then(|ud| ud.borrow::<Self>())
|
||||||
|
.map(|ud| ud.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl mlua::UserData for LuaHasQuery {
|
||||||
|
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
||||||
|
methods.add_function("new", |_, comp: LuaComponent| {
|
||||||
|
let reflect = comp.reflect_type()?;
|
||||||
|
if !reflect.reflect_branch.is_component() {
|
||||||
|
Err(mlua::Error::runtime("provided type is not a component!"))
|
||||||
|
} else {
|
||||||
|
Ok(Self(comp))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
methods.add_method(
|
||||||
|
FN_NAME_INTERNAL_ECS_QUERY_RESULT,
|
||||||
|
|_, this, (world, en): (ScriptWorldPtr, ScriptEntity)| {
|
||||||
|
let world = world.write();
|
||||||
|
let reflect = this.0.reflect_type()?;
|
||||||
|
|
||||||
|
let tyid = reflect.reflect_branch.reflect_type_id();
|
||||||
|
|
||||||
|
// try to find the entity's archetype and the component column in the archetype
|
||||||
|
let arch = match world.entity_archetype(*en) {
|
||||||
|
Some(a) => a,
|
||||||
|
None => return Ok(LuaQueryResult::FilterDeny)
|
||||||
|
};
|
||||||
|
let component_col = arch.get_column(tyid);
|
||||||
|
|
||||||
|
if component_col.is_some() {
|
||||||
|
Ok(LuaQueryResult::FilterPass)
|
||||||
|
} else {
|
||||||
|
Ok(LuaQueryResult::FilterDeny)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,183 @@
|
||||||
|
mod res;
|
||||||
|
pub use res::*;
|
||||||
|
|
||||||
|
mod changed;
|
||||||
|
pub use changed::*;
|
||||||
|
|
||||||
|
mod has;
|
||||||
|
pub use has::*;
|
||||||
|
|
||||||
|
mod not;
|
||||||
|
pub use not::*;
|
||||||
|
|
||||||
|
mod or;
|
||||||
|
pub use or::*;
|
||||||
|
|
||||||
|
mod tick_of;
|
||||||
|
pub use tick_of::*;
|
||||||
|
|
||||||
|
mod optional;
|
||||||
|
pub use optional::*;
|
||||||
|
|
||||||
|
use lyra_ecs::Entity;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
lua::{LuaComponent, FN_NAME_INTERNAL_ECS_QUERY_RESULT},
|
||||||
|
ScriptEntity, ScriptWorldPtr,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum QueryInner {
|
||||||
|
Component(LuaComponent),
|
||||||
|
Function(mlua::Function),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct LuaQuery(QueryInner);
|
||||||
|
|
||||||
|
impl LuaQuery {
|
||||||
|
pub fn new(query: LuaComponent) -> Self {
|
||||||
|
Self(QueryInner::Component(query))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_function(f: mlua::Function) -> Self {
|
||||||
|
Self(QueryInner::Function(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
entity: Entity,
|
||||||
|
) -> mlua::Result<LuaQueryResult> {
|
||||||
|
let lua_en = ScriptEntity(entity);
|
||||||
|
match &self.0 {
|
||||||
|
QueryInner::Component(comp) => {
|
||||||
|
comp.call_method(FN_NAME_INTERNAL_ECS_QUERY_RESULT, (world, lua_en))
|
||||||
|
}
|
||||||
|
QueryInner::Function(function) => function.call((world, lua_en)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl mlua::FromLua for LuaQuery {
|
||||||
|
fn from_lua(value: mlua::Value, lua: &mlua::Lua) -> mlua::Result<Self> {
|
||||||
|
let tyname = value.type_name();
|
||||||
|
|
||||||
|
if let Some(f) = value.as_function() {
|
||||||
|
Ok(Self(QueryInner::Function(f.clone())))
|
||||||
|
} else if let Ok(c) = LuaComponent::from_lua(value, lua) {
|
||||||
|
Ok(Self(QueryInner::Component(c)))
|
||||||
|
} else {
|
||||||
|
Err(mlua::Error::FromLuaConversionError {
|
||||||
|
from: tyname,
|
||||||
|
to: "Query".into(),
|
||||||
|
message: Some("expected query function, table, or user data".into()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum LuaQueryResult {
|
||||||
|
None,
|
||||||
|
AlwaysNone,
|
||||||
|
FilterPass,
|
||||||
|
FilterDeny,
|
||||||
|
Some(mlua::Value),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl mlua::IntoLua for LuaQueryResult {
|
||||||
|
fn into_lua(self, lua: &mlua::Lua) -> mlua::Result<mlua::Value> {
|
||||||
|
let t = lua.create_table()?;
|
||||||
|
t.set("enum_ty", "query_result")?;
|
||||||
|
|
||||||
|
match self {
|
||||||
|
LuaQueryResult::None => {
|
||||||
|
t.set("result", "none")?;
|
||||||
|
}
|
||||||
|
LuaQueryResult::AlwaysNone => {
|
||||||
|
t.set("result", "always_none")?;
|
||||||
|
}
|
||||||
|
LuaQueryResult::FilterPass => {
|
||||||
|
t.set("result", "filter_pass")?;
|
||||||
|
}
|
||||||
|
LuaQueryResult::FilterDeny => {
|
||||||
|
t.set("result", "filter_deny")?;
|
||||||
|
}
|
||||||
|
LuaQueryResult::Some(value) => {
|
||||||
|
t.set("result", "some")?;
|
||||||
|
t.set("val", value)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.into_lua(lua)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn from_lua_error_query_result(ty: &'static str, msg: &str) -> mlua::Error {
|
||||||
|
mlua::Error::FromLuaConversionError {
|
||||||
|
from: ty,
|
||||||
|
to: "QueryResult".into(),
|
||||||
|
message: Some(msg.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn malformed_table_error_query_result(ty: &'static str, missing_field: &str) -> mlua::Error {
|
||||||
|
mlua::Error::FromLuaConversionError {
|
||||||
|
from: ty,
|
||||||
|
to: "QueryResult".into(),
|
||||||
|
message: Some(format!(
|
||||||
|
"malformed table, cannot convert, failed to get field '{}'",
|
||||||
|
missing_field
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl mlua::FromLua for LuaQueryResult {
|
||||||
|
fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
|
||||||
|
let ty = value.type_name();
|
||||||
|
let table = value
|
||||||
|
.as_table()
|
||||||
|
.ok_or(from_lua_error_query_result(ty, "expected Table"))?;
|
||||||
|
|
||||||
|
let var_name: String = table
|
||||||
|
.get("enum_ty")
|
||||||
|
.map_err(|_| malformed_table_error_query_result(ty, "enum_ty"))?;
|
||||||
|
if var_name != "query_result" {
|
||||||
|
return Err(mlua::Error::FromLuaConversionError {
|
||||||
|
from: ty,
|
||||||
|
to: "QueryResult".into(),
|
||||||
|
message: Some(format!("mismatched enum_ty: '{}'", var_name)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let result: String = table
|
||||||
|
.get("result")
|
||||||
|
.map_err(|_| malformed_table_error_query_result(ty, "result"))?;
|
||||||
|
let result_str = result.as_str();
|
||||||
|
|
||||||
|
match result_str {
|
||||||
|
"none" => Ok(Self::None),
|
||||||
|
"always_none" => Ok(Self::AlwaysNone),
|
||||||
|
"filter_pass" => Ok(Self::FilterPass),
|
||||||
|
"filter_deny" => Ok(Self::FilterDeny),
|
||||||
|
"some" => {
|
||||||
|
let val: mlua::Value = table
|
||||||
|
.get("val")
|
||||||
|
.map_err(|_| malformed_table_error_query_result(ty, "val"))?;
|
||||||
|
Ok(Self::Some(val))
|
||||||
|
}
|
||||||
|
_ => Err(mlua::Error::FromLuaConversionError {
|
||||||
|
from: ty,
|
||||||
|
to: "QueryResult".into(),
|
||||||
|
message: Some(format!("unknown result type: '{}'", result_str)),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
use crate::{
|
||||||
|
lua::FN_NAME_INTERNAL_ECS_QUERY_RESULT,
|
||||||
|
ScriptEntity, ScriptWorldPtr,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{LuaQuery, LuaQueryResult};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct LuaNotQuery(LuaQuery);
|
||||||
|
|
||||||
|
impl mlua::FromLua for LuaNotQuery {
|
||||||
|
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: "NotQuery".into(),
|
||||||
|
message: None,
|
||||||
|
})
|
||||||
|
.and_then(|ud| ud.borrow::<Self>())
|
||||||
|
.map(|ud| ud.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl mlua::UserData for LuaNotQuery {
|
||||||
|
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
||||||
|
methods.add_function("new", |_, q: LuaQuery| {
|
||||||
|
Ok(Self(q))
|
||||||
|
});
|
||||||
|
|
||||||
|
methods.add_method(
|
||||||
|
FN_NAME_INTERNAL_ECS_QUERY_RESULT,
|
||||||
|
|_, this, (world, en): (ScriptWorldPtr, ScriptEntity)| {
|
||||||
|
let res = this.0.get_query_result(world, en.0)?;
|
||||||
|
|
||||||
|
match res {
|
||||||
|
LuaQueryResult::None => Ok(LuaQueryResult::FilterPass),
|
||||||
|
LuaQueryResult::AlwaysNone => Ok(LuaQueryResult::FilterPass),
|
||||||
|
LuaQueryResult::FilterPass => Ok(LuaQueryResult::FilterDeny),
|
||||||
|
LuaQueryResult::FilterDeny => Ok(LuaQueryResult::FilterPass),
|
||||||
|
LuaQueryResult::Some(_) => Ok(LuaQueryResult::FilterDeny),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
use crate::{
|
||||||
|
lua::FN_NAME_INTERNAL_ECS_QUERY_RESULT, ScriptEntity, ScriptWorldPtr,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{LuaQuery, LuaQueryResult};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct LuaOptionalQuery(LuaQuery);
|
||||||
|
|
||||||
|
impl mlua::FromLua for LuaOptionalQuery {
|
||||||
|
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: "OptionalQuery".into(),
|
||||||
|
message: None,
|
||||||
|
})
|
||||||
|
.and_then(|ud| ud.borrow::<Self>())
|
||||||
|
.map(|ud| ud.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl mlua::UserData for LuaOptionalQuery {
|
||||||
|
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
||||||
|
methods.add_function("new", |_, q: LuaQuery| Ok(Self(q)));
|
||||||
|
|
||||||
|
methods.add_method(
|
||||||
|
FN_NAME_INTERNAL_ECS_QUERY_RESULT,
|
||||||
|
|_, this, (world, en): (ScriptWorldPtr, ScriptEntity)| {
|
||||||
|
let res = this.0.get_query_result(world, en.0)?;
|
||||||
|
|
||||||
|
match res {
|
||||||
|
LuaQueryResult::None => Ok(LuaQueryResult::Some(mlua::Value::Nil)),
|
||||||
|
LuaQueryResult::AlwaysNone => Ok(LuaQueryResult::Some(mlua::Value::Nil)),
|
||||||
|
LuaQueryResult::FilterPass => Ok(LuaQueryResult::FilterPass),
|
||||||
|
LuaQueryResult::FilterDeny => Ok(LuaQueryResult::FilterPass),
|
||||||
|
LuaQueryResult::Some(v) => Ok(LuaQueryResult::Some(v)),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
use crate::{lua::FN_NAME_INTERNAL_ECS_QUERY_RESULT, ScriptEntity, ScriptWorldPtr};
|
||||||
|
|
||||||
|
use super::{LuaQuery, LuaQueryResult};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct LuaOrQuery(Vec<LuaQuery>);
|
||||||
|
|
||||||
|
impl mlua::FromLua for LuaOrQuery {
|
||||||
|
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: "OrQuery".into(),
|
||||||
|
message: None,
|
||||||
|
})
|
||||||
|
.and_then(|ud| ud.borrow::<Self>())
|
||||||
|
.map(|ud| ud.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl mlua::UserData for LuaOrQuery {
|
||||||
|
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
||||||
|
methods.add_function("new", |_, qs: mlua::Variadic<LuaQuery>| {
|
||||||
|
Ok(Self(qs.to_vec()))
|
||||||
|
});
|
||||||
|
|
||||||
|
methods.add_method(
|
||||||
|
FN_NAME_INTERNAL_ECS_QUERY_RESULT,
|
||||||
|
|_, this, (world, en): (ScriptWorldPtr, ScriptEntity)| {
|
||||||
|
for q in &this.0 {
|
||||||
|
let res = q.get_query_result(world.clone(), en.0)?;
|
||||||
|
|
||||||
|
match res {
|
||||||
|
LuaQueryResult::None
|
||||||
|
| LuaQueryResult::AlwaysNone
|
||||||
|
| LuaQueryResult::FilterDeny => {}
|
||||||
|
LuaQueryResult::FilterPass => return Ok(LuaQueryResult::FilterPass),
|
||||||
|
LuaQueryResult::Some(v) => return Ok(LuaQueryResult::Some(v)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(LuaQueryResult::FilterDeny)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
use lyra_reflect::{ReflectWorldExt, RegisteredType};
|
||||||
|
use mlua::IntoLua;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
lua::{LuaComponent, ReflectLuaProxy, FN_NAME_INTERNAL_ECS_QUERY_RESULT},
|
||||||
|
ScriptEntity, ScriptWorldPtr,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::LuaQueryResult;
|
||||||
|
|
||||||
|
#[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, ScriptEntity)| {
|
||||||
|
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");
|
||||||
|
|
||||||
|
Ok(LuaQueryResult::Some(
|
||||||
|
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)
|
||||||
|
Ok(LuaQueryResult::AlwaysNone)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
use crate::{
|
||||||
|
lua::{LuaComponent, FN_NAME_INTERNAL_ECS_QUERY_RESULT},
|
||||||
|
ReflectBranch, ScriptEntity, ScriptWorldPtr,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::LuaQueryResult;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct LuaTickOfQuery(LuaComponent);
|
||||||
|
|
||||||
|
impl mlua::FromLua for LuaTickOfQuery {
|
||||||
|
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: "TickOfQuery".into(),
|
||||||
|
message: None,
|
||||||
|
})
|
||||||
|
.and_then(|ud| ud.borrow::<Self>())
|
||||||
|
.map(|ud| ud.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl mlua::UserData for LuaTickOfQuery {
|
||||||
|
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
||||||
|
methods.add_function("new", |_, comp: LuaComponent| Ok(Self(comp)));
|
||||||
|
|
||||||
|
methods.add_method(
|
||||||
|
FN_NAME_INTERNAL_ECS_QUERY_RESULT,
|
||||||
|
|_, this, (world, en): (ScriptWorldPtr, ScriptEntity)| {
|
||||||
|
let world = world.read();
|
||||||
|
let reflect = this.0.reflect_type()?;
|
||||||
|
match &reflect.reflect_branch {
|
||||||
|
ReflectBranch::Component(comp) => {
|
||||||
|
if let Some(tick) = comp.reflect_tick(&world, *en) {
|
||||||
|
Ok(LuaQueryResult::Some(mlua::Value::Number(*tick as _)))
|
||||||
|
} else {
|
||||||
|
Ok(LuaQueryResult::FilterDeny)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ReflectBranch::Resource(res) => {
|
||||||
|
if let Some(tick) = res.reflect_tick(&world) {
|
||||||
|
Ok(LuaQueryResult::Some(mlua::Value::Number(*tick as _)))
|
||||||
|
} else {
|
||||||
|
Ok(LuaQueryResult::FilterDeny)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,345 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use atomic_refcell::AtomicRefCell;
|
||||||
|
use lyra_ecs::{
|
||||||
|
query::dynamic::{DynamicViewState, QueryDynamicType},
|
||||||
|
Entity,
|
||||||
|
};
|
||||||
|
use mlua::{IntoLua, IntoLuaMulti, ObjectLike};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
lua::{
|
||||||
|
LuaComponent, LuaEntityRef, ReflectedIteratorOwned, TypeLookup, WorldError,
|
||||||
|
FN_NAME_INTERNAL_ECS_QUERY_RESULT, FN_NAME_INTERNAL_REFLECT_TYPE,
|
||||||
|
},
|
||||||
|
ScriptBorrow, ScriptWorldPtr,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::query::{LuaQuery, LuaQueryResult};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) enum ViewQueryItem {
|
||||||
|
UserData(mlua::AnyUserData),
|
||||||
|
Table(mlua::Table),
|
||||||
|
Function(mlua::Function),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl mlua::FromLua for ViewQueryItem {
|
||||||
|
fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
|
||||||
|
let tyname = value.type_name();
|
||||||
|
match value {
|
||||||
|
mlua::Value::Table(table) => Ok(Self::Table(table)),
|
||||||
|
mlua::Value::Function(function) => Ok(Self::Function(function)),
|
||||||
|
mlua::Value::UserData(any_user_data) => Ok(Self::UserData(any_user_data)),
|
||||||
|
_ => Err(mlua::Error::FromLuaConversionError {
|
||||||
|
from: tyname,
|
||||||
|
to: "ViewQueryItem".into(),
|
||||||
|
message: Some("expected Table, Function, or UserData".into()),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ViewQueryItem {
|
||||||
|
/// 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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if self is a Query.
|
||||||
|
///
|
||||||
|
/// If self is a function, it will return true. Else, it checks for a function with the
|
||||||
|
/// name of [`FN_NAME_INTERNAL_ECS_QUERY_RESULT`] on the table or userdata. If the function
|
||||||
|
/// is found, it returns true.
|
||||||
|
pub fn is_query(&self) -> mlua::Result<bool> {
|
||||||
|
Ok(matches!(self, ViewQueryItem::Function(_))
|
||||||
|
|| self.has_function(FN_NAME_INTERNAL_ECS_QUERY_RESULT)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get self as a [`LuaQuery`].
|
||||||
|
///
|
||||||
|
/// If self is a function, it assumes that it is a filter.
|
||||||
|
pub fn as_query(&self) -> LuaQuery {
|
||||||
|
match self.clone() {
|
||||||
|
ViewQueryItem::UserData(ud) => LuaQuery::new(LuaComponent::UserData(ud)),
|
||||||
|
ViewQueryItem::Table(t) => LuaQuery::new(LuaComponent::Table(t)),
|
||||||
|
ViewQueryItem::Function(function) => LuaQuery::from_function(function),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct View {
|
||||||
|
pub(crate) items: Vec<ViewQueryItem>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl mlua::FromLua for View {
|
||||||
|
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 mlua::UserData for View {
|
||||||
|
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
||||||
|
methods.add_function("new", |_, args: mlua::Variadic<ViewQueryItem>| {
|
||||||
|
Ok(Self {
|
||||||
|
items: args.iter().cloned().collect(),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Results of queries in a View.
|
||||||
|
///
|
||||||
|
/// Represents the results of multiple queries.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
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)>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ViewResult {
|
||||||
|
world: ScriptWorldPtr,
|
||||||
|
reflected_iter: Arc<atomic_refcell::AtomicRefCell<ReflectedIteratorOwned>>,
|
||||||
|
/// The queries and the index they would be inserted in the result.
|
||||||
|
queries: Vec<(LuaQuery, u32)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for ViewResult {}
|
||||||
|
|
||||||
|
impl ViewResult {
|
||||||
|
pub fn new(world: ScriptWorldPtr, view: &View) -> Result<Self, mlua::Error> {
|
||||||
|
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()? {
|
||||||
|
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("ViewResult.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.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.push(dyn_type);
|
||||||
|
}
|
||||||
|
// functions are queries, the if statement at the start would cause this to
|
||||||
|
// be unreachable.
|
||||||
|
ViewQueryItem::Function(_) => unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(w);
|
||||||
|
|
||||||
|
let view_iter = view.into_iter();
|
||||||
|
let reflected_iter = ReflectedIteratorOwned {
|
||||||
|
world_ptr: world.clone(),
|
||||||
|
dyn_view: view_iter,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
world,
|
||||||
|
reflected_iter: Arc::new(AtomicRefCell::new(reflected_iter)),
|
||||||
|
queries,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the next row of components
|
||||||
|
fn next_components(
|
||||||
|
&mut self,
|
||||||
|
lua: &mlua::Lua,
|
||||||
|
) -> Result<Option<(Entity, mlua::MultiValue)>, mlua::Error> {
|
||||||
|
let mut query_iter = self.reflected_iter.borrow_mut();
|
||||||
|
if let Some(row) = query_iter.next_lua(lua) {
|
||||||
|
let (values, _): (Vec<_>, Vec<_>) = row
|
||||||
|
.row
|
||||||
|
.into_iter()
|
||||||
|
.into_iter()
|
||||||
|
.map(|r| (r.comp_val, r.comp_ptr.cast::<()>()))
|
||||||
|
.unzip();
|
||||||
|
let mult_val = mlua::MultiValue::from_iter(values.into_iter());
|
||||||
|
Ok(Some((row.entity, mult_val)))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl mlua::FromLua for ViewResult {
|
||||||
|
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 mlua::UserData for ViewResult {
|
||||||
|
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
||||||
|
methods.add_method_mut("next", |lua, this, ()| {
|
||||||
|
match this.next_components(lua)? {
|
||||||
|
Some((en, mut vals)) => {
|
||||||
|
loop {
|
||||||
|
let query_vals = match this.get_query_results(en)? {
|
||||||
|
ViewQueryResult::Some(v) => v,
|
||||||
|
ViewQueryResult::AlwaysNone => {
|
||||||
|
return mlua::Value::Nil.into_lua_multi(lua);
|
||||||
|
},
|
||||||
|
ViewQueryResult::None | ViewQueryResult::FilterDeny => {
|
||||||
|
// try to get it next loop
|
||||||
|
continue;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// insert query values to the result row
|
||||||
|
for (qval, qi) in query_vals {
|
||||||
|
vals.insert(qi as _, qval);
|
||||||
|
}
|
||||||
|
|
||||||
|
vals.push_front(LuaEntityRef::new(this.world.clone(), en).into_lua(lua)?);
|
||||||
|
return Ok(vals);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => mlua::Value::Nil.into_lua_multi(lua),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
methods.add_method("iter", |lua, this, ()| {
|
||||||
|
let key_arc = Arc::new(atomic_refcell::AtomicRefCell::new(Some(
|
||||||
|
lua.create_registry_value(this.clone())?,
|
||||||
|
)));
|
||||||
|
|
||||||
|
lua.create_function(move |lua, ()| {
|
||||||
|
let mut key_mut = key_arc.borrow_mut();
|
||||||
|
|
||||||
|
if let Some(key) = key_mut.as_ref() {
|
||||||
|
let mut this = lua.registry_value::<mlua::UserDataRefMut<Self>>(&key)?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match this.next_components(lua)? {
|
||||||
|
Some((en, mut vals)) => {
|
||||||
|
let lua_en =
|
||||||
|
LuaEntityRef::new(this.world.clone(), en).into_lua(lua)?;
|
||||||
|
|
||||||
|
let query_vals = match this.get_query_results(en)? {
|
||||||
|
ViewQueryResult::Some(v) => v,
|
||||||
|
ViewQueryResult::AlwaysNone => {
|
||||||
|
return mlua::Value::Nil.into_lua_multi(lua);
|
||||||
|
},
|
||||||
|
ViewQueryResult::None | ViewQueryResult::FilterDeny => {
|
||||||
|
// try to get it next loop
|
||||||
|
continue;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// insert query values to the result row
|
||||||
|
for (qval, qi) in query_vals {
|
||||||
|
vals.insert(qi as _, qval);
|
||||||
|
}
|
||||||
|
|
||||||
|
vals.push_front(lua_en);
|
||||||
|
return Ok(vals);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// If this is the last row, remove the registry value
|
||||||
|
// This doesn't protect against iterators that aren't fully consumed,
|
||||||
|
// that would cause a leak in the lua registry.
|
||||||
|
// TODO: fix leak
|
||||||
|
let key = key_mut.take().unwrap();
|
||||||
|
lua.remove_registry_value(key)?;
|
||||||
|
|
||||||
|
return mlua::Value::Nil.into_lua_multi(lua);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mlua::Value::Nil.into_lua_multi(lua)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,159 @@
|
||||||
|
use std::{any::TypeId, sync::Arc};
|
||||||
|
|
||||||
|
use lyra_ecs::{Entity, World};
|
||||||
|
use lyra_reflect::TypeRegistry;
|
||||||
|
use mlua::{IntoLua, ObjectLike};
|
||||||
|
|
||||||
|
use crate::{ScriptBorrow, ScriptWorldPtr};
|
||||||
|
|
||||||
|
use super::{reflect_type_user_data, Error, ReflectLuaProxy, TypeLookup, FN_NAME_INTERNAL_REFLECT_TYPE};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum LuaComponent {
|
||||||
|
UserData(mlua::AnyUserData),
|
||||||
|
Table(mlua::Table),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl mlua::FromLua for LuaComponent {
|
||||||
|
fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
|
||||||
|
let tyname = value.type_name();
|
||||||
|
match value {
|
||||||
|
mlua::Value::UserData(ud) => Ok(Self::UserData(ud)),
|
||||||
|
mlua::Value::Table(t) => Ok(Self::Table(t)),
|
||||||
|
_ => Err(mlua::Error::FromLuaConversionError {
|
||||||
|
from: tyname,
|
||||||
|
to: "LuaComponent".into(),
|
||||||
|
message: Some(
|
||||||
|
"expected Table or UserData that can be converted to a native struct".into(),
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl mlua::IntoLua for LuaComponent {
|
||||||
|
fn into_lua(self, lua: &mlua::Lua) -> mlua::Result<mlua::Value> {
|
||||||
|
match self {
|
||||||
|
Self::Table(t) => t.into_lua(lua),
|
||||||
|
Self::UserData(ud) => ud.into_lua(lua),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaComponent {
|
||||||
|
pub fn get_typeid(&self, world: &World) -> Option<TypeId> {
|
||||||
|
match self {
|
||||||
|
Self::Table(t) => {
|
||||||
|
let name: String = t.get(mlua::MetaMethod::Type.name()).ok()?;
|
||||||
|
let lookup = world.get_resource::<TypeLookup>().unwrap();
|
||||||
|
lookup.typeid_from_name.get(&name).cloned()
|
||||||
|
}
|
||||||
|
Self::UserData(ud) => {
|
||||||
|
let lua_comp = reflect_type_user_data(ud);
|
||||||
|
let refl_comp = lua_comp.reflect_branch.as_component_unchecked();
|
||||||
|
Some(refl_comp.info.type_id().as_rust())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
///
|
||||||
|
/// This can be used to make it easier to update things on the Entity.
|
||||||
|
pub struct LuaEntityRef {
|
||||||
|
en: Entity,
|
||||||
|
world: ScriptWorldPtr,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaEntityRef {
|
||||||
|
pub fn new(world: ScriptWorldPtr, en: Entity) -> Self {
|
||||||
|
Self {
|
||||||
|
en,
|
||||||
|
world,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl mlua::UserData for LuaEntityRef {
|
||||||
|
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
||||||
|
methods.add_method(
|
||||||
|
"update",
|
||||||
|
|lua, this, comps: mlua::Variadic<LuaComponent>| {
|
||||||
|
let mut world = this.world.write();
|
||||||
|
let world_tick = world.current_tick();
|
||||||
|
|
||||||
|
for (i, comp) in comps.iter().enumerate() {
|
||||||
|
let tid = comp.get_typeid(&world).ok_or(mlua::Error::BadArgument {
|
||||||
|
to: Some("Entity:update".into()),
|
||||||
|
pos: 2 + i,
|
||||||
|
name: Some("comps...".into()),
|
||||||
|
cause: Arc::new(mlua::Error::runtime(
|
||||||
|
"failed to get native TypeId from component",
|
||||||
|
)),
|
||||||
|
})?;
|
||||||
|
// convert component to mlua::Value
|
||||||
|
let comp = comp.clone().into_lua(lua)?;
|
||||||
|
|
||||||
|
// get the pointer of the component in the archetype column.
|
||||||
|
let arch = world.entity_archetype_mut(this.en).unwrap();
|
||||||
|
let arch_idx = *arch.entity_indexes().get(&this.en).unwrap();
|
||||||
|
let col = arch.get_column_mut(tid).unwrap();
|
||||||
|
let col_ptr = col.component_ptr(*arch_idx as usize, &world_tick).cast();
|
||||||
|
|
||||||
|
// get the type registry to apply the new value
|
||||||
|
let reg = world.get_resource::<TypeRegistry>().unwrap();
|
||||||
|
let reg_type = reg.get_type(tid).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, col_ptr, &comp)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,11 @@ pub mod wrappers;
|
||||||
pub mod proxy;
|
pub mod proxy;
|
||||||
pub use proxy::*;
|
pub use proxy::*;
|
||||||
|
|
||||||
|
pub mod ecs;
|
||||||
|
|
||||||
|
mod entity_ref;
|
||||||
|
pub use entity_ref::*;
|
||||||
|
|
||||||
pub mod system;
|
pub mod system;
|
||||||
pub use system::*;
|
pub use system::*;
|
||||||
use wrappers::{LuaHandleWrapper, LuaResHandleToComponent, LuaWrappedEventProxy};
|
use wrappers::{LuaHandleWrapper, LuaResHandleToComponent, LuaWrappedEventProxy};
|
||||||
|
@ -42,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 {
|
||||||
|
@ -86,6 +93,21 @@ 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.
|
||||||
|
///
|
||||||
|
/// The function must match the following definition: `fn(ScriptWorldPtr, Entity) -> LuaValue`.
|
||||||
|
///
|
||||||
|
/// When `nil` is returned, its considered that the query will not result in anything for this
|
||||||
|
/// [`View`], **no matter the entity**. When the query is used in a [`View`] and returns `nil`,
|
||||||
|
/// it will NOT check for other entities. This is used in the [`ResQuery`] Lua query. If the
|
||||||
|
/// resource is missing, it will always be missing for the [`View`], no matter the entity.
|
||||||
|
///
|
||||||
|
/// If it returns a boolean, the query will act as a filter. The boolean value will not be in the
|
||||||
|
/// result. When the boolean is `false`, other entities will be checked by the [`View`].
|
||||||
|
///
|
||||||
|
/// 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,
|
||||||
|
@ -271,6 +293,16 @@ impl mlua::UserData for ScriptBorrow {
|
||||||
|
|
||||||
/// Helper function used for reflecting userdata as a ScriptBorrow
|
/// Helper function used for reflecting userdata as a ScriptBorrow
|
||||||
pub fn reflect_user_data(ud: &mlua::AnyUserData) -> ScriptBorrow {
|
pub fn reflect_user_data(ud: &mlua::AnyUserData) -> ScriptBorrow {
|
||||||
|
let ud_name = ud.metatable().and_then(|mt| mt.get::<String>(mlua::MetaMethod::Type))
|
||||||
|
.unwrap_or("Unknown".to_string());
|
||||||
ud.call_method::<ScriptBorrow>(FN_NAME_INTERNAL_REFLECT, ())
|
ud.call_method::<ScriptBorrow>(FN_NAME_INTERNAL_REFLECT, ())
|
||||||
.expect("Type does not implement internal reflect method properly")
|
.unwrap_or_else(|_| panic!("UserData of name '{}' does not implement internal reflect method properly", ud_name))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function used for reflecting userdata type as a ScriptBorrow
|
||||||
|
pub fn reflect_type_user_data(ud: &mlua::AnyUserData) -> ScriptBorrow {
|
||||||
|
let ud_name = ud.metatable().and_then(|mt| mt.get::<String>(mlua::MetaMethod::Type))
|
||||||
|
.unwrap_or("Unknown".to_string());
|
||||||
|
ud.call_function::<ScriptBorrow>(FN_NAME_INTERNAL_REFLECT_TYPE, ())
|
||||||
|
.unwrap_or_else(|_| panic!("UserData of name '{}' does not implement internal reflect type function properly", ud_name))
|
||||||
}
|
}
|
|
@ -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, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE}, ScriptApiProvider, ScriptBorrow, ScriptData, ScriptDynamicBundle, ScriptWorldPtr};
|
use crate::{lua::{ecs::{query::{LuaChangedQuery, LuaHasQuery, LuaNotQuery, LuaOptionalQuery, LuaOrQuery, LuaResQuery, LuaTickOfQuery}, 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>()?)?;
|
||||||
|
@ -42,14 +44,22 @@ impl ScriptApiProvider for LyraEcsApiProvider {
|
||||||
globals.set("SceneHandler", ctx.create_proxy::<LuaSceneHandle>()?)?;
|
globals.set("SceneHandler", ctx.create_proxy::<LuaSceneHandle>()?)?;
|
||||||
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("ResQuery", ctx.create_proxy::<LuaResQuery>()?)?;
|
||||||
|
globals.set("ChangedQuery", ctx.create_proxy::<LuaChangedQuery>()?)?;
|
||||||
|
globals.set("HasQuery", ctx.create_proxy::<LuaHasQuery>()?)?;
|
||||||
|
globals.set("NotQuery", ctx.create_proxy::<LuaNotQuery>()?)?;
|
||||||
|
globals.set("AnyQuery", ctx.create_proxy::<LuaOrQuery>()?)?;
|
||||||
|
globals.set("TickOfQuery", ctx.create_proxy::<LuaTickOfQuery>()?)?;
|
||||||
|
globals.set("OptionalQuery", ctx.create_proxy::<LuaOptionalQuery>()?)?;
|
||||||
|
|
||||||
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")?;
|
||||||
expose_comp_table_wrapper::<LuaWorldTransform>(&ctx, &globals, "WorldTransform")?;
|
expose_comp_table_wrapper::<LuaWorldTransform>(&ctx, &globals, "WorldTransform")?;
|
||||||
expose_table_wrapper::<LuaDeviceEvent>(&ctx, &globals, "DeviceEvent")?;
|
expose_table_wrapper::<LuaDeviceEvent>(&ctx, &globals, "DeviceEvent")?;
|
||||||
|
|
||||||
let dt_table = create_reflect_table::<lyra_game::DeltaTime>(&ctx)?;
|
expose_resource_table_wrapper::<lyra_game::DeltaTime>(&ctx, &globals, "DeltaTime")?;
|
||||||
globals.set("DeltaTime", dt_table)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -63,13 +73,14 @@ impl ScriptApiProvider for LyraEcsApiProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_reflect_table<T: Reflect + ResourceObject + Default + 'static>(lua: &mlua::Lua) -> mlua::Result<mlua::Table> {
|
fn expose_resource_table_wrapper<T: Reflect + ResourceObject + Default + 'static>(lua: &mlua::Lua, globals: &mlua::Table, name: &str) -> mlua::Result<()> {
|
||||||
let table = lua.create_table()?;
|
let table = lua.create_table()?;
|
||||||
table.set(FN_NAME_INTERNAL_REFLECT_TYPE, lua.create_function(|_, ()| {
|
table.set(FN_NAME_INTERNAL_REFLECT_TYPE, lua.create_function(|_, ()| {
|
||||||
Ok(ScriptBorrow::from_resource::<T>(None))
|
Ok(ScriptBorrow::from_resource::<T>(None))
|
||||||
})?)?;
|
})?)?;
|
||||||
|
|
||||||
Ok(table)
|
globals.set(name, table)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_reflect_comp_table<T>(lua: &mlua::Lua, name: &str) -> mlua::Result<mlua::Table>
|
fn create_reflect_comp_table<T>(lua: &mlua::Lua, name: &str) -> mlua::Result<mlua::Table>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{any::TypeId, collections::HashMap, ptr::NonNull};
|
use std::{any::TypeId, collections::HashMap, ptr::NonNull};
|
||||||
|
|
||||||
use mlua::{ObjectLike, IntoLua};
|
use mlua::ObjectLike;
|
||||||
use lyra_ecs::{ComponentInfo, DynamicBundle};
|
use lyra_ecs::{ComponentInfo, DynamicBundle};
|
||||||
use lyra_reflect::Reflect;
|
use lyra_reflect::Reflect;
|
||||||
|
|
||||||
|
@ -78,9 +78,9 @@ pub struct TypeLookup {
|
||||||
/// A struct used for Proxying types to and from Lua.
|
/// A struct used for Proxying types to and from Lua.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ReflectLuaProxy {
|
pub struct ReflectLuaProxy {
|
||||||
pub fn_as_lua:
|
fn_as_lua:
|
||||||
for<'a> fn(lua: &'a mlua::Lua, this_ptr: NonNull<()>) -> mlua::Result<mlua::Value>,
|
for<'a> fn(lua: &'a mlua::Lua, this_ptr: NonNull<()>) -> mlua::Result<mlua::Value>,
|
||||||
pub fn_apply: for<'a> fn(
|
fn_apply: for<'a> fn(
|
||||||
lua: &'a mlua::Lua,
|
lua: &'a mlua::Lua,
|
||||||
this_ptr: NonNull<()>,
|
this_ptr: NonNull<()>,
|
||||||
value: &'a mlua::Value,
|
value: &'a mlua::Value,
|
||||||
|
@ -125,6 +125,16 @@ impl ReflectLuaProxy {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reflect the pointer to get a Lua value.
|
||||||
|
pub fn as_lua(&self, lua: &mlua::Lua, this_ptr: NonNull<()>) -> mlua::Result<mlua::Value> {
|
||||||
|
(self.fn_as_lua)(lua, this_ptr)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the contents in the pointer to a Lua value.
|
||||||
|
pub fn apply(&self, lua: &mlua::Lua, this_ptr: NonNull<()>, value: &mlua::Value) -> mlua::Result<()> {
|
||||||
|
(self.fn_apply)(lua, this_ptr, value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl mlua::FromLua for ScriptDynamicBundle {
|
impl mlua::FromLua for ScriptDynamicBundle {
|
||||||
|
|
|
@ -1,19 +1,16 @@
|
||||||
use std::{ops::DerefMut, ptr::NonNull, sync::Arc};
|
use std::{ops::DerefMut, sync::Arc};
|
||||||
|
|
||||||
use crate::{ScriptBorrow, ScriptEntity, ScriptWorldPtr};
|
use crate::{ScriptBorrow, ScriptEntity, ScriptWorldPtr};
|
||||||
use lyra_ecs::{
|
use lyra_ecs::{CommandQueue, Commands, DynamicBundle, World};
|
||||||
query::dynamic::{DynamicViewState, DynamicViewStateIter, QueryDynamicType},
|
|
||||||
CommandQueue, Commands, DynamicBundle, World,
|
|
||||||
};
|
|
||||||
use lyra_reflect::{ReflectWorldExt, RegisteredType, TypeRegistry};
|
use lyra_reflect::{ReflectWorldExt, RegisteredType, TypeRegistry};
|
||||||
use lyra_resource::ResourceManager;
|
use lyra_resource::ResourceManager;
|
||||||
use mlua::{IntoLua, ObjectLike};
|
use mlua::{IntoLua, ObjectLike};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
reflect_user_data,
|
ecs::{View, ViewOneResult, ViewResult},
|
||||||
wrappers::{LuaResHandleToComponent, LuaWrappedEventProxy},
|
wrappers::{LuaResHandleToComponent, LuaTick, LuaWrappedEventProxy},
|
||||||
Error, ReflectLuaProxy, ReflectedIterator, TypeLookup, FN_NAME_INTERNAL_AS_COMPONENT,
|
Error, ReflectLuaProxy, TypeLookup, FN_NAME_INTERNAL_AS_COMPONENT, FN_NAME_INTERNAL_REFLECT,
|
||||||
FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE,
|
FN_NAME_INTERNAL_REFLECT_TYPE,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl mlua::FromLua for ScriptEntity {
|
impl mlua::FromLua for ScriptEntity {
|
||||||
|
@ -101,146 +98,6 @@ impl mlua::UserData for ScriptWorldPtr {
|
||||||
|
|
||||||
Ok(ScriptEntity(entity))
|
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),
|
|
||||||
reflected_components: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
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.fn_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,)| {
|
methods.add_method_mut("resource", |lua, this, (ty,): (mlua::Value,)| {
|
||||||
let reflect = match ty {
|
let reflect = match ty {
|
||||||
mlua::Value::UserData(ud) => ud
|
mlua::Value::UserData(ud) => ud
|
||||||
|
@ -266,7 +123,9 @@ impl mlua::UserData for ScriptWorldPtr {
|
||||||
.get_data::<ReflectLuaProxy>()
|
.get_data::<ReflectLuaProxy>()
|
||||||
.expect("Type does not have ReflectLuaProxy as a TypeData");
|
.expect("Type does not have ReflectLuaProxy as a TypeData");
|
||||||
|
|
||||||
(proxy.fn_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 {
|
} else {
|
||||||
// if the resource is not found in the world, return nil
|
// if the resource is not found in the world, return nil
|
||||||
Ok(mlua::Value::Nil)
|
Ok(mlua::Value::Nil)
|
||||||
|
@ -353,5 +212,18 @@ impl mlua::UserData for ScriptWorldPtr {
|
||||||
data.reader(&mut world).into_lua(lua)
|
data.reader(&mut world).into_lua(lua)
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
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)
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,4 +23,7 @@ mod events;
|
||||||
pub use events::*;
|
pub use events::*;
|
||||||
|
|
||||||
mod world_transform;
|
mod world_transform;
|
||||||
pub use world_transform::*;
|
pub use world_transform::*;
|
||||||
|
|
||||||
|
mod tick;
|
||||||
|
pub use tick::*;
|
|
@ -0,0 +1,55 @@
|
||||||
|
|
||||||
|
use std::any::TypeId;
|
||||||
|
|
||||||
|
use lyra_ecs::Tick;
|
||||||
|
use crate::lua::LuaWrapper;
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct LuaTick(pub(crate) Tick);
|
||||||
|
|
||||||
|
impl std::ops::Deref for LuaTick {
|
||||||
|
type Target = Tick;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::DerefMut for LuaTick {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl mlua::FromLua for LuaTick {
|
||||||
|
fn from_lua(v: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
|
||||||
|
let tyname = v.type_name();
|
||||||
|
let num = v.as_number()
|
||||||
|
.ok_or(mlua::Error::FromLuaConversionError { from: tyname, to: "Tick".into(), message: None })?;
|
||||||
|
Ok(Self(Tick::from(num as u64)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl mlua::IntoLua for LuaTick {
|
||||||
|
fn into_lua(self, _: &mlua::Lua) -> mlua::Result<mlua::Value> {
|
||||||
|
Ok(mlua::Value::Number(*self.0 as f64))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaWrapper for LuaTick {
|
||||||
|
type Wrap = Tick;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn wrapped_type_id() -> std::any::TypeId {
|
||||||
|
TypeId::of::<Tick>()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn into_wrapped(self) -> Self::Wrap {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_wrapped(wrap: Self::Wrap) -> Option<Self> {
|
||||||
|
Some(Self(wrap))
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue