Merge pull request 'Implement relationships in the ECS' (#3) from feature/ecs-relations into main
Reviewed-on: #3
This commit is contained in:
commit
ad82f61cf4
|
@ -1,8 +1,5 @@
|
||||||
use lyra_engine::{
|
use lyra_engine::{
|
||||||
game::Game,
|
ecs::{query::{Res, View}, Component}, game::Game, input::ActionHandler, math::{EulerRot, Quat, Vec3}, plugin::Plugin, scene::CameraComponent, DeltaTime
|
||||||
input::{ActionHandler, CommonActionLabel},
|
|
||||||
math::{Quat, Vec3, EulerRot},
|
|
||||||
plugin::Plugin, ecs::{Component, query::{Res, View}}, DeltaTime, scene::CameraComponent,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* enum FreeFlyCameraActions {
|
/* enum FreeFlyCameraActions {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::ptr::NonNull;
|
use std::ptr::NonNull;
|
||||||
|
|
||||||
use lyra_engine::{math::{self, Vec3}, math::Transform, input::{KeyCode, ActionHandler, Action, ActionKind, LayoutId, ActionMapping, ActionSource, ActionMappingId, InputActionPlugin, MouseInput, MouseAxis, CommonActionLabel}, game::Game, render::{window::{CursorGrabMode, WindowOptions}, light::{PointLight, directional::DirectionalLight, SpotLight}}, change_tracker::Ct, ecs::{system::{Criteria, CriteriaSchedule, BatchedSystem, IntoSystem}, world::World, Component}, DeltaTime, scene::{ModelComponent, CameraComponent}, lua::{LuaScriptingPlugin, LuaScript}, Script, ScriptList};
|
use lyra_engine::{math::{self, Vec3}, math::Transform, input::{KeyCode, ActionHandler, Action, ActionKind, LayoutId, ActionMapping, ActionSource, ActionMappingId, InputActionPlugin, MouseInput, MouseAxis, CommonActionLabel}, game::Game, render::{window::{CursorGrabMode, WindowOptions}, light::{PointLight, directional::DirectionalLight, SpotLight}}, change_tracker::Ct, ecs::{system::{Criteria, CriteriaSchedule, BatchedSystem, IntoSystem}, World, Component}, DeltaTime, scene::{ModelComponent, CameraComponent}, lua::{LuaScriptingPlugin, LuaScript}, Script, ScriptList};
|
||||||
use lyra_engine::assets::{ResourceManager, Model};
|
use lyra_engine::assets::{ResourceManager, Model};
|
||||||
|
|
||||||
mod free_fly_camera;
|
mod free_fly_camera;
|
||||||
|
@ -88,6 +88,7 @@ async fn main() {
|
||||||
//let cube_model = resman.request::<Model>("assets/cube-texture-bin.glb").unwrap();
|
//let cube_model = resman.request::<Model>("assets/cube-texture-bin.glb").unwrap();
|
||||||
let cube_model = resman.request::<Model>("assets/texture-sep/texture-sep.gltf").unwrap();
|
let cube_model = resman.request::<Model>("assets/texture-sep/texture-sep.gltf").unwrap();
|
||||||
let crate_model = resman.request::<Model>("assets/crate/crate.gltf").unwrap();
|
let crate_model = resman.request::<Model>("assets/crate/crate.gltf").unwrap();
|
||||||
|
//let sponza_model = resman.request::<Model>("assets/sponza/Sponza.gltf").unwrap();
|
||||||
drop(resman);
|
drop(resman);
|
||||||
|
|
||||||
/* world.spawn((
|
/* world.spawn((
|
||||||
|
@ -95,6 +96,11 @@ async fn main() {
|
||||||
Transform::from_xyz(0.0, -5.0, -10.0),
|
Transform::from_xyz(0.0, -5.0, -10.0),
|
||||||
)); */
|
)); */
|
||||||
|
|
||||||
|
/* world.spawn((
|
||||||
|
ModelComponent(sponza_model),
|
||||||
|
Transform::from_xyz(0.0, 0.0, 0.0),
|
||||||
|
)); */
|
||||||
|
|
||||||
{
|
{
|
||||||
let cube_tran = Transform::from_xyz(-3.5, 0.0, -8.0);
|
let cube_tran = Transform::from_xyz(-3.5, 0.0, -8.0);
|
||||||
//cube_tran.rotate_y(math::Angle::Degrees(180.0));
|
//cube_tran.rotate_y(math::Angle::Degrees(180.0));
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{ptr::{NonNull, self}, alloc::{self, Layout, alloc, dealloc}, mem, collections::HashMap, cell::{RefCell, Ref, RefMut, BorrowError, BorrowMutError}, ops::{DerefMut, Deref}};
|
use std::{ptr::{NonNull, self}, alloc::{self, Layout, alloc, dealloc}, mem, collections::HashMap, cell::{RefCell, Ref, RefMut, BorrowError, BorrowMutError}, ops::DerefMut};
|
||||||
|
|
||||||
use crate::{world::ArchetypeEntityId, bundle::Bundle, component_info::ComponentInfo, DynTypeId, Tick, Entity};
|
use crate::{world::ArchetypeEntityId, bundle::Bundle, component_info::ComponentInfo, DynTypeId, Tick, Entity};
|
||||||
|
|
||||||
|
@ -18,9 +18,8 @@ impl Drop for ComponentColumn {
|
||||||
let data = data.as_ptr();
|
let data = data.as_ptr();
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
// layout of current alloc
|
// TODO: trigger drop on the components
|
||||||
let layout = Layout::from_size_align_unchecked(self.info.layout.size * self.capacity,
|
let layout = self.info.layout();
|
||||||
self.info.layout.alignment);
|
|
||||||
dealloc(data, layout);
|
dealloc(data, layout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,7 +41,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.into_layout().unwrap(), capacity);
|
let data = ComponentColumn::alloc(info.layout(), capacity);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
data: RefCell::new(data),
|
data: RefCell::new(data),
|
||||||
|
@ -64,8 +63,9 @@ 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 dest = NonNull::new_unchecked(data.as_ptr().add(entity_index * self.info.layout.size));
|
let size = self.info.layout().size();
|
||||||
ptr::copy_nonoverlapping(comp_src.as_ptr(), dest.as_ptr(), self.info.layout.size);
|
let dest = NonNull::new_unchecked(data.as_ptr().add(entity_index * size));
|
||||||
|
ptr::copy_nonoverlapping(comp_src.as_ptr(), dest.as_ptr(), size);
|
||||||
|
|
||||||
// check if a component spot is being set twice and that the entity's tick is
|
// check if a component spot is being set twice and that the entity's tick is
|
||||||
// already stored
|
// already stored
|
||||||
|
@ -81,31 +81,33 @@ impl ComponentColumn {
|
||||||
/// # 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) -> &T {
|
pub unsafe fn get<T>(&self, entity_index: usize) -> Ref<T> {
|
||||||
let data = self.data.borrow();
|
let data = self.data.borrow();
|
||||||
let data = data.deref();
|
|
||||||
|
|
||||||
|
Ref::map(data, |data| {
|
||||||
let ptr = NonNull::new_unchecked(data.as_ptr()
|
let ptr = NonNull::new_unchecked(data.as_ptr()
|
||||||
.add(entity_index * self.info.layout.size))
|
.add(entity_index * self.info.layout().size()))
|
||||||
.cast();
|
.cast();
|
||||||
&*ptr.as_ptr()
|
&*ptr.as_ptr()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a component at an entities index.
|
/// 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) -> &mut 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);
|
||||||
|
|
||||||
let mut data = self.data.borrow_mut();
|
let data = self.data.borrow_mut();
|
||||||
let data = data.deref_mut();
|
|
||||||
|
|
||||||
let p = data.as_ptr()
|
RefMut::map(data, |data| {
|
||||||
.cast::<T>()
|
let ptr = NonNull::new_unchecked(data.as_ptr()
|
||||||
.add(entity_index * self.info.layout.size);
|
.add(entity_index * self.info.layout().size()))
|
||||||
&mut *p
|
.cast();
|
||||||
|
&mut *ptr.as_ptr()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Grow the column to fit `new_capacity` amount of components.
|
/// Grow the column to fit `new_capacity` amount of components.
|
||||||
|
@ -123,17 +125,19 @@ impl ComponentColumn {
|
||||||
|
|
||||||
let mut data = self.data.borrow_mut();
|
let mut data = self.data.borrow_mut();
|
||||||
|
|
||||||
let mut new_ptr = Self::alloc(self.info.layout.into_layout().unwrap(), new_capacity);
|
let layout = self.info.layout();
|
||||||
|
let mut new_ptr = Self::alloc(layout, new_capacity);
|
||||||
|
|
||||||
if self.len > 0 {
|
if self.len > 0 {
|
||||||
ptr::copy_nonoverlapping(data.as_ptr(), new_ptr.as_ptr(), self.len * self.info.layout.size);
|
ptr::copy_nonoverlapping(data.as_ptr(), new_ptr.as_ptr(), self.len * layout.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
// dont attempt to free if we weren't able to store anything anyway
|
// dont attempt to free if we weren't able to store anything anyway
|
||||||
if self.capacity != 0 {
|
if self.capacity != 0 {
|
||||||
|
// 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(
|
||||||
self.info.layout.size.checked_mul(self.capacity).unwrap(),
|
layout.size().checked_mul(self.capacity).unwrap(),
|
||||||
self.info.layout.alignment
|
layout.align()
|
||||||
);
|
);
|
||||||
|
|
||||||
mem::swap(data.deref_mut(), &mut new_ptr);
|
mem::swap(data.deref_mut(), &mut new_ptr);
|
||||||
|
@ -152,15 +156,16 @@ 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 mut old_comp_ptr = NonNull::new_unchecked(data.as_ptr()
|
let mut old_comp_ptr = NonNull::new_unchecked(data.as_ptr()
|
||||||
.add(entity_index * self.info.layout.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 * self.info.layout.size));
|
.add(moved_index * size));
|
||||||
|
|
||||||
ptr::copy_nonoverlapping(new_comp_ptr.as_ptr(), old_comp_ptr.as_ptr(), self.info.layout.size);
|
ptr::copy_nonoverlapping(new_comp_ptr.as_ptr(), old_comp_ptr.as_ptr(), size);
|
||||||
|
|
||||||
mem::swap(&mut old_comp_ptr, &mut new_comp_ptr); // new_comp_ptr is now the old ptr
|
mem::swap(&mut old_comp_ptr, &mut new_comp_ptr); // new_comp_ptr is now the old ptr
|
||||||
|
|
||||||
|
@ -193,6 +198,7 @@ impl ComponentColumn {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An id of an Archetype
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)]
|
||||||
pub struct ArchetypeId(pub u64);
|
pub struct ArchetypeId(pub u64);
|
||||||
|
|
||||||
|
@ -206,20 +212,41 @@ impl ArchetypeId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
/// components as each column. This means you can have tightly packed components of entities and
|
||||||
|
/// quickly iterate through entities with the same components.
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct Archetype {
|
pub struct Archetype {
|
||||||
pub id: ArchetypeId,
|
id: ArchetypeId,
|
||||||
pub(crate) entities: HashMap<Entity, ArchetypeEntityId>,
|
/// The indexes of the entities in the archetype.
|
||||||
|
pub(crate) entity_ids: HashMap<Entity, ArchetypeEntityId>,
|
||||||
/// map an Archetype entity id to an entity
|
/// map an Archetype entity id to an entity
|
||||||
pub(crate) ids_to_entity: HashMap<ArchetypeEntityId, Entity>,
|
//pub(crate) ids_to_entity: HashMap<ArchetypeEntityId, Entity>,
|
||||||
|
/// The entities in the Archetype.
|
||||||
|
///
|
||||||
|
/// Can be used to map `ArchetypeEntityId` to an Entity since `ArchetypeEntityId` has
|
||||||
|
/// the index that the entity is stored at.
|
||||||
|
pub(crate) entities: Vec<Entity>,
|
||||||
pub(crate) columns: Vec<ComponentColumn>,
|
pub(crate) columns: Vec<ComponentColumn>,
|
||||||
pub capacity: usize,
|
capacity: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The default capacity of the columns
|
/// The default capacity of the columns
|
||||||
const DEFAULT_CAPACITY: usize = 32;
|
const DEFAULT_CAPACITY: usize = 32;
|
||||||
|
|
||||||
impl Archetype {
|
impl Archetype {
|
||||||
|
/// Returns the id of the Archetype
|
||||||
|
pub fn id(&self) -> ArchetypeId {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the max amount of Entities that the Archetype can store without reallocating.
|
||||||
|
pub fn capacity(&self) -> usize {
|
||||||
|
self.capacity
|
||||||
|
}
|
||||||
|
|
||||||
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.into_iter().map(|i| {
|
||||||
unsafe { ComponentColumn::new(i, DEFAULT_CAPACITY) }
|
unsafe { ComponentColumn::new(i, DEFAULT_CAPACITY) }
|
||||||
|
@ -227,8 +254,8 @@ impl Archetype {
|
||||||
|
|
||||||
Archetype {
|
Archetype {
|
||||||
id: new_id,
|
id: new_id,
|
||||||
entities: HashMap::new(),
|
entity_ids: HashMap::new(),
|
||||||
ids_to_entity: HashMap::new(),
|
entities: Vec::new(),
|
||||||
columns,
|
columns,
|
||||||
capacity: DEFAULT_CAPACITY,
|
capacity: DEFAULT_CAPACITY,
|
||||||
}
|
}
|
||||||
|
@ -243,15 +270,16 @@ impl Archetype {
|
||||||
where
|
where
|
||||||
B: Bundle
|
B: Bundle
|
||||||
{
|
{
|
||||||
if self.capacity == self.entities.len() {
|
if self.capacity == self.entity_ids.len() {
|
||||||
let new_cap = self.capacity * 2;
|
let new_cap = self.capacity * 2;
|
||||||
self.grow_columns(new_cap);
|
self.grow_columns(new_cap);
|
||||||
self.capacity = new_cap;
|
self.capacity = new_cap;
|
||||||
}
|
}
|
||||||
|
|
||||||
let entity_index = ArchetypeEntityId(self.entities.len() as u64);
|
debug_assert_eq!(self.entity_ids.len(), self.entities.len(), "Somehow the Archetype's entity storage got unsynced");
|
||||||
self.entities.insert(entity, entity_index);
|
let entity_index = ArchetypeEntityId(self.entity_ids.len() as u64);
|
||||||
self.ids_to_entity.insert(entity_index, entity);
|
self.entity_ids.insert(entity, entity_index);
|
||||||
|
self.entities.push(entity);
|
||||||
|
|
||||||
bundle.take(|data, type_id, _size| {
|
bundle.take(|data, type_id, _size| {
|
||||||
let col = self.get_column_mut(type_id).unwrap();
|
let col = self.get_column_mut(type_id).unwrap();
|
||||||
|
@ -262,61 +290,64 @@ impl Archetype {
|
||||||
entity_index
|
entity_index
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes an entity from the Archetype and frees its components. Returns the entity record that took its place in the component column.
|
/// Removes an entity from the Archetype and frees its components. Returns the entity record
|
||||||
|
/// that took its place in the component column.
|
||||||
pub(crate) fn remove_entity(&mut self, entity: Entity, tick: &Tick) -> Option<(Entity, ArchetypeEntityId)> {
|
pub(crate) fn remove_entity(&mut self, entity: Entity, tick: &Tick) -> Option<(Entity, ArchetypeEntityId)> {
|
||||||
let entity_index = *self.entities.get(&entity)
|
let entity_index = *self.entity_ids.get(&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;
|
||||||
|
|
||||||
for c in self.columns.iter_mut() {
|
for c in self.columns.iter_mut() {
|
||||||
let moved_entity = unsafe { c.remove_component(entity_index.0 as usize, tick) };
|
let moved_entity = unsafe { c.remove_component(entity_index.0 as usize, tick) };
|
||||||
|
|
||||||
// Make sure that the moved entity is the same as what was moved in other columns.
|
|
||||||
// If this is the first move, find the EntityId that points to the column index.
|
|
||||||
// If there wasn't a moved entity, make sure no other columns moved something.
|
|
||||||
if let Some(res) = moved_entity {
|
if let Some(res) = moved_entity {
|
||||||
|
|
||||||
if let Some((_, aid)) = removed_entity {
|
if let Some((_, aid)) = removed_entity {
|
||||||
assert!(res as u64 == aid.0); // make sure all columns removed the same entity
|
// Make sure that the moved entity is the same as what was moved in other columns.
|
||||||
|
assert!(res as u64 == aid.0);
|
||||||
} else {
|
} else {
|
||||||
let replaced_entity = self.entities.iter().find(|(_, a)| a.0 == res as u64)
|
// This is the first move, so find the EntityId that points to the column index.
|
||||||
.map(|(e, _a)| *e).expect("Failure to find entity for moved component!");
|
let just_removed = self.entities[res];
|
||||||
removed_entity = Some((replaced_entity, ArchetypeEntityId(res as u64)));
|
removed_entity = Some((just_removed, ArchetypeEntityId(res as u64)));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// 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.entities.remove(&entity).unwrap();
|
self.entity_ids.remove(&entity).unwrap();
|
||||||
self.ids_to_entity.remove(&entity_index).unwrap();
|
if self.entities.len() > 1 {
|
||||||
|
let len = self.entities.len();
|
||||||
|
self.entities.swap(entity_index.0 as _, len - 1);
|
||||||
|
}
|
||||||
|
self.entities.pop().unwrap();
|
||||||
|
|
||||||
// now change the ArchetypeEntityId to be the index that the moved entity was moved into.
|
// now change the ArchetypeEntityId to be the index that the moved entity was moved into.
|
||||||
removed_entity.map(|(e, _a)| (e, entity_index))
|
removed_entity.map(|(e, _a)| (e, entity_index))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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.iter().all(|c| types.contains(&c.info.type_id()))
|
||||||
} else { false }
|
} 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`
|
||||||
pub fn has_column(&self, comp_type: DynTypeId) -> bool {
|
pub fn has_column<I: Into<DynTypeId>>(&self, comp_type: I) -> bool {
|
||||||
self.columns.iter().any(|c| comp_type == c.info.type_id)
|
let comp_type = comp_type.into();
|
||||||
|
self.columns.iter().any(|c| comp_type == c.info.type_id())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a boolean indicating whether this archetype is empty or not.
|
/// Returns a boolean indicating whether this archetype is empty or not.
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.entities.is_empty()
|
self.entity_ids.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the amount of entities that are stored in the archetype.
|
/// Returns the amount of entities that are stored in the archetype.
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.entities.len()
|
self.entity_ids.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Grows columns in the archetype
|
/// Grows columns in the archetype
|
||||||
|
@ -337,28 +368,32 @@ impl Archetype {
|
||||||
self.capacity = new_capacity;
|
self.capacity = new_capacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_column(&self, type_id: DynTypeId) -> Option<&ComponentColumn> {
|
/// Attempts to find the column storing components of `type_id`
|
||||||
self.columns.iter().find(|c| c.info.type_id == type_id)
|
pub fn get_column<I: Into<DynTypeId>>(&self, type_id: I) -> Option<&ComponentColumn> {
|
||||||
|
let type_id = type_id.into();
|
||||||
|
self.columns.iter().find(|c| c.info.type_id() == type_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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(&mut self, type_id: DynTypeId) -> Option<&mut ComponentColumn> {
|
pub fn get_column_mut<I: Into<DynTypeId>>(&mut self, type_id: I) -> Option<&mut ComponentColumn> {
|
||||||
self.columns.iter_mut().find(|c| c.info.type_id == type_id)
|
let type_id = type_id.into();
|
||||||
|
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
|
||||||
pub fn reserve_one(&mut self, entity: Entity) -> ArchetypeEntityId {
|
pub fn reserve_one(&mut self, entity: Entity) -> ArchetypeEntityId {
|
||||||
if self.capacity == self.entities.len() {
|
if self.capacity == self.entity_ids.len() {
|
||||||
let new_cap = self.capacity * 2;
|
let new_cap = self.capacity * 2;
|
||||||
self.grow_columns(new_cap);
|
self.grow_columns(new_cap);
|
||||||
self.capacity = new_cap;
|
self.capacity = new_cap;
|
||||||
}
|
}
|
||||||
|
|
||||||
let entity_index = ArchetypeEntityId(self.entities.len() as u64);
|
debug_assert_eq!(self.entity_ids.len(), self.entities.len(), "Somehow the Archetype's entity storage got unsynced");
|
||||||
self.entities.insert(entity, entity_index);
|
let entity_index = ArchetypeEntityId(self.entity_ids.len() as u64);
|
||||||
self.ids_to_entity.insert(entity_index, entity);
|
self.entity_ids.insert(entity, entity_index);
|
||||||
|
self.entities.push(entity);
|
||||||
|
|
||||||
for col in self.columns.iter_mut() {
|
for col in self.columns.iter_mut() {
|
||||||
col.len += 1;
|
col.len += 1;
|
||||||
|
@ -370,8 +405,8 @@ impl Archetype {
|
||||||
/// 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`].
|
/// 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`]
|
/// It was done this way because I had some borrow check issues when writing [`World::insert`](crate::World)
|
||||||
/// related to borrowing mutably from self more than once.
|
/// related to borrowing mutably from self more than once.
|
||||||
/* pub fn move_into<B>(&self, into_arch: &mut Archetype, entity: Entity, new_components: B)
|
/* pub fn move_into<B>(&self, into_arch: &mut Archetype, entity: Entity, new_components: B)
|
||||||
where
|
where
|
||||||
|
@ -404,22 +439,29 @@ impl Archetype {
|
||||||
//self.remove_entity(entity);
|
//self.remove_entity(entity);
|
||||||
} */
|
} */
|
||||||
|
|
||||||
pub fn entities(&self) -> &HashMap<Entity, ArchetypeEntityId> {
|
/// Returns a borrow to the map used to find the column indices of the entity.
|
||||||
&self.entities
|
pub fn entity_indexes(&self) -> &HashMap<Entity, ArchetypeEntityId> {
|
||||||
|
&self.entity_ids
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn entity_of_index(&self, id: ArchetypeEntityId) -> Option<Entity> {
|
/// Returns the Entity that is stored at the column matching `id`.
|
||||||
self.ids_to_entity.get(&id).cloned()
|
pub fn entity_at_index(&self, id: ArchetypeEntityId) -> Option<Entity> {
|
||||||
|
self.entities.get(id.0 as usize).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a boolean indicating if the Archetype is storing the entity.
|
||||||
|
pub fn has_entity(&self, e: Entity) -> bool {
|
||||||
|
self.entity_ids.contains_key(&e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::{alloc::Layout, ptr::NonNull};
|
use std::{alloc::Layout, cell::Ref, ptr::NonNull};
|
||||||
|
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
|
||||||
use crate::{bundle::Bundle, tests::{Vec2, Vec3}, ComponentInfo, DynTypeId, DynamicBundle, Entity, EntityId, MemoryLayout, Tick};
|
use crate::{bundle::Bundle, tests::{Vec2, Vec3}, ComponentInfo, DynTypeId, DynamicBundle, Entity, EntityId, Tick};
|
||||||
|
|
||||||
use super::Archetype;
|
use super::Archetype;
|
||||||
|
|
||||||
|
@ -435,7 +477,7 @@ mod tests {
|
||||||
let entity_arch_id = a.add_entity(entity, bundle, &Tick::default());
|
let entity_arch_id = a.add_entity(entity, bundle, &Tick::default());
|
||||||
|
|
||||||
let col = a.columns.get(0).unwrap();
|
let col = a.columns.get(0).unwrap();
|
||||||
let vec2: &Vec2 = unsafe { col.get(entity_arch_id.0 as usize) };
|
let vec2: Ref<Vec2> = unsafe { col.get(entity_arch_id.0 as usize) };
|
||||||
assert_eq!(vec2.clone(), bundle.0);
|
assert_eq!(vec2.clone(), bundle.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -451,11 +493,11 @@ mod tests {
|
||||||
let entity_arch_id = a.add_entity(entity, bundle, &Tick::default());
|
let entity_arch_id = a.add_entity(entity, bundle, &Tick::default());
|
||||||
|
|
||||||
let col = a.columns.get(0).unwrap();
|
let col = a.columns.get(0).unwrap();
|
||||||
let vec2: &Vec2 = unsafe { col.get(entity_arch_id.0 as usize) };
|
let vec2: Ref<Vec2> = unsafe { col.get(entity_arch_id.0 as usize) };
|
||||||
assert_eq!(vec2.clone(), bundle.0);
|
assert_eq!(vec2.clone(), bundle.0);
|
||||||
|
|
||||||
let col = a.columns.get(1).unwrap();
|
let col = a.columns.get(1).unwrap();
|
||||||
let vec3: &Vec3 = unsafe { col.get(entity_arch_id.0 as usize) };
|
let vec3: Ref<Vec3> = unsafe { col.get(entity_arch_id.0 as usize) };
|
||||||
assert_eq!(vec3.clone(), bundle.1);
|
assert_eq!(vec3.clone(), bundle.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -477,9 +519,9 @@ mod tests {
|
||||||
let earch2 = a.add_entity(e2, b2, &Tick::default());
|
let earch2 = a.add_entity(e2, b2, &Tick::default());
|
||||||
|
|
||||||
let col = a.columns.get(0).unwrap();
|
let col = a.columns.get(0).unwrap();
|
||||||
let vec2: &Vec2 = unsafe { col.get(earch1.0 as usize) };
|
let vec2: Ref<Vec2> = unsafe { col.get(earch1.0 as usize) };
|
||||||
assert_eq!(vec2.clone(), b1.0);
|
assert_eq!(vec2.clone(), b1.0);
|
||||||
let vec2: &Vec2 = unsafe { col.get(earch2.0 as usize) };
|
let vec2: Ref<Vec2> = unsafe { col.get(earch2.0 as usize) };
|
||||||
assert_eq!(vec2.clone(), b2.0);
|
assert_eq!(vec2.clone(), b2.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -501,15 +543,15 @@ mod tests {
|
||||||
let earch2 = a.add_entity(e2, b2, &Tick::default());
|
let earch2 = a.add_entity(e2, b2, &Tick::default());
|
||||||
|
|
||||||
let col = a.columns.get(0).unwrap();
|
let col = a.columns.get(0).unwrap();
|
||||||
let vec2: &Vec2 = unsafe { col.get(earch1.0 as usize) };
|
let vec2: Ref<Vec2> = unsafe { col.get(earch1.0 as usize) };
|
||||||
assert_eq!(vec2.clone(), b1.0);
|
assert_eq!(vec2.clone(), b1.0);
|
||||||
let vec2: &Vec2 = unsafe { col.get(earch2.0 as usize) };
|
let vec2: Ref<Vec2> = unsafe { col.get(earch2.0 as usize) };
|
||||||
assert_eq!(vec2.clone(), b2.0);
|
assert_eq!(vec2.clone(), b2.0);
|
||||||
|
|
||||||
let col = a.columns.get(1).unwrap();
|
let col = a.columns.get(1).unwrap();
|
||||||
let vec3: &Vec3 = unsafe { col.get(earch1.0 as usize) };
|
let vec3: Ref<Vec3> = unsafe { col.get(earch1.0 as usize) };
|
||||||
assert_eq!(vec3.clone(), b1.1);
|
assert_eq!(vec3.clone(), b1.1);
|
||||||
let vec3: &Vec3 = unsafe { col.get(earch2.0 as usize) };
|
let vec3: Ref<Vec3> = unsafe { col.get(earch2.0 as usize) };
|
||||||
assert_eq!(vec3.clone(), b2.1);
|
assert_eq!(vec3.clone(), b2.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -552,7 +594,7 @@ mod tests {
|
||||||
|
|
||||||
let col = a.columns.get(0).unwrap();
|
let col = a.columns.get(0).unwrap();
|
||||||
for i in 0..bundle_count {
|
for i in 0..bundle_count {
|
||||||
let vec2: &Vec2 = unsafe { col.get(i) };
|
let vec2: Ref<Vec2> = unsafe { col.get(i) };
|
||||||
assert_eq!(vec2.clone(), bundles[i].0);
|
assert_eq!(vec2.clone(), bundles[i].0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -599,7 +641,7 @@ mod tests {
|
||||||
/// This test simulates an archetype that stores types that rust does not know about.
|
/// This test simulates an archetype that stores types that rust does not know about.
|
||||||
#[test]
|
#[test]
|
||||||
fn dynamic_archetype() {
|
fn dynamic_archetype() {
|
||||||
let layout = MemoryLayout::from(Layout::new::<u32>());
|
let layout = Layout::new::<u32>();
|
||||||
let info = ComponentInfo::new_unknown(DynTypeId::Unknown(100), "u32", layout);
|
let info = ComponentInfo::new_unknown(DynTypeId::Unknown(100), "u32", layout);
|
||||||
let infos = vec![info.clone()];
|
let infos = vec![info.clone()];
|
||||||
|
|
||||||
|
|
|
@ -143,7 +143,7 @@ impl DynamicBundle {
|
||||||
|
|
||||||
impl Bundle for DynamicBundle {
|
impl Bundle for DynamicBundle {
|
||||||
fn type_ids(&self) -> Vec<DynTypeId> {
|
fn type_ids(&self) -> Vec<DynTypeId> {
|
||||||
self.bundle.iter().map(|b| b.1.type_id).collect()
|
self.bundle.iter().map(|b| b.1.type_id()).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn info(&self) -> Vec<ComponentInfo> {
|
fn info(&self) -> Vec<ComponentInfo> {
|
||||||
|
@ -152,7 +152,7 @@ impl Bundle for DynamicBundle {
|
||||||
|
|
||||||
fn take(self, mut f: impl FnMut(NonNull<u8>, DynTypeId, usize)) {
|
fn take(self, mut f: impl FnMut(NonNull<u8>, DynTypeId, usize)) {
|
||||||
for (data, info) in self.bundle.iter() {
|
for (data, info) in self.bundle.iter() {
|
||||||
f(*data, info.type_id, info.layout.size);
|
f(*data, info.type_id(), info.layout().size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,30 +2,55 @@ use std::{any::Any, cell::RefMut, collections::VecDeque, ptr::{self, NonNull}};
|
||||||
|
|
||||||
use crate::{system::FnArgFetcher, Access, Bundle, Entities, Entity, World};
|
use crate::{system::FnArgFetcher, Access, Bundle, Entities, Entity, World};
|
||||||
|
|
||||||
|
/// A Command be used to delay mutation of the world until after this system is ran.
|
||||||
pub trait Command: Any {
|
pub trait Command: Any {
|
||||||
fn as_any_boxed(self: Box<Self>) -> Box<dyn Any>;
|
fn as_any_boxed(self: Box<Self>) -> Box<dyn Any>;
|
||||||
|
|
||||||
fn run(self, world: &mut World) -> anyhow::Result<()>;
|
/// Executes the command
|
||||||
|
fn run(self, world: &mut World);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<F> Command for F
|
impl<F> Command for F
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut World) -> anyhow::Result<()> + 'static
|
F: FnOnce(&mut World) + 'static
|
||||||
{
|
{
|
||||||
fn as_any_boxed(self: Box<Self>) -> Box<dyn Any> {
|
fn as_any_boxed(self: Box<Self>) -> Box<dyn Any> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(self, world: &mut World) -> anyhow::Result<()> {
|
fn run(self, world: &mut World) {
|
||||||
self(world)
|
self(world)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type RunCommand = unsafe fn(cmd: Box<dyn Command>, world: &mut World) -> anyhow::Result<()>;
|
type RunCommand = unsafe fn(cmd: Box<dyn Command>, world: &mut World);
|
||||||
|
|
||||||
|
/// Stores a queue of commands that will get executed after the system is ran.
|
||||||
|
///
|
||||||
|
/// This struct can be inserted as a resource into the world, and the commands will be
|
||||||
|
/// executed by the [`GraphExecutor`](crate::system::GraphExecutor) after the system is executed.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct CommandQueue(VecDeque<(RunCommand, Box<dyn Command>)>);
|
pub struct CommandQueue(VecDeque<(RunCommand, Box<dyn Command>)>);
|
||||||
|
|
||||||
|
/// Used in a system to queue up commands that will run right after this system.
|
||||||
|
///
|
||||||
|
/// This can be used to delay the mutation of the world until after the system is ran. These
|
||||||
|
/// must be used if you're mutating the world inside a [`ViewState`](crate::query::ViewState).
|
||||||
|
///
|
||||||
|
/// ```nobuild
|
||||||
|
/// fn particle_spawner_system(
|
||||||
|
/// commands: Commands,
|
||||||
|
/// view: ViewState<(&Campfire, &Transform)>
|
||||||
|
/// ) -> anyhow::Result<()> {
|
||||||
|
/// for (campfire, pos) in view.iter() {
|
||||||
|
/// // If you do not use commands to spawn this, the next iteration
|
||||||
|
/// // of the view will cause a segfault.
|
||||||
|
/// commands.spawn((pos, Particle::new(/* ... */)));
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// Ok(())
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
pub struct Commands<'a, 'b> {
|
pub struct Commands<'a, 'b> {
|
||||||
queue: &'b mut CommandQueue,
|
queue: &'b mut CommandQueue,
|
||||||
entities: &'a mut Entities,
|
entities: &'a mut Entities,
|
||||||
|
@ -48,20 +73,18 @@ impl<'a, 'b> Commands<'a, 'b> {
|
||||||
.downcast::<C>()
|
.downcast::<C>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
cmd.run(world)?;
|
cmd.run(world);
|
||||||
|
|
||||||
Ok(())
|
|
||||||
};
|
};
|
||||||
|
|
||||||
self.queue.0.push_back((run_fn, cmd));
|
self.queue.0.push_back((run_fn, cmd));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Spawn an entity into the World. See [`World::spawn`]
|
||||||
pub fn spawn<B: Bundle + 'static>(&mut self, bundle: B) -> Entity {
|
pub fn spawn<B: Bundle + 'static>(&mut self, bundle: B) -> Entity {
|
||||||
let e = self.entities.reserve();
|
let e = self.entities.reserve();
|
||||||
|
|
||||||
self.add(move |world: &mut World| {
|
self.add(move |world: &mut World| {
|
||||||
world.spawn_into(e, bundle);
|
world.spawn_into(e, bundle);
|
||||||
Ok(())
|
|
||||||
});
|
});
|
||||||
|
|
||||||
e
|
e
|
||||||
|
@ -71,7 +94,7 @@ impl<'a, 'b> Commands<'a, 'b> {
|
||||||
pub fn execute(&mut self, world: &mut World) -> anyhow::Result<()> {
|
pub fn execute(&mut self, world: &mut World) -> anyhow::Result<()> {
|
||||||
while let Some((cmd_fn, cmd_ptr)) = self.queue.0.pop_front() {
|
while let Some((cmd_fn, cmd_ptr)) = self.queue.0.pop_front() {
|
||||||
unsafe {
|
unsafe {
|
||||||
cmd_fn(cmd_ptr, world)?;
|
cmd_fn(cmd_ptr, world);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,7 +137,7 @@ pub fn execute_deferred_commands(world: &mut World, mut commands: RefMut<Command
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::ptr::NonNull;
|
use std::{cell::Ref, ptr::NonNull};
|
||||||
|
|
||||||
use crate::{system::{GraphExecutor, IntoSystem}, tests::Vec2, Commands, DynTypeId, World};
|
use crate::{system::{GraphExecutor, IntoSystem}, tests::Vec2, Commands, DynTypeId, World};
|
||||||
|
|
||||||
|
@ -144,7 +167,7 @@ mod tests {
|
||||||
// there's only one archetype
|
// there's only one archetype
|
||||||
let arch = world.archetypes.values().next().unwrap();
|
let arch = world.archetypes.values().next().unwrap();
|
||||||
let col = arch.get_column(DynTypeId::of::<Vec2>()).unwrap();
|
let col = arch.get_column(DynTypeId::of::<Vec2>()).unwrap();
|
||||||
let vec2: &Vec2 = unsafe { col.get(3) };
|
let vec2: Ref<Vec2> = unsafe { col.get(3) };
|
||||||
assert_eq!(vec2.clone(), spawned_vec);
|
assert_eq!(vec2.clone(), spawned_vec);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,41 +1,4 @@
|
||||||
use std::{any::TypeId, alloc::{Layout, LayoutError}};
|
use std::{any::TypeId, alloc::Layout};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
pub struct MemoryLayout {
|
|
||||||
pub size: usize,
|
|
||||||
pub alignment: usize
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryInto<Layout> for MemoryLayout {
|
|
||||||
type Error = LayoutError;
|
|
||||||
|
|
||||||
fn try_into(self) -> Result<Layout, Self::Error> {
|
|
||||||
Layout::from_size_align(self.size, self.alignment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Layout> for MemoryLayout {
|
|
||||||
fn from(value: Layout) -> Self {
|
|
||||||
Self {
|
|
||||||
size: value.size(),
|
|
||||||
alignment: value.align(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MemoryLayout {
|
|
||||||
pub fn new(size: usize, alignment: usize) -> Self {
|
|
||||||
MemoryLayout {
|
|
||||||
size,
|
|
||||||
alignment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts self into a Layout.
|
|
||||||
pub fn into_layout(self) -> Result<Layout, LayoutError> {
|
|
||||||
Layout::from_size_align(self.size, self.alignment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A dynamic type id. Supports types that are not known to Rust.
|
/// A dynamic type id. Supports types that are not known to Rust.
|
||||||
#[derive(Clone, Copy, Hash, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Hash, Debug, PartialEq, Eq)]
|
||||||
|
@ -88,32 +51,38 @@ impl DynTypeId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Some information about a component.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub struct ComponentInfo {
|
pub struct ComponentInfo {
|
||||||
pub type_id: DynTypeId,
|
type_id: DynTypeId,
|
||||||
//pub name: String,
|
layout: Layout,
|
||||||
pub layout: MemoryLayout,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ComponentInfo {
|
impl ComponentInfo {
|
||||||
pub fn new<T: 'static>() -> Self {
|
pub fn new<T: 'static>() -> Self {
|
||||||
Self {
|
Self {
|
||||||
type_id: TypeId::of::<T>().into(),
|
type_id: TypeId::of::<T>().into(),
|
||||||
//name: type_name::<T>().to_string(),
|
layout: Layout::new::<T>(),
|
||||||
layout: MemoryLayout::from(Layout::new::<T>()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create ComponentInfo from a type that is not known to rust
|
/// Create ComponentInfo from a type that is not known to rust
|
||||||
pub fn new_unknown<D>(type_id: D, name: &str, layout: MemoryLayout) -> Self
|
pub fn new_unknown<D>(type_id: D, name: &str, layout: Layout) -> Self
|
||||||
where
|
where
|
||||||
D: Into<DynTypeId>,
|
D: Into<DynTypeId>,
|
||||||
{
|
{
|
||||||
let _ = name; // would be used at some point
|
let _ = name; // would be used at some point
|
||||||
Self {
|
Self {
|
||||||
type_id: type_id.into(),
|
type_id: type_id.into(),
|
||||||
//name: name.to_string(),
|
|
||||||
layout,
|
layout,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn type_id(&self) -> DynTypeId {
|
||||||
|
self.type_id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn layout(&self) -> Layout {
|
||||||
|
self.layout
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -7,39 +7,42 @@ pub(crate) mod lyra_engine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod archetype;
|
|
||||||
use std::ops::BitOr;
|
use std::ops::BitOr;
|
||||||
|
|
||||||
|
mod archetype;
|
||||||
pub use archetype::*;
|
pub use archetype::*;
|
||||||
|
|
||||||
pub mod entity;
|
mod entity;
|
||||||
pub use entity::*;
|
pub use entity::*;
|
||||||
|
|
||||||
pub mod world;
|
mod world;
|
||||||
pub use world::*;
|
pub use world::*;
|
||||||
|
|
||||||
pub mod command;
|
mod command;
|
||||||
pub use command::*;
|
pub use command::*;
|
||||||
|
|
||||||
pub mod bundle;
|
mod bundle;
|
||||||
pub use bundle::*;
|
pub use bundle::*;
|
||||||
|
|
||||||
pub mod component;
|
mod component;
|
||||||
pub use component::*;
|
pub use component::*;
|
||||||
|
|
||||||
pub mod query;
|
pub mod query;
|
||||||
//pub use query::*;
|
//pub use query::*;
|
||||||
|
|
||||||
pub mod component_info;
|
mod relation;
|
||||||
|
pub use relation::Relation;
|
||||||
|
|
||||||
|
mod component_info;
|
||||||
pub use component_info::*;
|
pub use component_info::*;
|
||||||
|
|
||||||
pub mod resource;
|
mod resource;
|
||||||
pub use resource::*;
|
pub use resource::*;
|
||||||
|
|
||||||
pub mod system;
|
pub mod system;
|
||||||
//pub use system::*;
|
//pub use system::*;
|
||||||
|
|
||||||
pub mod tick;
|
mod tick;
|
||||||
pub use tick::*;
|
pub use tick::*;
|
||||||
|
|
||||||
/// Implements Component for glam math types
|
/// Implements Component for glam math types
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
use std::{marker::PhantomData, ptr::NonNull, cell::{Ref, RefMut}};
|
use std::{marker::PhantomData, ptr::NonNull, cell::{Ref, RefMut}};
|
||||||
|
|
||||||
use crate::{world::World, ComponentColumn, DynTypeId, Tick, Component};
|
use crate::{World, ComponentColumn, DynTypeId, Tick, Component};
|
||||||
|
|
||||||
use super::{Fetch, Query, AsQuery};
|
use super::{Fetch, Query, AsQuery};
|
||||||
|
|
||||||
/// Fetcher for borrowing components from archetypes.
|
/// Fetcher for borrowing components from archetypes.
|
||||||
pub struct FetchBorrow<'a, T> {
|
pub struct FetchBorrow<'a, T> {
|
||||||
col: &'a ComponentColumn,
|
col: &'a ComponentColumn,
|
||||||
size: usize,
|
|
||||||
_phantom: PhantomData<&'a T>
|
_phantom: PhantomData<&'a T>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,13 +21,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn get_item(&mut self, entity: crate::world::ArchetypeEntityId) -> Self::Item {
|
unsafe fn get_item(&mut self, entity: crate::world::ArchetypeEntityId) -> Self::Item {
|
||||||
let ptr = self.col.borrow_ptr();
|
self.col.get(entity.0 as _)
|
||||||
Ref::map(ptr, |ptr| {
|
|
||||||
let ptr = NonNull::new_unchecked(ptr.as_ptr()
|
|
||||||
.add(entity.0 as usize * self.size))
|
|
||||||
.cast();
|
|
||||||
&*ptr.as_ptr()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,17 +74,16 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool {
|
fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool {
|
||||||
archetype.columns.iter().any(|c| c.info.type_id == self.type_id)
|
archetype.has_column(self.type_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn fetch<'a>(&self, _world: &'a World, archetype: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> {
|
unsafe fn fetch<'a>(&self, _world: &'a World, archetype: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> {
|
||||||
let _ = tick;
|
let _ = tick;
|
||||||
let col = archetype.columns.iter().find(|c| c.info.type_id == self.type_id)
|
let col = archetype.get_column(self.type_id)
|
||||||
.expect("You ignored 'can_visit_archetype'!");
|
.expect("You ignored 'can_visit_archetype'!");
|
||||||
|
|
||||||
FetchBorrow {
|
FetchBorrow {
|
||||||
col,
|
col,
|
||||||
size: col.info.layout.size,
|
|
||||||
_phantom: PhantomData,
|
_phantom: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,7 +100,6 @@ impl<T: Component> AsQuery for &T {
|
||||||
/// A fetcher for mutably borrowing components from archetypes.
|
/// A fetcher for mutably borrowing components from archetypes.
|
||||||
pub struct FetchBorrowMut<'a, T> {
|
pub struct FetchBorrowMut<'a, T> {
|
||||||
col: NonNull<ComponentColumn>,
|
col: NonNull<ComponentColumn>,
|
||||||
size: usize,
|
|
||||||
tick: Tick,
|
tick: Tick,
|
||||||
_phantom: PhantomData<&'a T>
|
_phantom: PhantomData<&'a T>
|
||||||
}
|
}
|
||||||
|
@ -125,15 +116,7 @@ where
|
||||||
|
|
||||||
unsafe fn get_item(&mut self, entity: crate::world::ArchetypeEntityId) -> Self::Item {
|
unsafe fn get_item(&mut self, entity: crate::world::ArchetypeEntityId) -> Self::Item {
|
||||||
let col = unsafe { self.col.as_mut() };
|
let col = unsafe { self.col.as_mut() };
|
||||||
col.entity_ticks[entity.0 as usize] = self.tick;
|
col.get_mut(entity.0 as _, &self.tick)
|
||||||
let ptr = col.borrow_mut_ptr();
|
|
||||||
|
|
||||||
RefMut::map(ptr, |ptr| {
|
|
||||||
let ptr = NonNull::new_unchecked(ptr.as_ptr()
|
|
||||||
.add(entity.0 as usize * self.size))
|
|
||||||
.cast();
|
|
||||||
&mut *ptr.as_ptr()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,13 +171,12 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool {
|
fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool {
|
||||||
archetype.columns.iter().any(|c| c.info.type_id == self.type_id)
|
archetype.has_column(self.type_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn fetch<'a>(&self, _world: &'a World, archetype: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> {
|
unsafe fn fetch<'a>(&self, _world: &'a World, archetype: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> {
|
||||||
let col = archetype.columns.iter().find(|c| c.info.type_id == self.type_id)
|
let col = archetype.get_column(self.type_id)
|
||||||
.expect("You ignored 'can_visit_archetype'!");
|
.expect("You ignored 'can_visit_archetype'!");
|
||||||
let layout_size = col.info.layout.size;
|
|
||||||
let col = NonNull::from(col);
|
let col = NonNull::from(col);
|
||||||
|
|
||||||
// TODO: find a way to get the component column mutable with a borrowed archetype so its tick can be updated.
|
// TODO: find a way to get the component column mutable with a borrowed archetype so its tick can be updated.
|
||||||
|
@ -202,7 +184,6 @@ where
|
||||||
|
|
||||||
FetchBorrowMut {
|
FetchBorrowMut {
|
||||||
col,
|
col,
|
||||||
size: layout_size,
|
|
||||||
tick,
|
tick,
|
||||||
_phantom: PhantomData,
|
_phantom: PhantomData,
|
||||||
}
|
}
|
||||||
|
@ -219,9 +200,9 @@ impl<T: Component> AsQuery for &mut T {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::{mem::size_of, marker::PhantomData, ptr::NonNull};
|
use std::{marker::PhantomData, ptr::NonNull};
|
||||||
|
|
||||||
use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{Fetch, View}, tests::Vec2, world::World, DynTypeId, Entity, EntityId, Tick};
|
use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{Fetch, ViewState}, tests::Vec2, World, DynTypeId, Entity, EntityId, Tick};
|
||||||
|
|
||||||
use super::{QueryBorrow, QueryBorrowMut, FetchBorrowMut};
|
use super::{QueryBorrow, QueryBorrowMut, FetchBorrowMut};
|
||||||
|
|
||||||
|
@ -244,7 +225,7 @@ mod tests {
|
||||||
_phantom: std::marker::PhantomData,
|
_phantom: std::marker::PhantomData,
|
||||||
};
|
};
|
||||||
let archetypes: Vec<&Archetype> = world.archetypes.values().collect();
|
let archetypes: Vec<&Archetype> = world.archetypes.values().collect();
|
||||||
let v = View::<QueryBorrow<Vec2>>::new(&world, borrow, archetypes);
|
let v = ViewState::<QueryBorrow<Vec2>, ()>::new(&world, borrow, (), archetypes);
|
||||||
|
|
||||||
for e in v.into_iter() {
|
for e in v.into_iter() {
|
||||||
println!("Found entity at {:?}", e);
|
println!("Found entity at {:?}", e);
|
||||||
|
@ -260,7 +241,7 @@ mod tests {
|
||||||
_phantom: std::marker::PhantomData,
|
_phantom: std::marker::PhantomData,
|
||||||
};
|
};
|
||||||
let archetypes: Vec<&Archetype> = world.archetypes.values().collect();
|
let archetypes: Vec<&Archetype> = world.archetypes.values().collect();
|
||||||
let v = View::<QueryBorrowMut<Vec2>>::new(&world, borrow, archetypes);
|
let v = ViewState::<QueryBorrowMut<Vec2>, ()>::new(&world, borrow, (), archetypes);
|
||||||
|
|
||||||
let mut orig = vec![];
|
let mut orig = vec![];
|
||||||
|
|
||||||
|
@ -277,7 +258,7 @@ mod tests {
|
||||||
_phantom: std::marker::PhantomData,
|
_phantom: std::marker::PhantomData,
|
||||||
};
|
};
|
||||||
let archetypes: Vec<&Archetype> = world.archetypes.values().collect();
|
let archetypes: Vec<&Archetype> = world.archetypes.values().collect();
|
||||||
let v = View::<QueryBorrow<Vec2>>::new(&world, borrow, archetypes);
|
let v = ViewState::<QueryBorrow<Vec2>, ()>::new(&world, borrow, (), archetypes);
|
||||||
|
|
||||||
for (new, orig) in v.into_iter().zip(orig.iter()) {
|
for (new, orig) in v.into_iter().zip(orig.iter()) {
|
||||||
assert!(new.x - orig.x == 10.0);
|
assert!(new.x - orig.x == 10.0);
|
||||||
|
@ -298,12 +279,11 @@ mod tests {
|
||||||
}, (Vec2::rand(),), &Tick::default());
|
}, (Vec2::rand(),), &Tick::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
let col = a.columns.iter().find(|c| c.info.type_id == DynTypeId::of::<Vec2>()).unwrap();
|
let col = a.get_column(DynTypeId::of::<Vec2>()).unwrap();
|
||||||
|
|
||||||
let mut bmut = FetchBorrowMut::<Vec2> {
|
let mut bmut = FetchBorrowMut::<Vec2> {
|
||||||
col: NonNull::from(col),
|
col: NonNull::from(col),
|
||||||
tick: Tick::default(),
|
tick: Tick::default(),
|
||||||
size: size_of::<Vec2>(),
|
|
||||||
_phantom: PhantomData,
|
_phantom: PhantomData,
|
||||||
};
|
};
|
||||||
let item = unsafe { bmut.get_item(crate::ArchetypeEntityId(0)) };
|
let item = unsafe { bmut.get_item(crate::ArchetypeEntityId(0)) };
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use std::ptr::NonNull;
|
use std::ptr::NonNull;
|
||||||
|
|
||||||
use crate::{world::World, ComponentColumn, ComponentInfo};
|
use crate::{World, ComponentColumn, ComponentInfo};
|
||||||
|
|
||||||
pub mod view;
|
mod view;
|
||||||
pub use view::*;
|
pub use view::*;
|
||||||
|
|
||||||
use super::Fetch;
|
use super::Fetch;
|
||||||
|
@ -15,7 +15,7 @@ pub struct DynamicType {
|
||||||
|
|
||||||
impl DynamicType {
|
impl DynamicType {
|
||||||
pub fn is<T: 'static>(&self) -> bool {
|
pub fn is<T: 'static>(&self) -> bool {
|
||||||
self.info.type_id.is::<T>()
|
self.info.type_id().is::<T>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ impl<'a> Fetch<'a> for FetchDynamicType<'a> {
|
||||||
unsafe fn get_item(&mut self, entity: crate::ArchetypeEntityId) -> Self::Item {
|
unsafe fn get_item(&mut self, entity: crate::ArchetypeEntityId) -> Self::Item {
|
||||||
let ptr = self.col.borrow_ptr();
|
let ptr = self.col.borrow_ptr();
|
||||||
let ptr = NonNull::new_unchecked(ptr.as_ptr()
|
let ptr = NonNull::new_unchecked(ptr.as_ptr()
|
||||||
.add(entity.0 as usize * self.info.layout.size));
|
.add(entity.0 as usize * self.info.layout().size()));
|
||||||
|
|
||||||
DynamicType {
|
DynamicType {
|
||||||
info: self.info,
|
info: self.info,
|
||||||
|
@ -63,11 +63,11 @@ impl QueryDynamicType {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool {
|
pub fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool {
|
||||||
archetype.has_column(self.info.type_id)
|
archetype.has_column(self.info.type_id())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub unsafe fn fetch<'a>(&self, _world: &'a World, _arch_id: crate::archetype::ArchetypeId, archetype: &'a crate::archetype::Archetype) -> FetchDynamicType<'a> {
|
pub unsafe fn fetch<'a>(&self, _world: &'a World, _arch_id: crate::archetype::ArchetypeId, archetype: &'a crate::archetype::Archetype) -> FetchDynamicType<'a> {
|
||||||
let col = archetype.columns.iter().find(|c| c.info.type_id == self.info.type_id)
|
let col = archetype.get_column(self.info.type_id())
|
||||||
.expect("You ignored 'can_visit_archetype'!");
|
.expect("You ignored 'can_visit_archetype'!");
|
||||||
|
|
||||||
FetchDynamicType {
|
FetchDynamicType {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
use crate::{world::World, Archetype, ArchetypeEntityId, ArchetypeId, query::Fetch};
|
use crate::{World, Archetype, ArchetypeEntityId, ArchetypeId, query::Fetch};
|
||||||
|
|
||||||
use super::{QueryDynamicType, FetchDynamicType, DynamicType};
|
use super::{QueryDynamicType, FetchDynamicType, DynamicType};
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ impl<'a> Iterator for DynamicViewIter<'a> {
|
||||||
self.next_archetype += 1;
|
self.next_archetype += 1;
|
||||||
let arch = unsafe { self.archetypes.get_unchecked(arch_id) };
|
let arch = unsafe { self.archetypes.get_unchecked(arch_id) };
|
||||||
|
|
||||||
if arch.entities.is_empty() {
|
if arch.entity_ids.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ impl<'a> Iterator for DynamicViewIter<'a> {
|
||||||
self.fetchers = self.queries.iter()
|
self.fetchers = self.queries.iter()
|
||||||
.map(|q| unsafe { q.fetch(self.world, ArchetypeId(arch_id as u64), arch) } )
|
.map(|q| unsafe { q.fetch(self.world, ArchetypeId(arch_id as u64), arch) } )
|
||||||
.collect();
|
.collect();
|
||||||
self.component_indices = 0..arch.entities.len() as u64;
|
self.component_indices = 0..arch.entity_ids.len() as u64;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,13 +104,13 @@ impl<'a> Iterator for DynamicViewIter<'a> {
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::{alloc::Layout, ptr::NonNull};
|
use std::{alloc::Layout, ptr::NonNull};
|
||||||
|
|
||||||
use crate::{world::World, MemoryLayout, ComponentInfo, DynTypeId, DynamicBundle, query::dynamic::QueryDynamicType};
|
use crate::{World, ComponentInfo, DynTypeId, DynamicBundle, query::dynamic::QueryDynamicType};
|
||||||
|
|
||||||
use super::DynamicView;
|
use super::DynamicView;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn single_dynamic_view() {
|
fn single_dynamic_view() {
|
||||||
let comp_layout = MemoryLayout::from(Layout::new::<u32>());
|
let comp_layout = Layout::new::<u32>();
|
||||||
let comp_info = ComponentInfo::new_unknown(DynTypeId::Unknown(100), "u32", comp_layout);
|
let comp_info = ComponentInfo::new_unknown(DynTypeId::Unknown(100), "u32", comp_layout);
|
||||||
|
|
||||||
let mut dynamic_bundle = DynamicBundle::default();
|
let mut dynamic_bundle = DynamicBundle::default();
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{archetype::Archetype, world::World, Entity};
|
use crate::{archetype::Archetype, World, Entity};
|
||||||
|
|
||||||
use super::{Fetch, Query, AsQuery};
|
use super::{Fetch, Query, AsQuery};
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ impl Query for Entities {
|
||||||
unsafe fn fetch<'a>(&self, _world: &'a World, archetype: &'a Archetype, tick: crate::Tick) -> Self::Fetch<'a> {
|
unsafe fn fetch<'a>(&self, _world: &'a World, archetype: &'a Archetype, tick: crate::Tick) -> Self::Fetch<'a> {
|
||||||
let _ = tick; // ignore unused warnings
|
let _ = tick; // ignore unused warnings
|
||||||
EntitiesFetch {
|
EntitiesFetch {
|
||||||
entities: archetype.entities.keys().cloned().collect::<Vec<Entity>>(),
|
entities: archetype.entity_ids.keys().cloned().collect::<Vec<Entity>>(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,27 +3,27 @@ use crate::{archetype::Archetype, world::{ArchetypeEntityId, World}, Tick};
|
||||||
pub mod view;
|
pub mod view;
|
||||||
pub use view::*;
|
pub use view::*;
|
||||||
|
|
||||||
pub mod entities;
|
mod entities;
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
pub use entities::*;
|
pub use entities::*;
|
||||||
|
|
||||||
pub mod borrow;
|
mod borrow;
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
pub use borrow::*;
|
pub use borrow::*;
|
||||||
|
|
||||||
pub mod tuple;
|
mod tuple;
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
pub use tuple::*;
|
pub use tuple::*;
|
||||||
|
|
||||||
pub mod resource;
|
mod resource;
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
pub use resource::*;
|
pub use resource::*;
|
||||||
|
|
||||||
pub mod tick;
|
mod tick;
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
pub use tick::*;
|
pub use tick::*;
|
||||||
|
|
||||||
pub mod world;
|
mod world;
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
pub use world::*;
|
pub use world::*;
|
||||||
|
|
||||||
|
@ -87,11 +87,47 @@ pub trait IntoQuery {
|
||||||
fn into_query(self) -> Self;
|
fn into_query(self) -> Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> Fetch<'a> for () {
|
||||||
|
type Item = ();
|
||||||
|
|
||||||
|
fn dangling() -> Self {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn get_item(&mut self, _: ArchetypeEntityId) -> Self::Item {
|
||||||
|
()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Query for () {
|
||||||
|
type Item<'a> = ();
|
||||||
|
|
||||||
|
type Fetch<'a> = ();
|
||||||
|
|
||||||
|
const ALWAYS_FETCHES: bool = true;
|
||||||
|
|
||||||
|
fn new() -> Self {
|
||||||
|
()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_visit_archetype(&self, _: &Archetype) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn fetch<'a>(&self, _: &'a World, _: &'a Archetype, _: Tick) -> Self::Fetch<'a> {
|
||||||
|
()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsQuery for () {
|
||||||
|
type Query = ();
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{world::World, archetype::Archetype, tests::Vec2};
|
use crate::{World, archetype::Archetype, tests::Vec2};
|
||||||
|
|
||||||
use super::{View, Entities};
|
use super::{ViewState, Entities};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn simple_view() {
|
fn simple_view() {
|
||||||
|
@ -104,7 +140,7 @@ mod tests {
|
||||||
|
|
||||||
let archetypes: Vec<&Archetype> = world.archetypes.values().collect();
|
let archetypes: Vec<&Archetype> = world.archetypes.values().collect();
|
||||||
|
|
||||||
let v = View::<Entities>::new(&world, entities, archetypes);
|
let v = ViewState::<Entities, ()>::new(&world, entities, (), archetypes);
|
||||||
|
|
||||||
for e in v.into_iter() {
|
for e in v.into_iter() {
|
||||||
println!("Got entity! {:?}", e);
|
println!("Got entity! {:?}", e);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{marker::PhantomData, cell::{Ref, RefMut}};
|
use std::{marker::PhantomData, cell::{Ref, RefMut}};
|
||||||
|
|
||||||
use crate::{world::World, resource::ResourceObject};
|
use crate::{World, resource::ResourceObject};
|
||||||
|
|
||||||
use super::{Query, Fetch, AsQuery};
|
use super::{Query, Fetch, AsQuery};
|
||||||
|
|
||||||
|
@ -192,9 +192,7 @@ impl<'a, T: ResourceObject> AsQuery for ResMut<'a, T> {
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
use crate::{world::World, tests::{Vec2, Vec3}, query::QueryResourceMut};
|
use crate::{query::{Res, ResMut}, tests::{Vec2, Vec3}, World};
|
||||||
|
|
||||||
use super::QueryResource;
|
|
||||||
|
|
||||||
struct SomeCounter(u32);
|
struct SomeCounter(u32);
|
||||||
|
|
||||||
|
@ -207,7 +205,7 @@ mod tests {
|
||||||
println!("Added resource");
|
println!("Added resource");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut res_iter = world.view_iter::<QueryResource<SomeCounter>>();
|
let mut res_iter = world.view_iter::<Res<SomeCounter>>();
|
||||||
let res = res_iter.next().unwrap();
|
let res = res_iter.next().unwrap();
|
||||||
let res = res.deref();
|
let res = res.deref();
|
||||||
assert_eq!(res.0, 0);
|
assert_eq!(res.0, 0);
|
||||||
|
@ -228,16 +226,16 @@ mod tests {
|
||||||
println!("Added resource");
|
println!("Added resource");
|
||||||
}
|
}
|
||||||
|
|
||||||
let i = world.view_iter::<(QueryResource<SomeCounter>, &Vec2)>();
|
let i = world.view_iter::<(Res<SomeCounter>, &Vec2)>();
|
||||||
assert_eq!(i.count(), 3);
|
assert_eq!(i.count(), 3);
|
||||||
let i = world.view_iter::<(&Vec2, QueryResource<SomeCounter>)>();
|
let i = world.view_iter::<(&Vec2, Res<SomeCounter>)>();
|
||||||
assert_eq!(i.count(), 3);
|
assert_eq!(i.count(), 3);
|
||||||
|
|
||||||
for (res, e) in world.view_iter::<(QueryResource<SomeCounter>, &Vec2)>() {
|
for (res, e) in world.view_iter::<(Res<SomeCounter>, &Vec2)>() {
|
||||||
println!("Got res {}! and entity at {:?}", res.deref().0, e);
|
println!("Got res {}! and entity at {:?}", res.deref().0, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
let i = world.view_iter::<QueryResource<SomeCounter>>();
|
let i = world.view_iter::<Res<SomeCounter>>();
|
||||||
assert_eq!(i.count(), 1);
|
assert_eq!(i.count(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,14 +249,14 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut resmut_iter = world.view_iter::<QueryResourceMut<SomeCounter>>();
|
let mut resmut_iter = world.view_iter::<ResMut<SomeCounter>>();
|
||||||
let mut resmut = resmut_iter.next().unwrap();
|
let mut resmut = resmut_iter.next().unwrap();
|
||||||
let resmut = resmut.deref_mut();
|
let resmut = resmut.deref_mut();
|
||||||
assert_eq!(resmut.0, 0);
|
assert_eq!(resmut.0, 0);
|
||||||
resmut.0 += 20;
|
resmut.0 += 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut res_iter = world.view_iter::<QueryResource<SomeCounter>>();
|
let mut res_iter = world.view_iter::<Res<SomeCounter>>();
|
||||||
let res = res_iter.next().unwrap();
|
let res = res_iter.next().unwrap();
|
||||||
let res = res.deref();
|
let res = res.deref();
|
||||||
assert_eq!(res.0, 20);
|
assert_eq!(res.0, 20);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use crate::{ComponentColumn, Tick, DynTypeId, world::World};
|
use crate::{ComponentColumn, Tick, DynTypeId, World};
|
||||||
|
|
||||||
use super::{Query, Fetch, AsQuery};
|
use super::{Query, Fetch, AsQuery};
|
||||||
|
|
||||||
|
@ -90,11 +90,11 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool {
|
fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool {
|
||||||
archetype.columns.iter().any(|c| c.info.type_id == self.type_id)
|
archetype.has_column(self.type_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn fetch<'a>(&self, _world: &'a World, archetype: &'a crate::archetype::Archetype, _tick: crate::Tick) -> Self::Fetch<'a> {
|
unsafe fn fetch<'a>(&self, _world: &'a World, archetype: &'a crate::archetype::Archetype, _tick: crate::Tick) -> Self::Fetch<'a> {
|
||||||
let col = archetype.columns.iter().find(|c| c.info.type_id == self.type_id)
|
let col = archetype.get_column(self.type_id)
|
||||||
.expect("You ignored 'can_visit_archetype'!");
|
.expect("You ignored 'can_visit_archetype'!");
|
||||||
|
|
||||||
FetchTickOf {
|
FetchTickOf {
|
||||||
|
|
|
@ -1,7 +1,51 @@
|
||||||
use crate::world::World;
|
use crate::World;
|
||||||
|
|
||||||
use super::{Query, Fetch, AsQuery};
|
use super::{Query, Fetch, AsQuery};
|
||||||
|
|
||||||
|
impl<'a, F1> Fetch<'a> for (F1,)
|
||||||
|
where
|
||||||
|
F1: Fetch<'a>,
|
||||||
|
{
|
||||||
|
type Item = (F1::Item,);
|
||||||
|
|
||||||
|
fn dangling() -> Self {
|
||||||
|
(F1::dangling(),)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_visit_item(&mut self, entity: crate::world::ArchetypeEntityId) -> bool {
|
||||||
|
let (f1,) = self;
|
||||||
|
f1.can_visit_item(entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn get_item(&mut self, entity: crate::world::ArchetypeEntityId) -> Self::Item {
|
||||||
|
let (f1,) = self;
|
||||||
|
( f1.get_item(entity), )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Q1> Query for (Q1,)
|
||||||
|
where
|
||||||
|
Q1: Query,
|
||||||
|
{
|
||||||
|
type Item<'a> = (Q1::Item<'a>,);
|
||||||
|
|
||||||
|
type Fetch<'a> = (Q1::Fetch<'a>,);
|
||||||
|
|
||||||
|
fn new() -> Self {
|
||||||
|
(Q1::new(),)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool {
|
||||||
|
let (q1,) = self;
|
||||||
|
q1.can_visit_archetype(archetype)
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn fetch<'a>(&self, world: &'a World, archetype: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> {
|
||||||
|
let (q1,) = self;
|
||||||
|
( q1.fetch(world, archetype, tick), )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Technically all of these implementations for a 2-sized tuple
|
// Technically all of these implementations for a 2-sized tuple
|
||||||
// can be implemented by the macro near the end of the file, but
|
// can be implemented by the macro near the end of the file, but
|
||||||
// these are left here for development.
|
// these are left here for development.
|
||||||
|
@ -131,7 +175,7 @@ impl_bundle_tuple! { Q1, Q2, Q3, Q4, Q5, Q6, Q7, Q8, Q9, Q10, Q11, Q12, Q13, Q14
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{world::World, tests::{Vec2, Vec3}};
|
use crate::{World, tests::{Vec2, Vec3}};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tuple_queries() {
|
fn tuple_queries() {
|
||||||
|
|
|
@ -4,41 +4,64 @@ use crate::{archetype::Archetype, world::{ArchetypeEntityId, World}, EntityId, T
|
||||||
|
|
||||||
use super::{Query, Fetch, AsQuery};
|
use super::{Query, Fetch, AsQuery};
|
||||||
|
|
||||||
pub struct View<'a, Q: AsQuery> {
|
pub type View<'a, Q, F = ()> = ViewState<'a, <Q as AsQuery>::Query, <F as AsQuery>::Query>;
|
||||||
|
|
||||||
|
pub struct ViewState<'a, Q: Query, F: Query> {
|
||||||
world: &'a World,
|
world: &'a World,
|
||||||
query: Q::Query,
|
query: Q,
|
||||||
|
filter: F,
|
||||||
archetypes: Vec<&'a Archetype>,
|
archetypes: Vec<&'a Archetype>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Q> View<'a, Q>
|
impl<'a, Q, F> ViewState<'a, Q, F>
|
||||||
where
|
where
|
||||||
Q: AsQuery,
|
Q: Query,
|
||||||
|
F: Query,
|
||||||
{
|
{
|
||||||
pub fn new(world: &'a World, query: Q::Query, archetypes: Vec<&'a Archetype>) -> Self {
|
pub fn new(world: &'a World, query: Q, filter: F, archetypes: Vec<&'a Archetype>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
world,
|
world,
|
||||||
query,
|
query,
|
||||||
|
filter,
|
||||||
archetypes,
|
archetypes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts self into an iterator
|
||||||
|
pub fn iter(self) -> ViewIter<'a, Q, F> {
|
||||||
|
self.into_iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes `self`, adding a query to the view.
|
||||||
|
pub fn expand<U: AsQuery>(self, query: U::Query) -> ViewState<'a, (Q, U::Query), F> {
|
||||||
|
ViewState::new(self.world, (self.query, query), self.filter, self.archetypes)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes `self`, adding a filter to the view.
|
||||||
|
pub fn with<U: AsQuery>(self, filter: U::Query) -> ViewState<'a, Q, (F, U::Query)> {
|
||||||
|
ViewState::new(self.world, self.query, (self.filter, filter), self.archetypes)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Q> IntoIterator for View<'a, Q>
|
impl<'a, Q, F> IntoIterator for ViewState<'a, Q, F>
|
||||||
where
|
where
|
||||||
Q: AsQuery,
|
Q: Query,
|
||||||
|
F: Query,
|
||||||
{
|
{
|
||||||
type Item = <Q::Query as Query>::Item<'a>;
|
type Item = Q::Item<'a>;
|
||||||
|
|
||||||
type IntoIter = ViewIter<'a, Q::Query>;
|
type IntoIter = ViewIter<'a, Q, F>;
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
let tick = self.world.tick_tracker().tick_when(Q::Query::MUTATES);
|
let tick = self.world.tick_tracker().tick_when(Q::MUTATES);
|
||||||
|
|
||||||
ViewIter {
|
ViewIter {
|
||||||
world: self.world,
|
world: self.world,
|
||||||
tick,
|
tick,
|
||||||
query: self.query,
|
query: self.query,
|
||||||
|
filter: self.filter,
|
||||||
fetcher: None,
|
fetcher: None,
|
||||||
|
filter_fetcher: None,
|
||||||
archetypes: self.archetypes,
|
archetypes: self.archetypes,
|
||||||
next_archetype: 0,
|
next_archetype: 0,
|
||||||
component_indices: 0..0,
|
component_indices: 0..0,
|
||||||
|
@ -46,25 +69,28 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ViewIter<'a, Q: Query> {
|
pub struct ViewIter<'a, Q: Query, F: Query> {
|
||||||
world: &'a World,
|
world: &'a World,
|
||||||
tick: Tick,
|
tick: Tick,
|
||||||
query: Q,
|
query: Q,
|
||||||
|
filter: F,
|
||||||
fetcher: Option<Q::Fetch<'a>>,
|
fetcher: Option<Q::Fetch<'a>>,
|
||||||
|
filter_fetcher: Option<F::Fetch<'a>>,
|
||||||
archetypes: Vec<&'a Archetype>,
|
archetypes: Vec<&'a Archetype>,
|
||||||
next_archetype: usize,
|
next_archetype: usize,
|
||||||
component_indices: Range<u64>,
|
component_indices: Range<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Q> Iterator for ViewIter<'a, Q>
|
impl<'a, Q, F> Iterator for ViewIter<'a, Q, F>
|
||||||
where
|
where
|
||||||
Q: Query,
|
Q: Query,
|
||||||
|
F: Query,
|
||||||
{
|
{
|
||||||
type Item = Q::Item<'a>;
|
type Item = Q::Item<'a>;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
loop {
|
loop {
|
||||||
if Q::ALWAYS_FETCHES {
|
if Q::ALWAYS_FETCHES && F::ALWAYS_FETCHES {
|
||||||
// only fetch this query once.
|
// only fetch this query once.
|
||||||
// fetcher gets set to Some after this `next` call.
|
// fetcher gets set to Some after this `next` call.
|
||||||
if self.fetcher.is_none() {
|
if self.fetcher.is_none() {
|
||||||
|
@ -80,10 +106,10 @@ where
|
||||||
|
|
||||||
if let Some(entity_index) = self.component_indices.next() {
|
if let Some(entity_index) = self.component_indices.next() {
|
||||||
let fetcher = self.fetcher.as_mut().unwrap();
|
let fetcher = self.fetcher.as_mut().unwrap();
|
||||||
|
let filter_fetcher = self.filter_fetcher.as_mut().unwrap();
|
||||||
let entity_index = ArchetypeEntityId(entity_index);
|
let entity_index = ArchetypeEntityId(entity_index);
|
||||||
if !fetcher.can_visit_item(entity_index) {
|
|
||||||
continue;
|
if fetcher.can_visit_item(entity_index) && filter_fetcher.can_visit_item(entity_index) {
|
||||||
} else {
|
|
||||||
let i = unsafe { fetcher.get_item(entity_index) };
|
let i = unsafe { fetcher.get_item(entity_index) };
|
||||||
return Some(i);
|
return Some(i);
|
||||||
}
|
}
|
||||||
|
@ -96,16 +122,17 @@ where
|
||||||
self.next_archetype += 1;
|
self.next_archetype += 1;
|
||||||
let arch = unsafe { self.archetypes.get_unchecked(arch_id) };
|
let arch = unsafe { self.archetypes.get_unchecked(arch_id) };
|
||||||
|
|
||||||
if arch.entities.is_empty() {
|
if arch.entity_ids.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.query.can_visit_archetype(arch) {
|
if self.query.can_visit_archetype(arch) && self.filter.can_visit_archetype(arch) {
|
||||||
continue;
|
unsafe {
|
||||||
|
self.fetcher = Some(self.query.fetch(self.world, arch, self.tick));
|
||||||
|
self.filter_fetcher = Some(self.filter.fetch(self.world, arch, self.tick));
|
||||||
|
}
|
||||||
|
self.component_indices = 0..arch.entity_ids.len() as u64;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.fetcher = unsafe { Some(self.query.fetch(self.world, arch, self.tick)) };
|
|
||||||
self.component_indices = 0..arch.entities.len() as u64;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,177 @@
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
use lyra_ecs_derive::Component;
|
||||||
|
|
||||||
|
use crate::query::Query;
|
||||||
|
use crate::query::ViewState;
|
||||||
|
use crate::Entity;
|
||||||
|
|
||||||
|
use crate::lyra_engine;
|
||||||
|
use crate::World;
|
||||||
|
|
||||||
|
mod relates_to;
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub use relates_to::*;
|
||||||
|
|
||||||
|
mod relate_pair;
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub use relate_pair::*;
|
||||||
|
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
pub trait Relation: 'static {
|
||||||
|
/// called when a relation of this type is set on a target
|
||||||
|
fn relation_add(&self, origin: Entity, target: Entity) { }
|
||||||
|
/// called when a relation is removed
|
||||||
|
fn relation_remove(&self, origin: Entity, target: Entity) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A component that stores the target of a relation.
|
||||||
|
///
|
||||||
|
/// This component is on the origin of the relation and can be used to find all
|
||||||
|
/// entities that the relation targets.
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct RelationOriginComponent<R: Relation> {
|
||||||
|
pub(crate) relation: R,
|
||||||
|
target: Entity,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A component that stores the origin of a relation.
|
||||||
|
///
|
||||||
|
/// This component is on the target of the relation and can be used to find the
|
||||||
|
/// origin of the relation -- the entity that targets this entity.
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct RelationTargetComponent<R: Relation> {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
origin: Entity,
|
||||||
|
_marker: PhantomData<R>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl World {
|
||||||
|
/// Creates a relation between two entities.
|
||||||
|
///
|
||||||
|
/// ```nobuild
|
||||||
|
/// struct ChildOf;
|
||||||
|
///
|
||||||
|
/// impl Relation for ChildOf { /* snip */ };
|
||||||
|
///
|
||||||
|
/// let a = world.spawn((Vec2 { x: 10.0, y: 20.0 },));
|
||||||
|
/// let b = world.spawn((Vec2 { x: 158.0, y: 65.0 },));
|
||||||
|
///
|
||||||
|
/// // Entity a is a child of entity b
|
||||||
|
/// world.add_relation(a, ChildOf, b);
|
||||||
|
///
|
||||||
|
/// // Construct a view for querying the entities that have a relation to entity `b`.
|
||||||
|
/// let v = world.view::<Entities>()
|
||||||
|
/// .relates_to::<ChildOf>(b);
|
||||||
|
///
|
||||||
|
/// // Iterate through the entities. ChildOf has no members, so it will be unused.
|
||||||
|
/// for (e, _childof) in v.into_iter() {
|
||||||
|
/// println!("{:?} is a child of {:?}", e, b);
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn add_relation<R>(&mut self, origin: Entity, relation: R, target: Entity)
|
||||||
|
where
|
||||||
|
R: Relation
|
||||||
|
{
|
||||||
|
let comp = RelationTargetComponent {
|
||||||
|
origin,
|
||||||
|
_marker: PhantomData::<R>,
|
||||||
|
};
|
||||||
|
self.insert(target, comp);
|
||||||
|
|
||||||
|
let comp = RelationOriginComponent {
|
||||||
|
relation,
|
||||||
|
target,
|
||||||
|
};
|
||||||
|
|
||||||
|
comp.relation.relation_add(origin, target);
|
||||||
|
self.insert(origin, comp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a, Q, F> ViewState<'a, Q, F>
|
||||||
|
where
|
||||||
|
Q: Query,
|
||||||
|
F: Query,
|
||||||
|
{
|
||||||
|
/// Consumes `self` to return a view that fetches the relation to a specific target entity.
|
||||||
|
pub fn relates_to<R>(self, target: Entity) -> ViewState<'a, (Q, QueryRelatesTo<R>), F>
|
||||||
|
where
|
||||||
|
R: Relation,
|
||||||
|
{
|
||||||
|
let rel = QueryRelatesTo::new(Some(target));
|
||||||
|
self.expand::<RelatesTo<R>>(rel)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes `self` to return a view that fetches the origin, target, and a reference to
|
||||||
|
/// the relation that the entities have together.
|
||||||
|
pub fn relate_pair<R>(self) -> ViewState<'a, (Q, QueryRelatePair<R>), F>
|
||||||
|
where
|
||||||
|
R: Relation,
|
||||||
|
{
|
||||||
|
let rel = QueryRelatePair::<R>::new();
|
||||||
|
self.expand::<RelatePair<R>>(rel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::{query::{Entities, ViewState}, relation::QueryRelatesTo, tests::Vec2, World};
|
||||||
|
|
||||||
|
use super::{RelatePair, Relation};
|
||||||
|
|
||||||
|
struct ChildOf;
|
||||||
|
|
||||||
|
impl Relation for ChildOf {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simple_relates_to() {
|
||||||
|
let mut world = World::new();
|
||||||
|
|
||||||
|
let a = world.spawn((Vec2::new(10.0, 20.0),));
|
||||||
|
let b = world.spawn((Vec2::new(158.0, 65.0),));
|
||||||
|
|
||||||
|
world.add_relation(a, ChildOf, b);
|
||||||
|
|
||||||
|
let archetypes = world.archetypes.values().collect();
|
||||||
|
let v = ViewState::<(Entities, QueryRelatesTo<ChildOf>), ()>::new(&world, (Entities::default(), QueryRelatesTo::new(Some(b))), (), archetypes);
|
||||||
|
|
||||||
|
for (e, _relation) in v.into_iter() {
|
||||||
|
println!("{:?} is a child of {:?}", e, b);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simple_relates_to_view() {
|
||||||
|
let mut world = World::new();
|
||||||
|
|
||||||
|
let a = world.spawn((Vec2::new(10.0, 20.0),));
|
||||||
|
let b = world.spawn((Vec2::new(158.0, 65.0),));
|
||||||
|
|
||||||
|
world.add_relation(a, ChildOf, b);
|
||||||
|
|
||||||
|
let v = world.view::<Entities>()
|
||||||
|
.relates_to::<ChildOf>(b);
|
||||||
|
|
||||||
|
for (e, _rel) in v.into_iter() {
|
||||||
|
println!("{:?} is a child of {:?}", e, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simple_relate_pair_view() {
|
||||||
|
let mut world = World::new();
|
||||||
|
|
||||||
|
let a = world.spawn((Vec2::new(10.0, 20.0),));
|
||||||
|
let b = world.spawn((Vec2::new(158.0, 65.0),));
|
||||||
|
|
||||||
|
world.add_relation(a, ChildOf, b);
|
||||||
|
|
||||||
|
let v = world.view::<RelatePair<ChildOf>>();
|
||||||
|
|
||||||
|
for (origin, _childof, target) in v.into_iter() {
|
||||||
|
println!("{:?} is a child of {:?}", origin, target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
use std::{any::{Any, TypeId}, cell::Ref, marker::PhantomData};
|
||||||
|
|
||||||
|
use crate::{query::{AsQuery, Fetch, Query}, Archetype, ComponentColumn, Entity, World};
|
||||||
|
|
||||||
|
use super::{Relation, RelationOriginComponent};
|
||||||
|
|
||||||
|
pub struct FetchRelatePair<'a, T> {
|
||||||
|
col: &'a ComponentColumn,
|
||||||
|
arch: &'a Archetype,
|
||||||
|
_phantom: PhantomData<&'a T>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, R> Fetch<'a> for FetchRelatePair<'a, R>
|
||||||
|
where
|
||||||
|
R: Relation,
|
||||||
|
{
|
||||||
|
type Item = (Entity, Ref<'a, R>, Entity);
|
||||||
|
|
||||||
|
fn dangling() -> Self {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn get_item(&mut self, entity: crate::world::ArchetypeEntityId) -> Self::Item {
|
||||||
|
let comp: Ref<RelationOriginComponent<R>> = self.col.get(entity.0 as usize);
|
||||||
|
let rel_target = comp.target;
|
||||||
|
let rel_origin = self.arch.entity_at_index(entity).unwrap();
|
||||||
|
|
||||||
|
let comp = Ref::map(comp, |r| &r.relation);
|
||||||
|
(rel_origin, comp, rel_target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct QueryRelatePair<R> {
|
||||||
|
_marker: PhantomData<R>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R> Copy for QueryRelatePair<R> {}
|
||||||
|
|
||||||
|
impl<R> Clone for QueryRelatePair<R> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R> QueryRelatePair<R> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
_marker: PhantomData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R> Query for QueryRelatePair<R>
|
||||||
|
where
|
||||||
|
R: Relation + 'static
|
||||||
|
{
|
||||||
|
type Item<'a> = (Entity, Ref<'a, R>, Entity);
|
||||||
|
|
||||||
|
type Fetch<'a> = FetchRelatePair<'a, R>;
|
||||||
|
|
||||||
|
fn new() -> Self {
|
||||||
|
QueryRelatePair::<R>::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool {
|
||||||
|
let tyid = crate::DynTypeId::Rust(TypeId::of::<RelationOriginComponent<R>>());
|
||||||
|
archetype.has_column(tyid)
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn fetch<'a>(&self, _world: &'a World, archetype: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> {
|
||||||
|
let _ = tick;
|
||||||
|
let col = archetype.get_column(self.type_id())
|
||||||
|
.expect("You ignored 'can_visit_archetype'!");
|
||||||
|
|
||||||
|
FetchRelatePair {
|
||||||
|
col,
|
||||||
|
arch: archetype,
|
||||||
|
_phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A query that fetches the origin, and target of a relation of type `R`.
|
||||||
|
///
|
||||||
|
/// It provides it as a tuple in the following format: `(origin, relation, target)`.
|
||||||
|
/// Similar to [`RelatesTo`](super::RelatesTo), you can use [`ViewState::relate_pair`] to get a view that fetches the
|
||||||
|
/// pair, or unlike [`RelatesTo`](super::RelatesTo), you can do the common procedure of using [`World::view`].
|
||||||
|
pub struct RelatePair<R: Relation> {
|
||||||
|
_marker: PhantomData<R>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Relation> AsQuery for RelatePair<R> {
|
||||||
|
type Query = QueryRelatePair<R>;
|
||||||
|
}
|
|
@ -0,0 +1,109 @@
|
||||||
|
use std::{any::TypeId, cell::Ref, marker::PhantomData};
|
||||||
|
|
||||||
|
use crate::{query::{AsQuery, Fetch, Query}, ComponentColumn, Entity, World};
|
||||||
|
|
||||||
|
use super::{Relation, RelationOriginComponent};
|
||||||
|
|
||||||
|
pub struct FetchRelatesTo<'a, T> {
|
||||||
|
col: &'a ComponentColumn,
|
||||||
|
target: Entity,
|
||||||
|
_phantom: PhantomData<&'a T>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, R> Fetch<'a> for FetchRelatesTo<'a, R>
|
||||||
|
where
|
||||||
|
R: Relation,
|
||||||
|
{
|
||||||
|
type Item = Ref<'a, R>;
|
||||||
|
|
||||||
|
fn dangling() -> Self {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_visit_item(&mut self, entity: crate::ArchetypeEntityId) -> bool {
|
||||||
|
unsafe {
|
||||||
|
let comp: Ref<RelationOriginComponent<R>> = self.col.get(entity.0 as usize);
|
||||||
|
comp.target == self.target
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn get_item(&mut self, entity: crate::world::ArchetypeEntityId) -> Self::Item {
|
||||||
|
let comp: Ref<RelationOriginComponent<R>> = self.col.get(entity.0 as usize);
|
||||||
|
let comp = Ref::map(comp, |r| &r.relation);
|
||||||
|
comp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct QueryRelatesTo<R> {
|
||||||
|
target: Option<Entity>,
|
||||||
|
_marker: PhantomData<R>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R> Copy for QueryRelatesTo<R> {}
|
||||||
|
|
||||||
|
impl<R> Clone for QueryRelatesTo<R> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R> QueryRelatesTo<R> {
|
||||||
|
pub fn new(target: Option<Entity>) -> Self {
|
||||||
|
Self {
|
||||||
|
target,
|
||||||
|
_marker: PhantomData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R> Query for QueryRelatesTo<R>
|
||||||
|
where
|
||||||
|
R: Relation + 'static
|
||||||
|
{
|
||||||
|
type Item<'a> = Ref<'a, R>;
|
||||||
|
|
||||||
|
type Fetch<'a> = FetchRelatesTo<'a, R>;
|
||||||
|
|
||||||
|
fn new() -> Self {
|
||||||
|
panic!("RelatesTo MUST be made with View::relates_to since it requires State provided by \
|
||||||
|
that function.")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool {
|
||||||
|
archetype.has_column(TypeId::of::<RelationOriginComponent<R>>())
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn fetch<'a>(&self, _world: &'a World, archetype: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> {
|
||||||
|
let _ = tick;
|
||||||
|
let col = archetype.get_column(TypeId::of::<RelationOriginComponent<R>>())
|
||||||
|
.expect("You ignored 'can_visit_archetype'!");
|
||||||
|
|
||||||
|
FetchRelatesTo {
|
||||||
|
col,
|
||||||
|
target: self.target.expect("Filter not initialized"),
|
||||||
|
_phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A query that fetches the relation to a specific target entity.
|
||||||
|
///
|
||||||
|
/// This can be combined with Entities, to query all entities that have a relation targeting
|
||||||
|
/// the target entity.
|
||||||
|
///
|
||||||
|
/// ```nobuild
|
||||||
|
/// let v = world.view::<Entities>()
|
||||||
|
/// .relates_to::<ChildOf>(b);
|
||||||
|
///
|
||||||
|
/// // Iterate through the entities. ChildOf has no members, so it will be unused.
|
||||||
|
/// for (e, _childof) in v.into_iter() {
|
||||||
|
/// println!("{:?} is a child of {:?}", e, b);
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub struct RelatesTo<R: Relation> {
|
||||||
|
_marker: PhantomData<R>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Relation> AsQuery for RelatesTo<R> {
|
||||||
|
type Query = QueryRelatesTo<R>;
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
use lyra_ecs::world::World;
|
use lyra_ecs::World;
|
||||||
|
|
||||||
use crate::Access;
|
use crate::Access;
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,24 @@
|
||||||
use std::ptr::NonNull;
|
use std::ptr::NonNull;
|
||||||
|
|
||||||
use lyra_ecs::world::World;
|
use lyra_ecs::World;
|
||||||
|
|
||||||
|
/// An enum that is used to control if the Criteria was met or not.
|
||||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub enum CriteriaSchedule {
|
pub enum CriteriaSchedule {
|
||||||
|
/// The criteria was completely met and the system can continue to run.
|
||||||
Yes,
|
Yes,
|
||||||
|
/// The criteria was not met and must run next time the system batch is ran.
|
||||||
No,
|
No,
|
||||||
|
/// The criteria was met and the system can run.
|
||||||
|
/// After the system runs, the criteria should be checked again and may cause another
|
||||||
|
/// execution of the system.
|
||||||
YesAndLoop,
|
YesAndLoop,
|
||||||
|
/// The criteria was not met, but it should be checked again during this tick before giving
|
||||||
|
/// up. If the criteria returns `Yes` next check, the systems will run.
|
||||||
NoAndLoop,
|
NoAndLoop,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A Criteria can be used to conditionally execute [`BatchedSystem`](super::BatchedSystem).
|
||||||
pub trait Criteria {
|
pub trait Criteria {
|
||||||
/// Checks if this Criteria can run, and if it should check it again.
|
/// Checks if this Criteria can run, and if it should check it again.
|
||||||
///
|
///
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
use std::{any::Any, marker::PhantomData, ptr::NonNull};
|
use std::{any::Any, marker::PhantomData, ptr::NonNull};
|
||||||
|
|
||||||
use paste::paste;
|
use paste::paste;
|
||||||
use crate::{world::World, Access, ResourceObject, query::{Query, View, AsQuery, ResMut, Res}};
|
use crate::{World, Access, ResourceObject, query::{Query, ViewState, ResMut, Res}};
|
||||||
|
|
||||||
use super::{System, IntoSystem};
|
use super::{System, IntoSystem};
|
||||||
|
|
||||||
|
/// A trait that is used for fetching an argument for a [`FnSystem`].
|
||||||
pub trait FnArgFetcher {
|
pub trait FnArgFetcher {
|
||||||
/// stores data that persists after an execution of a system
|
/// stores data that persists after an execution of a system
|
||||||
type State: 'static;
|
type State: 'static;
|
||||||
|
@ -30,14 +31,20 @@ pub trait FnArgFetcher {
|
||||||
fn apply_deferred(state: Self::State, world: NonNull<World>);
|
fn apply_deferred(state: Self::State, world: NonNull<World>);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait FnArg {
|
/// A system that is implemented as a function.
|
||||||
type Fetcher: FnArgFetcher;
|
///
|
||||||
}
|
/// The arguments of the functions must implement `FnArgFetcher` so that the arguments can be
|
||||||
|
/// fetched on the fly.
|
||||||
|
///
|
||||||
|
/// ```fail_compile
|
||||||
|
/// fn enemy_movement_system(enemies: View<(&Health, &EnemyStats, &Movement)>) -> anyhow::Result<()> {
|
||||||
|
/// for (health, stats, movement) in enemies.iter() {
|
||||||
|
/// // ...
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
pub struct FnSystem<F, Args> {
|
pub struct FnSystem<F, Args> {
|
||||||
inner: F,
|
inner: F,
|
||||||
//#[allow(dead_code)]
|
|
||||||
//args: Args,
|
|
||||||
arg_state: Option<Vec<Box<dyn Any>>>,
|
arg_state: Option<Vec<Box<dyn Any>>>,
|
||||||
_marker: PhantomData<Args>,
|
_marker: PhantomData<Args>,
|
||||||
}
|
}
|
||||||
|
@ -129,22 +136,14 @@ impl_fn_system_tuple!{ A, B, C, D, E, F2, G, H, I, J, K, L, M, N }
|
||||||
impl_fn_system_tuple!{ A, B, C, D, E, F2, G, H, I, J, K, L, M, N, O }
|
impl_fn_system_tuple!{ A, B, C, D, E, F2, G, H, I, J, K, L, M, N, O }
|
||||||
impl_fn_system_tuple!{ A, B, C, D, E, F2, G, H, I, J, K, L, M, N, O, P }
|
impl_fn_system_tuple!{ A, B, C, D, E, F2, G, H, I, J, K, L, M, N, O, P }
|
||||||
|
|
||||||
/// An ArgFetcher implementation for query [`View`]s
|
/// An ArgFetcher implementation for query [`ViewState`]s
|
||||||
/* pub struct ViewArgFetcher<Q: AsQuery> {
|
impl<'c, Q, F> FnArgFetcher for ViewState<'c, Q, F>
|
||||||
query: Q::Query
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, Q: AsQuery> FnArg for View<'a, Q> {
|
|
||||||
type Fetcher = ViewArgFetcher<Q>;
|
|
||||||
} */
|
|
||||||
|
|
||||||
impl<'c, Q> FnArgFetcher for View<'c, Q>
|
|
||||||
where
|
where
|
||||||
Q: AsQuery,
|
Q: Query + 'static,
|
||||||
<Q as AsQuery>::Query: 'static
|
F: Query + 'static,
|
||||||
{
|
{
|
||||||
type State = Q::Query;
|
type State = (Q, F);
|
||||||
type Arg<'a, 'state> = View<'a, Q>;
|
type Arg<'a, 'state> = ViewState<'a, Q, F>;
|
||||||
|
|
||||||
fn world_access(&self) -> Access {
|
fn world_access(&self) -> Access {
|
||||||
todo!()
|
todo!()
|
||||||
|
@ -153,7 +152,8 @@ where
|
||||||
unsafe fn get<'a, 'state>(state: &'state mut Self::State, world: NonNull<World>) -> Self::Arg<'a, 'state> {
|
unsafe fn get<'a, 'state>(state: &'state mut Self::State, world: NonNull<World>) -> Self::Arg<'a, 'state> {
|
||||||
let world = &*world.as_ptr();
|
let world = &*world.as_ptr();
|
||||||
let arch = world.archetypes.values().collect();
|
let arch = world.archetypes.values().collect();
|
||||||
let v = View::new(world, state.clone(), arch);
|
let (query, filter) = state.clone();
|
||||||
|
let v = ViewState::new(world, query, filter, arch);
|
||||||
|
|
||||||
v
|
v
|
||||||
}
|
}
|
||||||
|
@ -161,17 +161,10 @@ where
|
||||||
fn apply_deferred(_: Self::State, _: NonNull<World>) { }
|
fn apply_deferred(_: Self::State, _: NonNull<World>) { }
|
||||||
|
|
||||||
fn create_state(_: NonNull<World>) -> Self::State {
|
fn create_state(_: NonNull<World>) -> Self::State {
|
||||||
<Q::Query as Query>::new()
|
(Q::new(), F::new())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An ArgFetcher implementation for borrowing the [`World`].
|
|
||||||
/* pub struct WorldArgFetcher;
|
|
||||||
|
|
||||||
impl<'a> FnArg for &'a World {
|
|
||||||
type Fetcher = WorldArgFetcher;
|
|
||||||
} */
|
|
||||||
|
|
||||||
impl FnArgFetcher for &'_ World {
|
impl FnArgFetcher for &'_ World {
|
||||||
type State = ();
|
type State = ();
|
||||||
type Arg<'a, 'state> = &'a World;
|
type Arg<'a, 'state> = &'a World;
|
||||||
|
@ -246,7 +239,7 @@ impl<R: ResourceObject> FnArgFetcher for ResMut<'_, R> {
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::ptr::NonNull;
|
use std::ptr::NonNull;
|
||||||
|
|
||||||
use crate::{tests::{Vec2, Vec3}, world::World, query::{QueryBorrow, View, ResMut}};
|
use crate::{tests::{Vec2, Vec3}, World, query::{QueryBorrow, ViewState, ResMut}};
|
||||||
use super::{System, IntoSystem};
|
use super::{System, IntoSystem};
|
||||||
|
|
||||||
struct SomeCounter(u32);
|
struct SomeCounter(u32);
|
||||||
|
@ -262,7 +255,7 @@ mod tests {
|
||||||
|
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
|
|
||||||
let test_system = |view: View<QueryBorrow<Vec2>>| -> anyhow::Result<()> {
|
let test_system = |view: ViewState<QueryBorrow<Vec2>, ()>| -> anyhow::Result<()> {
|
||||||
let mut vecs = vecs.to_vec();
|
let mut vecs = vecs.to_vec();
|
||||||
for v in view.into_iter() {
|
for v in view.into_iter() {
|
||||||
let pos = vecs.iter().position(|vec| *vec == *v)
|
let pos = vecs.iter().position(|vec| *vec == *v)
|
||||||
|
@ -291,7 +284,7 @@ mod tests {
|
||||||
|
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
|
|
||||||
let test_system = |view: View<(QueryBorrow<Vec2>, QueryBorrow<Vec3>)>| -> anyhow::Result<()> {
|
let test_system = |view: ViewState<(QueryBorrow<Vec2>, QueryBorrow<Vec3>), ()>| -> anyhow::Result<()> {
|
||||||
for (v2, v3) in view.into_iter() {
|
for (v2, v3) in view.into_iter() {
|
||||||
println!("Got v2 at '{:?}' and v3 at: '{:?}'", v2, v3);
|
println!("Got v2 at '{:?}' and v3 at: '{:?}'", v2, v3);
|
||||||
count += 1;
|
count += 1;
|
||||||
|
@ -392,7 +385,7 @@ mod tests {
|
||||||
world.spawn((Vec2::rand(), ));
|
world.spawn((Vec2::rand(), ));
|
||||||
world.add_resource(SomeCounter(0));
|
world.add_resource(SomeCounter(0));
|
||||||
|
|
||||||
let test_system = |mut counter: ResMut<SomeCounter>, view: View<QueryBorrow<Vec2>>| -> anyhow::Result<()> {
|
let test_system = |mut counter: ResMut<SomeCounter>, view: ViewState<QueryBorrow<Vec2>, ()>| -> anyhow::Result<()> {
|
||||||
for v2 in view.into_iter() {
|
for v2 in view.into_iter() {
|
||||||
println!("Got v2 at '{:?}'", v2);
|
println!("Got v2 at '{:?}'", v2);
|
||||||
// .0 is twice here since ResMut's tuple field is pub(crate).
|
// .0 is twice here since ResMut's tuple field is pub(crate).
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::{collections::{HashMap, VecDeque, HashSet}, ptr::NonNull};
|
||||||
|
|
||||||
use super::System;
|
use super::System;
|
||||||
|
|
||||||
use crate::{world::World, CommandQueue, Commands};
|
use crate::{World, CommandQueue, Commands};
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum GraphExecutorError {
|
pub enum GraphExecutorError {
|
||||||
|
@ -140,7 +140,7 @@ impl GraphExecutor {
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::ptr::NonNull;
|
use std::ptr::NonNull;
|
||||||
|
|
||||||
use crate::{world::World, query::{ResMut, View}, system::IntoSystem};
|
use crate::{query::{ResMut, View}, system::IntoSystem, World};
|
||||||
|
|
||||||
use super::GraphExecutor;
|
use super::GraphExecutor;
|
||||||
|
|
||||||
|
@ -152,7 +152,7 @@ mod tests {
|
||||||
|
|
||||||
let mut exec = GraphExecutor::new();
|
let mut exec = GraphExecutor::new();
|
||||||
|
|
||||||
let a_system = |view: View<ResMut<Vec<String>>>| -> anyhow::Result<()> {
|
let a_system = |view: View<ResMut<Vec<String>>, ()>| -> anyhow::Result<()> {
|
||||||
println!("System 'a' ran!");
|
println!("System 'a' ran!");
|
||||||
|
|
||||||
let mut order = view.into_iter().next().unwrap();
|
let mut order = view.into_iter().next().unwrap();
|
||||||
|
@ -161,7 +161,7 @@ mod tests {
|
||||||
Ok(())
|
Ok(())
|
||||||
};
|
};
|
||||||
|
|
||||||
let b_system = |view: View<ResMut<Vec<String>>>| -> anyhow::Result<()> {
|
let b_system = |view: View<ResMut<Vec<String>>, ()>| -> anyhow::Result<()> {
|
||||||
println!("System 'b' ran!");
|
println!("System 'b' ran!");
|
||||||
|
|
||||||
let mut order = view.into_iter().next().unwrap();
|
let mut order = view.into_iter().next().unwrap();
|
||||||
|
@ -170,7 +170,7 @@ mod tests {
|
||||||
Ok(())
|
Ok(())
|
||||||
};
|
};
|
||||||
|
|
||||||
let c_system = |view: View<ResMut<Vec<String>>>| -> anyhow::Result<()> {
|
let c_system = |view: View<ResMut<Vec<String>>, ()>| -> anyhow::Result<()> {
|
||||||
println!("System 'c' ran!");
|
println!("System 'c' ran!");
|
||||||
|
|
||||||
let mut order = view.into_iter().next().unwrap();
|
let mut order = view.into_iter().next().unwrap();
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
use std::ptr::NonNull;
|
use std::ptr::NonNull;
|
||||||
|
|
||||||
use crate::{world::World, Access};
|
use crate::{World, Access};
|
||||||
|
|
||||||
pub mod graph;
|
mod graph;
|
||||||
pub use graph::*;
|
pub use graph::*;
|
||||||
|
|
||||||
pub mod criteria;
|
mod criteria;
|
||||||
pub use criteria::*;
|
pub use criteria::*;
|
||||||
|
|
||||||
pub mod batched;
|
mod batched;
|
||||||
pub use batched::*;
|
pub use batched::*;
|
||||||
|
|
||||||
pub mod fn_sys;
|
mod fn_sys;
|
||||||
pub use fn_sys::*;
|
pub use fn_sys::*;
|
||||||
|
|
||||||
/// A system that does not mutate the world
|
/// A system that does not mutate the world
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use std::sync::atomic::{AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
|
||||||
/// TickTracker is used for tracking changes of [`Component`]s and entities.
|
/// TickTracker is used for tracking changes of [`Component`](crate::Component)s and entities.
|
||||||
///
|
///
|
||||||
/// TickTracker stores an [`AtomicU64`], making all operations on `TickTracker`, atomic as well.
|
/// TickTracker stores an [`AtomicU64`], making all operations on `TickTracker`, atomic as well.
|
||||||
/// Note that [`Tick::Clone`] only clones the inner value of atomic, and not the atomic itself.
|
/// Note that [`Tick::clone`] only clones the inner value of atomic, and not the atomic itself.
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct TickTracker {
|
pub struct TickTracker {
|
||||||
tick: AtomicU64,
|
tick: AtomicU64,
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
use std::{any::TypeId, cell::{Ref, RefMut}, collections::HashMap, ptr::NonNull};
|
use std::{any::TypeId, cell::{Ref, RefMut}, collections::HashMap, ptr::NonNull};
|
||||||
|
|
||||||
use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{dynamic::DynamicView, AsQuery, Query, View, ViewIter, ViewOne}, resource::ResourceData, ComponentInfo, DynTypeId, Entities, Entity, ResourceObject, Tick, TickTracker};
|
use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{dynamic::DynamicView, AsQuery, Query, ViewState, ViewIter, ViewOne}, resource::ResourceData, ComponentInfo, DynTypeId, Entities, Entity, ResourceObject, Tick, TickTracker};
|
||||||
|
|
||||||
/// The id of the entity for the Archetype.
|
/// The id of the entity for the Archetype.
|
||||||
/// The Archetype struct uses this as the index in the component columns
|
///
|
||||||
|
/// The Archetype uses this as the index in the component columns
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct ArchetypeEntityId(pub u64);
|
pub struct ArchetypeEntityId(pub u64);
|
||||||
|
|
||||||
|
@ -53,7 +54,7 @@ impl World {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Spawn the components into a reserved entity. Only do this with entities that
|
/// Spawn the components into a reserved entity. Only do this with entities that
|
||||||
/// were 'reserved' with [`World::reserve`]
|
/// were reserved with [`World::reserve_entity`].
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// Do not use this method with an entity that is currently alive, it WILL cause undefined behavior.
|
/// Do not use this method with an entity that is currently alive, it WILL cause undefined behavior.
|
||||||
|
@ -72,14 +73,14 @@ impl World {
|
||||||
|
|
||||||
if let Some(archetype) = archetype {
|
if let Some(archetype) = archetype {
|
||||||
// make at just one check to ensure you're not spawning twice
|
// make at just one check to ensure you're not spawning twice
|
||||||
debug_assert!(!archetype.entities.contains_key(&entity),
|
debug_assert!(!archetype.entity_ids.contains_key(&entity),
|
||||||
"You attempted to spawn components into an entity that already exists!");
|
"You attempted to spawn components into an entity that already exists!");
|
||||||
|
|
||||||
let arche_idx = archetype.add_entity(entity, bundle, &tick);
|
let arche_idx = archetype.add_entity(entity, bundle, &tick);
|
||||||
|
|
||||||
// Create entity record and store it
|
// Create entity record and store it
|
||||||
let record = Record {
|
let record = Record {
|
||||||
id: archetype.id,
|
id: archetype.id(),
|
||||||
index: arche_idx,
|
index: arche_idx,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -142,7 +143,7 @@ impl World {
|
||||||
let record = self.entities.entity_record(entity).unwrap();
|
let record = self.entities.entity_record(entity).unwrap();
|
||||||
let current_arch = self.archetypes.get(&record.id).unwrap();
|
let current_arch = self.archetypes.get(&record.id).unwrap();
|
||||||
|
|
||||||
let mut col_types: Vec<DynTypeId> = current_arch.columns.iter().map(|c| c.info.type_id).collect();
|
let mut col_types: Vec<DynTypeId> = current_arch.columns.iter().map(|c| c.info.type_id()).collect();
|
||||||
let orig_col = col_types.clone();
|
let orig_col = col_types.clone();
|
||||||
col_types.extend(bundle.type_ids());
|
col_types.extend(bundle.type_ids());
|
||||||
|
|
||||||
|
@ -157,7 +158,7 @@ impl World {
|
||||||
for (col_type, (col_ptr, col_info)) in orig_col.into_iter().zip(col_ptrs.into_iter()) {
|
for (col_type, (col_ptr, col_info)) in orig_col.into_iter().zip(col_ptrs.into_iter()) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let ptr = NonNull::new_unchecked(col_ptr.as_ptr()
|
let ptr = NonNull::new_unchecked(col_ptr.as_ptr()
|
||||||
.add(res_index.0 as usize * col_info.layout.size));
|
.add(res_index.0 as usize * col_info.layout().size()));
|
||||||
let col = arch.get_column_mut(col_type).unwrap();
|
let col = arch.get_column_mut(col_type).unwrap();
|
||||||
col.set_at(res_index.0 as _, ptr, tick);
|
col.set_at(res_index.0 as _, ptr, tick);
|
||||||
}
|
}
|
||||||
|
@ -169,10 +170,10 @@ impl World {
|
||||||
col.len += 1;
|
col.len += 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
arch.entities.insert(entity, res_index);
|
arch.entity_ids.insert(entity, res_index);
|
||||||
|
|
||||||
let new_record = Record {
|
let new_record = Record {
|
||||||
id: arch.id,
|
id: arch.id(),
|
||||||
index: res_index,
|
index: res_index,
|
||||||
};
|
};
|
||||||
self.entities.insert_entity_record(entity, new_record);
|
self.entities.insert_entity_record(entity, new_record);
|
||||||
|
@ -207,9 +208,20 @@ impl World {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// View into the world for a set of entities that satisfy the queries.
|
/// View into the world for a set of entities that satisfy the queries.
|
||||||
pub fn view_iter<T: 'static + AsQuery>(&self) -> ViewIter<T::Query> {
|
pub fn view<Q: AsQuery>(&self) -> ViewState<Q::Query, ()> {
|
||||||
|
self.filtered_view::<Q, ()>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// View into the world for a set of entities that satisfy the query and the filter.
|
||||||
|
pub fn filtered_view<Q: AsQuery, F: AsQuery>(&self) -> ViewState<Q::Query, F::Query> {
|
||||||
let archetypes = self.archetypes.values().collect();
|
let archetypes = self.archetypes.values().collect();
|
||||||
let v = View::<T>::new(self, T::Query::new(), archetypes);
|
ViewState::<Q::Query, F::Query>::new(self, Q::Query::new(), F::Query::new(), archetypes)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// View into the world for a set of entities that satisfy the queries.
|
||||||
|
pub fn view_iter<Q: AsQuery>(&self) -> ViewIter<Q::Query, ()> {
|
||||||
|
let archetypes = self.archetypes.values().collect();
|
||||||
|
let v = ViewState::new(self, Q::Query::new(), (), archetypes);
|
||||||
v.into_iter()
|
v.into_iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,7 +229,7 @@ impl World {
|
||||||
DynamicView::new(self)
|
DynamicView::new(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn view_one<T: 'static + AsQuery>(&self, entity: Entity) -> ViewOne<T::Query> {
|
pub fn view_one<T: AsQuery>(&self, entity: Entity) -> ViewOne<T::Query> {
|
||||||
ViewOne::new(self, entity.id, T::Query::new())
|
ViewOne::new(self, entity.id, T::Query::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,7 +264,7 @@ impl World {
|
||||||
|
|
||||||
/// Gets a resource from the World.
|
/// Gets a resource from the World.
|
||||||
///
|
///
|
||||||
/// Will panic if the resource is not in the world. See [`try_get_resource`] for
|
/// Will panic if the resource is not in the world. See [`World::try_get_resource`] for
|
||||||
/// a function that returns an option.
|
/// a function that returns an option.
|
||||||
pub fn get_resource<T: 'static>(&self) -> Ref<T> {
|
pub fn get_resource<T: 'static>(&self) -> Ref<T> {
|
||||||
self.resources.get(&TypeId::of::<T>())
|
self.resources.get(&TypeId::of::<T>())
|
||||||
|
@ -275,7 +287,7 @@ impl World {
|
||||||
|
|
||||||
/// Gets a mutable borrow of a resource from the World.
|
/// Gets a mutable borrow of a resource from the World.
|
||||||
///
|
///
|
||||||
/// Will panic if the resource is not in the world. See [`try_get_resource_mut`] for
|
/// Will panic if the resource is not in the world. See [`World::try_get_resource_mut`] for
|
||||||
/// a function that returns an option.
|
/// a function that returns an option.
|
||||||
pub fn get_resource_mut<T: 'static>(&self) -> RefMut<T> {
|
pub fn get_resource_mut<T: 'static>(&self) -> RefMut<T> {
|
||||||
self.resources.get(&TypeId::of::<T>())
|
self.resources.get(&TypeId::of::<T>())
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use instant::Instant;
|
use instant::Instant;
|
||||||
use lyra_ecs::{Component, world::World};
|
use lyra_ecs::{Component, World};
|
||||||
use lyra_reflect::Reflect;
|
use lyra_reflect::Reflect;
|
||||||
|
|
||||||
use crate::{plugin::Plugin, game::GameStages};
|
use crate::{plugin::Plugin, game::GameStages};
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::{sync::Arc, collections::VecDeque, ptr::NonNull};
|
||||||
|
|
||||||
use async_std::task::block_on;
|
use async_std::task::block_on;
|
||||||
|
|
||||||
use lyra_ecs::{world::World, system::{System, IntoSystem}};
|
use lyra_ecs::{World, system::{System, IntoSystem}};
|
||||||
use tracing::{info, error, Level};
|
use tracing::{info, error, Level};
|
||||||
use tracing_appender::non_blocking;
|
use tracing_appender::non_blocking;
|
||||||
use tracing_subscriber::{
|
use tracing_subscriber::{
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::{collections::HashMap, ops::Deref, hash::{Hash, DefaultHasher, Hasher}, fmt::Debug};
|
use std::{collections::HashMap, ops::Deref, hash::{Hash, DefaultHasher, Hasher}, fmt::Debug};
|
||||||
|
|
||||||
use glam::Vec2;
|
use glam::Vec2;
|
||||||
use lyra_ecs::world::World;
|
use lyra_ecs::World;
|
||||||
use lyra_reflect::Reflect;
|
use lyra_reflect::Reflect;
|
||||||
|
|
||||||
use crate::{plugin::Plugin, game::GameStages, EventQueue};
|
use crate::{plugin::Plugin, game::GameStages, EventQueue};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::ptr::NonNull;
|
use std::ptr::NonNull;
|
||||||
|
|
||||||
use glam::Vec2;
|
use glam::Vec2;
|
||||||
use lyra_ecs::{world::World, system::IntoSystem};
|
use lyra_ecs::{World, system::IntoSystem};
|
||||||
use winit::event::MouseScrollDelta;
|
use winit::event::MouseScrollDelta;
|
||||||
|
|
||||||
use crate::{EventQueue, plugin::Plugin, game::GameStages};
|
use crate::{EventQueue, plugin::Plugin, game::GameStages};
|
||||||
|
|
|
@ -2,7 +2,7 @@ pub mod point;
|
||||||
pub mod directional;
|
pub mod directional;
|
||||||
pub mod spotlight;
|
pub mod spotlight;
|
||||||
|
|
||||||
use lyra_ecs::{Entity, Tick, world::World, query::{Entities, TickOf}};
|
use lyra_ecs::{Entity, Tick, World, query::{Entities, TickOf}};
|
||||||
pub use point::*;
|
pub use point::*;
|
||||||
pub use spotlight::*;
|
pub use spotlight::*;
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ use instant::Instant;
|
||||||
use itertools::izip;
|
use itertools::izip;
|
||||||
use lyra_ecs::Entity;
|
use lyra_ecs::Entity;
|
||||||
use lyra_ecs::query::{Entities, TickOf};
|
use lyra_ecs::query::{Entities, TickOf};
|
||||||
use lyra_ecs::world::World;
|
use lyra_ecs::World;
|
||||||
use tracing::{debug, warn};
|
use tracing::{debug, warn};
|
||||||
use wgpu::{BindGroupLayout, Limits};
|
use wgpu::{BindGroupLayout, Limits};
|
||||||
use wgpu::util::DeviceExt;
|
use wgpu::util::DeviceExt;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use glam::{Vec2, IVec2};
|
use glam::{Vec2, IVec2};
|
||||||
use lyra_ecs::world::World;
|
use lyra_ecs::World;
|
||||||
use tracing::{warn, error};
|
use tracing::{warn, error};
|
||||||
use winit::{window::{Window, Fullscreen}, dpi::{LogicalPosition, LogicalSize, PhysicalPosition}, error::ExternalError};
|
use winit::{window::{Window, Fullscreen}, dpi::{LogicalPosition, LogicalSize, PhysicalPosition}, error::ExternalError};
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
use std::{any::TypeId, any::Any, cell::{Ref, RefMut}};
|
use std::{any::TypeId, any::Any, cell::{Ref, RefMut}};
|
||||||
|
|
||||||
use lyra_ecs::{world::World, DynamicBundle, Component, Entity, ComponentInfo};
|
use lyra_ecs::World;
|
||||||
|
|
||||||
extern crate self as lyra_reflect;
|
extern crate self as lyra_reflect;
|
||||||
|
|
||||||
|
|
|
@ -62,6 +62,8 @@ impl ModelLoader {
|
||||||
|
|
||||||
fn process_node(buffers: &Vec<Vec<u8>>, materials: &Vec<Material>, node: gltf::Node<'_>) -> Vec<Mesh> {
|
fn process_node(buffers: &Vec<Vec<u8>>, materials: &Vec<Material>, node: gltf::Node<'_>) -> Vec<Mesh> {
|
||||||
let mut meshes = vec![];
|
let mut meshes = vec![];
|
||||||
|
//node.transform()
|
||||||
|
|
||||||
if let Some(mesh) = node.mesh() {
|
if let Some(mesh) = node.mesh() {
|
||||||
for prim in mesh.primitives() {
|
for prim in mesh.primitives() {
|
||||||
let reader = prim.reader(|buf| Some(buffers[buf.index()].as_slice()));
|
let reader = prim.reader(|buf| Some(buffers[buf.index()].as_slice()));
|
||||||
|
|
|
@ -27,7 +27,7 @@ impl<'a> Fetch<'a> for FetchDynamicType {
|
||||||
unsafe fn get_item(&mut self, entity: ArchetypeEntityId) -> Self::Item {
|
unsafe fn get_item(&mut self, entity: ArchetypeEntityId) -> Self::Item {
|
||||||
let ptr = unsafe { self.col.as_ref().borrow_ptr() };
|
let ptr = unsafe { self.col.as_ref().borrow_ptr() };
|
||||||
let ptr = NonNull::new_unchecked(ptr.as_ptr()
|
let ptr = NonNull::new_unchecked(ptr.as_ptr()
|
||||||
.add(entity.0 as usize * self.info.layout.size));
|
.add(entity.0 as usize * self.info.layout().size()));
|
||||||
|
|
||||||
DynamicType {
|
DynamicType {
|
||||||
info: self.info,
|
info: self.info,
|
||||||
|
@ -96,7 +96,7 @@ impl Iterator for DynamicViewIter {
|
||||||
}
|
}
|
||||||
|
|
||||||
let arch = unsafe { self.archetypes.get_unchecked(self.next_archetype - 1).as_ref() };
|
let arch = unsafe { self.archetypes.get_unchecked(self.next_archetype - 1).as_ref() };
|
||||||
let entity = arch.entity_of_index(entity_index).unwrap();
|
let entity = arch.entity_at_index(entity_index).unwrap();
|
||||||
let row = DynamicViewRow {
|
let row = DynamicViewRow {
|
||||||
entity,
|
entity,
|
||||||
item: fetch_res,
|
item: fetch_res,
|
||||||
|
@ -112,7 +112,7 @@ impl Iterator for DynamicViewIter {
|
||||||
self.next_archetype += 1;
|
self.next_archetype += 1;
|
||||||
let arch = unsafe { self.archetypes.get_unchecked(arch_id).as_ref() };
|
let arch = unsafe { self.archetypes.get_unchecked(arch_id).as_ref() };
|
||||||
|
|
||||||
if arch.entities().len() == 0 {
|
if arch.entity_indexes().len() == 0 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,7 +126,7 @@ impl Iterator for DynamicViewIter {
|
||||||
.map(|q| unsafe { q.fetch(world, ArchetypeId(arch_id as u64), arch) } )
|
.map(|q| unsafe { q.fetch(world, ArchetypeId(arch_id as u64), arch) } )
|
||||||
.map(|f| FetchDynamicType::from(f))
|
.map(|f| FetchDynamicType::from(f))
|
||||||
.collect();
|
.collect();
|
||||||
self.component_indices = 0..arch.entities().len() as u64;
|
self.component_indices = 0..arch.entity_indexes().len() as u64;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -167,7 +167,7 @@ impl ReflectedIterator {
|
||||||
|
|
||||||
let mut dynamic_row = vec![];
|
let mut dynamic_row = vec![];
|
||||||
for d in row.item.iter() {
|
for d in row.item.iter() {
|
||||||
let id = d.info.type_id.as_rust();
|
let id = d.info.type_id().as_rust();
|
||||||
let reflected_components =
|
let reflected_components =
|
||||||
unsafe { self.reflected_components.as_ref().unwrap().as_ref() };
|
unsafe { self.reflected_components.as_ref().unwrap().as_ref() };
|
||||||
|
|
||||||
|
|
|
@ -192,14 +192,14 @@ impl elua::Userdata for ScriptWorldPtr {
|
||||||
let lua_comp = reflect_user_data(ud);
|
let lua_comp = reflect_user_data(ud);
|
||||||
let refl_comp =
|
let refl_comp =
|
||||||
lua_comp.reflect_branch.as_component_unchecked();
|
lua_comp.reflect_branch.as_component_unchecked();
|
||||||
refl_comp.info.type_id.as_rust()
|
refl_comp.info.type_id().as_rust()
|
||||||
}
|
},
|
||||||
elua::Value::Table(tbl) => {
|
elua::Value::Table(tbl) => {
|
||||||
let name: String = tbl.get(elua::MetaMethod::Name)?;
|
let name: String = tbl.get(elua::MetaMethod::Name)?;
|
||||||
|
|
||||||
let lookup = world.get_resource::<LuaTableProxyLookup>();
|
let lookup = world.get_resource::<LuaTableProxyLookup>();
|
||||||
*lookup.typeid_from_name.get(&name).unwrap()
|
*lookup.typeid_from_name.get(&name).unwrap()
|
||||||
}
|
},
|
||||||
_ => {
|
_ => {
|
||||||
panic!("A userdata or table value was not returned!");
|
panic!("A userdata or table value was not returned!");
|
||||||
// TODO: Handle properly
|
// TODO: Handle properly
|
||||||
|
@ -209,8 +209,8 @@ impl elua::Userdata for ScriptWorldPtr {
|
||||||
// update the component tick
|
// update the component tick
|
||||||
let world = unsafe { this.inner.as_mut() };
|
let world = unsafe { this.inner.as_mut() };
|
||||||
let arch = world.entity_archetype_mut(row.entity).unwrap();
|
let arch = world.entity_archetype_mut(row.entity).unwrap();
|
||||||
let idx = arch.entities().get(&row.entity).unwrap().clone();
|
let idx = arch.entity_indexes().get(&row.entity).unwrap().clone();
|
||||||
let c = arch.get_column_mut(lua_typeid.into()).unwrap();
|
let c = arch.get_column_mut(lua_typeid).unwrap();
|
||||||
c.entity_ticks[idx.0 as usize] = current;
|
c.entity_ticks[idx.0 as usize] = current;
|
||||||
|
|
||||||
// apply the new component data
|
// apply the new component data
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::ptr::NonNull;
|
use std::ptr::NonNull;
|
||||||
|
|
||||||
use lyra_ecs::{world::World, Entity};
|
use lyra_ecs::{World, Entity};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ScriptEntity(pub Entity);
|
pub struct ScriptEntity(pub Entity);
|
||||||
|
|
Loading…
Reference in New Issue