805 lines
28 KiB
Rust
805 lines
28 KiB
Rust
use std::{any::TypeId, collections::HashMap, ops::Deref, ptr::NonNull};
|
|
|
|
use atomic_refcell::{AtomicRef, AtomicRefMut};
|
|
|
|
use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{dynamic::DynamicView, AsFilter, AsQuery, Query, Res, ResMut, ViewIter, ViewOne, ViewState}, resource::ResourceData, ComponentInfo, DynTypeId, DynamicBundle, Entities, Entity, ResourceObject, Tick, TickTracker, TrackedResource};
|
|
|
|
/// The id of the entity for the Archetype.
|
|
///
|
|
/// The Archetype uses this as the index in the component columns
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
|
pub struct ArchetypeEntityId(pub u64);
|
|
|
|
impl Deref for ArchetypeEntityId {
|
|
type Target = u64;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
|
pub struct Record {
|
|
pub id: ArchetypeId,
|
|
pub index: ArchetypeEntityId,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct World {
|
|
pub archetypes: HashMap<ArchetypeId, Archetype>,
|
|
next_archetype_id: ArchetypeId,
|
|
resources: HashMap<TypeId, ResourceData>,
|
|
tracker: TickTracker,
|
|
pub entities: Entities,
|
|
}
|
|
|
|
impl Default for World {
|
|
fn default() -> Self {
|
|
Self {
|
|
archetypes: HashMap::new(),
|
|
next_archetype_id: ArchetypeId(0),
|
|
resources: HashMap::new(),
|
|
tracker: TickTracker::new(),
|
|
entities: Entities::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl World {
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
/// Reserves an entity in the world
|
|
pub fn reserve_entity(&mut self) -> Entity {
|
|
self.entities.reserve()
|
|
}
|
|
|
|
pub fn spawn<B>(&mut self, bundle: B) -> Entity
|
|
where
|
|
B: Bundle
|
|
{
|
|
let new_entity = self.reserve_entity();
|
|
self.spawn_into(new_entity, bundle);
|
|
new_entity
|
|
}
|
|
|
|
/// Spawn the components into a reserved entity. Only do this with entities that
|
|
/// were reserved with [`World::reserve_entity`].
|
|
///
|
|
/// # Safety
|
|
/// Do not use this method with an entity that is currently alive, it WILL cause undefined behavior.
|
|
pub fn spawn_into<B>(&mut self, entity: Entity, bundle: B)
|
|
where
|
|
B: Bundle
|
|
{
|
|
let tick = self.current_tick();
|
|
let bundle_types = bundle.type_ids();
|
|
|
|
// try to find an archetype
|
|
let archetype = self.archetypes
|
|
.values_mut()
|
|
.find(|a| a.is_archetype_for(&bundle_types));
|
|
|
|
if let Some(archetype) = archetype {
|
|
// make at just one check to ensure you're not spawning twice
|
|
debug_assert!(!archetype.entity_ids.contains_key(&entity),
|
|
"You attempted to spawn components into an entity that already exists!");
|
|
|
|
let arche_idx = archetype.add_entity(entity, bundle, &tick);
|
|
|
|
// Create entity record and store it
|
|
let record = Record {
|
|
id: archetype.id(),
|
|
index: arche_idx,
|
|
};
|
|
|
|
self.entities.insert_entity_record(entity, record);
|
|
}
|
|
// create a new archetype if one isn't found
|
|
else {
|
|
// create archetype
|
|
let new_arch_id = self.next_archetype_id.increment();
|
|
let mut archetype = Archetype::from_bundle_info(new_arch_id, bundle.info());
|
|
let entity_arch_id = archetype.add_entity(entity, bundle, &tick);
|
|
|
|
// store archetype
|
|
self.archetypes.insert(new_arch_id, archetype);
|
|
|
|
// Create entity record and store it
|
|
let record = Record {
|
|
id: new_arch_id,
|
|
// this is the first entity in the archetype
|
|
index: entity_arch_id,
|
|
};
|
|
|
|
self.entities.insert_entity_record(entity, record);
|
|
}
|
|
}
|
|
|
|
/// Despawn an entity from the World
|
|
pub fn despawn(&mut self, entity: Entity) {
|
|
let tick = self.current_tick();
|
|
if let Some(record) = self.entities.arch_index.get_mut(&entity.id) {
|
|
let arch = self.archetypes.get_mut(&record.id).unwrap();
|
|
|
|
if let Some((moved, new_index)) = arch.remove_entity(entity, &tick) {
|
|
// replace the archetype index of the moved index with its new index.
|
|
self.entities.arch_index.get_mut(&moved.id).unwrap().index = new_index;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Insert a component bundle into an existing entity.
|
|
///
|
|
/// If the components are already existing on the entity, they will be updated, else the
|
|
/// entity will be moved to a different Archetype that can store the entity. That may
|
|
/// involve creating a new Archetype.
|
|
pub fn insert<B>(&mut self, entity: Entity, bundle: B)
|
|
where
|
|
B: Bundle
|
|
{
|
|
let tick = self.current_tick();
|
|
let record = self.entities.entity_record(entity);
|
|
|
|
if record.is_none() {
|
|
//let mut combined_column_infos: Vec<ComponentInfo> = bundle.info().columns.iter().map(|c| c.info).collect();
|
|
let new_arch_id = self.next_archetype_id.increment();
|
|
let mut archetype = Archetype::from_bundle_info(new_arch_id, bundle.info());
|
|
|
|
let mut dbun = DynamicBundle::new();
|
|
dbun.push_bundle(bundle);
|
|
|
|
let entity_arch_id = archetype.add_entity(entity, dbun, &tick);
|
|
|
|
self.archetypes.insert(new_arch_id, archetype);
|
|
|
|
// Create entity record and store it
|
|
let record = Record {
|
|
id: new_arch_id,
|
|
index: entity_arch_id,
|
|
};
|
|
|
|
self.entities.insert_entity_record(entity, record);
|
|
|
|
return;
|
|
}
|
|
|
|
let record = record.unwrap();
|
|
|
|
let current_arch = self.archetypes.get(&record.id).unwrap();
|
|
let current_arch_len = current_arch.len();
|
|
|
|
let mut contains_all = true;
|
|
for id in bundle.type_ids() {
|
|
contains_all = contains_all && current_arch.get_column(id).is_some();
|
|
}
|
|
|
|
if contains_all {
|
|
let current_arch = self.archetypes.get_mut(&record.id).unwrap();
|
|
let entry_idx = *current_arch.entity_indexes()
|
|
.get(&entity).unwrap();
|
|
|
|
bundle.take(|ptr, id, _info| {
|
|
let col = current_arch.get_column_mut(id).unwrap();
|
|
unsafe { col.set_at(entry_idx.0 as _, ptr, tick) };
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
// contains the type ids for the old component columns + the ids for the new components
|
|
let mut combined_column_types: Vec<DynTypeId> = current_arch.columns.iter().map(|c| c.info.type_id()).collect();
|
|
combined_column_types.extend(bundle.type_ids());
|
|
|
|
// contains the ComponentInfo for the old component columns + the info for the new components
|
|
let mut combined_column_infos: Vec<ComponentInfo> = current_arch.columns.iter().map(|c| c.info).collect();
|
|
combined_column_infos.extend(bundle.info());
|
|
|
|
// pointers only for the old columns
|
|
let old_columns: Vec<(NonNull<u8>, ComponentInfo)> = current_arch.columns.iter()
|
|
.map(|c| unsafe { (NonNull::new_unchecked(c.borrow_ptr().as_ptr()), c.info) })
|
|
.collect();
|
|
|
|
// try to find an archetype that this entity and its new components can fit into
|
|
if let Some(arch) = self.archetypes.values_mut().find(|a| a.is_archetype_for(&combined_column_types)) {
|
|
let mut dbun = DynamicBundle::new();
|
|
// move old entity components into new archetype columns
|
|
for (col_ptr, col_info) in old_columns.into_iter() {
|
|
unsafe {
|
|
let ptr = NonNull::new_unchecked(col_ptr.as_ptr()
|
|
.add(record.index.0 as usize * col_info.layout().size()));
|
|
dbun.push_unknown(ptr, col_info);
|
|
}
|
|
}
|
|
dbun.push_bundle(bundle);
|
|
|
|
let res_index = arch.add_entity(entity, dbun, &tick);
|
|
arch.ensure_synced();
|
|
|
|
let new_record = Record {
|
|
id: arch.id(),
|
|
index: res_index,
|
|
};
|
|
self.entities.insert_entity_record(entity, new_record);
|
|
} else {
|
|
if current_arch_len == 1 {
|
|
// if this entity is the only entity for this archetype, add more columns to it
|
|
let current_arch = self.archetypes.get_mut(&record.id).unwrap();
|
|
current_arch.extend(&tick, vec![bundle]);
|
|
return;
|
|
}
|
|
|
|
let new_arch_id = self.next_archetype_id.increment();
|
|
let mut archetype = Archetype::from_bundle_info(new_arch_id, combined_column_infos);
|
|
|
|
let mut dbun = DynamicBundle::new();
|
|
for (column_ptr, column_info) in old_columns.into_iter() {
|
|
unsafe {
|
|
// ptr of component for the entity
|
|
let comp_ptr = NonNull::new_unchecked(column_ptr.as_ptr()
|
|
.add(record.index.0 as usize * column_info.layout().size()));
|
|
dbun.push_unknown(comp_ptr, column_info);
|
|
}
|
|
}
|
|
dbun.push_bundle(bundle);
|
|
|
|
let entity_arch_id = archetype.add_entity(entity, dbun, &tick);
|
|
|
|
self.archetypes.insert(new_arch_id, archetype);
|
|
|
|
// Create entity record and store it
|
|
let record = Record {
|
|
id: new_arch_id,
|
|
index: entity_arch_id,
|
|
};
|
|
|
|
self.entities.insert_entity_record(entity, record);
|
|
}
|
|
|
|
let current_arch = self.archetypes.get_mut(&record.id).unwrap();
|
|
if let Some((en, enar)) = current_arch.remove_entity(entity, &tick) {
|
|
let rec = Record {
|
|
id: current_arch.id(),
|
|
index: enar
|
|
};
|
|
self.entities.insert_entity_record(en, rec);
|
|
}
|
|
|
|
current_arch.ensure_synced();
|
|
}
|
|
|
|
/// A method used for debugging implementation details of the ECS.
|
|
///
|
|
/// Here's an example of the output:
|
|
/// ```nobuild
|
|
/// Entities
|
|
/// 1 in archetype 0 at 0
|
|
/// 0 in archetype 1 at 0
|
|
/// 2 in archetype 0 at 1
|
|
/// 3 in archetype 2 at 0
|
|
/// Arch 1 -- 1 entities
|
|
/// Col 175564825027445222460146453544114453753
|
|
/// 0: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 63 0 0 128 63 0 0 128 63 0 0 128 63 78 86 0 0
|
|
/// Col 162279302565774655543278578489329315472
|
|
/// 0: 0 0 32 65 0 0 32 65 0 0 32 65 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 63 0 0 128 63 0 0 128 63 0 0 128 63 0 0 0 0
|
|
/// Col 24291284537013640759061027938209843602
|
|
/// 0: 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
|
/// Arch 2 -- 1 entities
|
|
/// Col 175564825027445222460146453544114453753
|
|
/// 0: 0 0 0 0 0 0 0 0 0 0 0 0 237 127 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 63 0 0 128 63 0 0 128 63 0 0 128 63 78 86 0 0
|
|
/// Col 162279302565774655543278578489329315472
|
|
/// 0: 0 0 76 66 0 0 170 66 0 0 136 65 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 63 0 0 128 63 0 0 128 63 0 0 128 63 0 0 0 0
|
|
/// Col 142862377085187052737282554588643015580
|
|
/// 0: 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
|
/// Arch 0 -- 2 entities
|
|
/// Col 175564825027445222460146453544114453753
|
|
/// 0: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 32 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 63 0 0 128 63 0 0 128 63 0 0 128 63 0 0 0 0
|
|
/// 1: 0 0 0 0 0 0 0 0 0 0 0 0 237 127 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 63 0 0 128 63 0 0 128 63 0 0 128 63 78 86 0 0
|
|
/// Col 162279302565774655543278578489329315472
|
|
/// 0: 0 0 112 65 0 0 112 65 0 0 112 65 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 63 0 0 128 63 0 0 128 63 0 0 128 63 0 0 0 0
|
|
/// 1: 0 0 27 67 0 0 184 65 0 0 192 64 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 63 0 0 128 63 0 0 128 63 0 0 128 63 0 0 0 0
|
|
/// Col 142862377085187052737282554588643015580
|
|
/// 0: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
|
/// 1: 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
|
/// Col 24291284537013640759061027938209843602
|
|
/// 0: 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
|
/// 1: 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
|
/// Arch 3 -- 0 entities
|
|
/// Col 175564825027445222460146453544114453753
|
|
/// Col 162279302565774655543278578489329315472
|
|
/// ```
|
|
///
|
|
/// This output prints all Entity ids, the archetype they're in, and the index that they're
|
|
/// in inside of the archetype. Additionally, the archetypes are printing, including their
|
|
/// columns type ids, and the contents of the type ids. This output can be used to debug
|
|
/// the contents of entities inside the archetypes.
|
|
///
|
|
/// Below is a template of the output:
|
|
/// ```nobuild
|
|
/// Entities
|
|
/// %ENTITY_ID% in archetype %ARCHETYPE_ID% at %INDEX%
|
|
/// Arch ID -- %ARCHETYPE_LEN% entities
|
|
/// %FOR EACH COL%
|
|
/// Col COLUMN_COMPONENT_TYPE_ID
|
|
/// %FOR EACH ENTITY%
|
|
/// %ENTITY_INDEX%: %COMPONENT_BYTES%
|
|
/// ```
|
|
/// If the template above doesn't help you in understanding the output, read the source code
|
|
/// of the function. The source code is pretty simple.
|
|
pub fn debug_print_world(&self) {
|
|
println!("Entities");
|
|
for (en, rec) in &self.entities.arch_index {
|
|
println!(" {} in archetype {} at {}", en.0, rec.id.0, rec.index.0);
|
|
}
|
|
|
|
for arch in self.archetypes.values() {
|
|
println!("Arch {} -- {} entities", arch.id().0, arch.len());
|
|
|
|
for col in &arch.columns {
|
|
// no clue if doing this is stable, but this is a debug function so :shrug:
|
|
let tyid: u128 = unsafe { std::mem::transmute(col.info.type_id().as_rust()) };
|
|
println!(" Col {}", tyid);
|
|
|
|
for en in 0..col.len {
|
|
// get the ptr starting at the component
|
|
let p = col.borrow_ptr();
|
|
let p = unsafe { p.as_ptr().add(en * col.info.layout().size()) };
|
|
|
|
print!(" {}: ", en);
|
|
// print each byte of the component
|
|
for i in 0..col.info.layout().size() {
|
|
let d = unsafe { *p.add(i) };
|
|
print!("{} ", d);
|
|
}
|
|
println!();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn entity_archetype(&self, entity: Entity) -> Option<&Archetype> {
|
|
self.entities.entity_record(entity)
|
|
.and_then(|record| self.archetypes.get(&record.id))
|
|
}
|
|
|
|
pub fn entity_archetype_mut(&mut self, entity: Entity) -> Option<&mut Archetype> {
|
|
self.entities.entity_record(entity)
|
|
.and_then(|record| self.archetypes.get_mut(&record.id))
|
|
}
|
|
|
|
/// View into the world for a set of entities that satisfy the queries.
|
|
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: AsFilter>(&self) -> ViewState<Q::Query, F::Filter> {
|
|
let archetypes = self.archetypes.values().collect();
|
|
ViewState::<Q::Query, F::Filter>::new(self, Q::Query::new(), F::Filter::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()
|
|
}
|
|
|
|
/// View into the world for a set of entities that satisfy the queries.
|
|
pub fn filtered_view_iter<Q: AsQuery, F: AsFilter>(&self) -> ViewIter<Q::Query, F::Filter> {
|
|
let archetypes = self.archetypes.values().collect();
|
|
let v = ViewState::new(self, Q::Query::new(), F::Filter::new(), archetypes);
|
|
v.into_iter()
|
|
}
|
|
|
|
pub fn dynamic_view(&self) -> DynamicView {
|
|
DynamicView::new(self)
|
|
}
|
|
|
|
pub fn view_one<T: AsQuery>(&self, entity: Entity) -> ViewOne<T::Query> {
|
|
ViewOne::new(self, entity.id, T::Query::new())
|
|
}
|
|
|
|
/// Add a resource to the world.
|
|
pub fn add_resource<T: ResourceObject>(&mut self, data: T) {
|
|
self.resources.insert(TypeId::of::<T>(), ResourceData::new(data, self.current_tick()));
|
|
}
|
|
|
|
/// Add the default value of a resource.
|
|
///
|
|
/// > Note: This will replace existing values.
|
|
pub fn add_resource_default<T: ResourceObject + Default>(&mut self) {
|
|
self.resources.insert(TypeId::of::<T>(), ResourceData::new(T::default(), self.current_tick()));
|
|
}
|
|
|
|
/// Add the default value of a resource if it does not already exist.
|
|
///
|
|
/// Returns a boolean indicating if the resource was added.
|
|
pub fn add_resource_default_if_absent<T: ResourceObject + Default>(&mut self) -> bool {
|
|
let id = TypeId::of::<T>();
|
|
if !self.resources.contains_key(&id) {
|
|
self.resources.insert(id, ResourceData::new(T::default(), self.current_tick()));
|
|
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
/// Get a resource from the world, or insert it into the world with the provided
|
|
/// `fn` and return it.
|
|
pub fn get_resource_or_else<T: ResourceObject, F>(&mut self, f: F) -> ResMut<T>
|
|
where
|
|
F: Fn() -> T + 'static
|
|
{
|
|
let tick = self.current_tick();
|
|
let res = self.resources.entry(TypeId::of::<T>())
|
|
.or_insert_with(|| ResourceData::new(f(), tick));
|
|
|
|
ResMut {
|
|
inner: res.data.borrow_mut(),
|
|
world_tick: tick,
|
|
_marker: std::marker::PhantomData::<T>,
|
|
}
|
|
}
|
|
|
|
/// Get a resource from the world, or insert its default value.
|
|
pub fn get_resource_or_default<T: ResourceObject + Default>(&mut self) -> ResMut<T>
|
|
{
|
|
let tick = self.current_tick();
|
|
let res = self.resources.entry(TypeId::of::<T>())
|
|
.or_insert_with(|| ResourceData::new(T::default(), tick));
|
|
|
|
ResMut {
|
|
inner: res.data.borrow_mut(),
|
|
world_tick: tick,
|
|
_marker: std::marker::PhantomData::<T>,
|
|
}
|
|
}
|
|
|
|
/// Gets a resource from the World.
|
|
pub fn get_resource<T: ResourceObject>(&self) -> Option<Res<T>> {
|
|
self.get_tracked_resource::<T>().map(|r| Res {
|
|
inner: r,
|
|
world_tick: self.current_tick(),
|
|
_marker: std::marker::PhantomData::<T>,
|
|
})
|
|
}
|
|
|
|
/// Get the tick of a resource.
|
|
///
|
|
/// This tick represents the last time the resource was mutated.
|
|
pub fn get_resource_tick<T: ResourceObject>(&self) -> Option<Tick> {
|
|
self.get_tracked_resource::<T>().map(|r| r.tick)
|
|
}
|
|
|
|
/// Gets a reference to a change tracked resource.
|
|
///
|
|
/// You will have to manually downcast the inner resource. Most people don't need this, see
|
|
/// [`World::get_resource`].
|
|
pub fn get_tracked_resource<T: ResourceObject>(&self) -> Option<AtomicRef<TrackedResource<dyn ResourceObject>>> {
|
|
self.resources.get(&TypeId::of::<T>())
|
|
.map(|r| r.data.borrow())
|
|
}
|
|
|
|
/// Gets a mutable borrow to a change tracked resource.
|
|
///
|
|
/// You will have to manually downcast the inner resource. Most people don't need this, see
|
|
/// [`World::get_resource_mut`].
|
|
pub fn get_tracked_resource_mut<T: ResourceObject>(&self) -> Option<AtomicRefMut<TrackedResource<dyn ResourceObject>>> {
|
|
self.resources.get(&TypeId::of::<T>())
|
|
.map(|r| r.data.borrow_mut())
|
|
}
|
|
|
|
/// Returns a boolean indicating if the resource changed.
|
|
///
|
|
/// This will return false if the resource doesn't exist.
|
|
pub fn has_resource_changed<T: ResourceObject>(&self) -> bool {
|
|
let tick = self.current_tick();
|
|
self.resources.get(&TypeId::of::<T>())
|
|
.map(|r| r.changed(tick))
|
|
.unwrap_or(false)
|
|
}
|
|
|
|
/// Returns the [`Tick`] that the resource was last modified at.
|
|
pub fn resource_tick<T: ResourceObject>(&self) -> Option<Tick> {
|
|
self.resources.get(&TypeId::of::<T>())
|
|
.map(|r| r.data.borrow().tick)
|
|
}
|
|
|
|
/// Returns boolean indicating if the World contains a resource of type `T`.
|
|
pub fn has_resource<T: ResourceObject>(&self) -> bool {
|
|
self.resources.contains_key(&TypeId::of::<T>())
|
|
}
|
|
|
|
/// Gets a mutable borrow of a resource from the World.
|
|
pub fn get_resource_mut<T: ResourceObject>(&self) -> Option<ResMut<T>> {
|
|
self.get_tracked_resource_mut::<T>().map(|r| ResMut {
|
|
inner: r,
|
|
world_tick: self.current_tick(),
|
|
_marker: std::marker::PhantomData::<T>,
|
|
})
|
|
}
|
|
|
|
/// Get the corresponding [`ResourceData`].
|
|
pub fn get_resource_data<T: ResourceObject>(&self) -> Option<ResourceData> {
|
|
self.resources.get(&TypeId::of::<T>())
|
|
.map(|r| r.clone())
|
|
}
|
|
|
|
/// Increments the world current tick for tracking changes to components and resources.
|
|
///
|
|
/// # Note:
|
|
/// For change tracking to work correctly, this must be ran each loop before you run world
|
|
/// systems.
|
|
pub fn tick(&self) -> Tick {
|
|
self.tracker.tick()
|
|
}
|
|
|
|
/// Gets the current tick that the world is at.
|
|
///
|
|
/// See [`World::tick`].
|
|
pub fn current_tick(&self) -> Tick {
|
|
self.tracker.current()
|
|
}
|
|
|
|
pub fn tick_tracker(&self) -> &TickTracker {
|
|
&self.tracker
|
|
}
|
|
|
|
/// Attempts to find a resource in the world and returns a NonNull pointer to it
|
|
pub unsafe fn get_resource_ptr<T: ResourceObject>(&self) -> Option<NonNull<T>> {
|
|
self.resources.get(&TypeId::of::<T>())
|
|
.map(|d| unsafe {
|
|
let data = d.data.borrow();
|
|
let ptr = NonNull::from(&data.res);
|
|
NonNull::new_unchecked(ptr.as_ptr() as *mut T)
|
|
})
|
|
}
|
|
|
|
pub fn archetype_count(&self) -> usize {
|
|
self.archetypes.len()
|
|
}
|
|
}
|
|
|
|
// TODO: Ensure that all non-send resources are only accessible on the main thread.
|
|
unsafe impl Send for World {}
|
|
unsafe impl Sync for World {}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::{query::TickOf, tests::{Vec2, Vec3}, Entity};
|
|
|
|
use super::World;
|
|
|
|
struct SimpleCounter(i32);
|
|
|
|
#[test]
|
|
fn spawning_entity() {
|
|
let mut world = World::new();
|
|
let _e = world.spawn((Vec2 {
|
|
x: 10.0,
|
|
y: 15.0,
|
|
}, ));
|
|
}
|
|
|
|
#[test]
|
|
fn world_view_entities() {
|
|
let mut world = World::new();
|
|
world.spawn((Vec2 {
|
|
x: 10.0,
|
|
y: 15.0,
|
|
}, ));
|
|
world.spawn((Vec2 {
|
|
x: 152.0,
|
|
y: 3585.0,
|
|
}, ));
|
|
world.spawn((Vec2 {
|
|
x: 235.0,
|
|
y: 734.0,
|
|
}, ));
|
|
|
|
let mut count = 0;
|
|
for pos in world.view_iter::<&Vec2>() {
|
|
println!("Found entity at {:?}", pos);
|
|
count += 1;
|
|
}
|
|
assert!(count == 3);
|
|
}
|
|
|
|
#[test]
|
|
fn despawn_entity() {
|
|
let mut world = World::new();
|
|
world.spawn((Vec2::rand(),));
|
|
let middle_en = world.spawn((Vec2::rand(),));
|
|
let last_en = world.spawn((Vec2::rand(),));
|
|
|
|
world.despawn(middle_en);
|
|
|
|
let record = world.entities.entity_record(last_en).unwrap();
|
|
assert_eq!(record.index.0, 1);
|
|
}
|
|
|
|
#[test]
|
|
fn simple_resource() {
|
|
let mut world = World::new();
|
|
{
|
|
let counter = SimpleCounter(0);
|
|
world.add_resource(counter);
|
|
}
|
|
|
|
let counter = world.get_resource::<SimpleCounter>()
|
|
.expect("Counter resource is missing");
|
|
assert_eq!(counter.0, 0);
|
|
drop(counter);
|
|
|
|
let mut counter = world.get_resource_mut::<SimpleCounter>()
|
|
.expect("Counter resource is missing");
|
|
counter.0 += 4582;
|
|
drop(counter);
|
|
|
|
assert!(world.get_resource::<u32>().is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn resource_multi_borrow() {
|
|
let mut world = World::new();
|
|
let counter = SimpleCounter(4582);
|
|
world.add_resource(counter);
|
|
|
|
// test multiple borrows at the same time
|
|
let counter = world.get_resource::<SimpleCounter>()
|
|
.expect("Counter resource is missing");
|
|
assert_eq!(counter.0, 4582);
|
|
let counter2 = world.get_resource::<SimpleCounter>()
|
|
.expect("Counter resource is missing");
|
|
assert_eq!(counter.0, 4582);
|
|
assert_eq!(counter2.0, 4582);
|
|
}
|
|
|
|
#[test]
|
|
fn resource_one_mutable_borrow() {
|
|
let mut world = World::new();
|
|
{
|
|
let counter = SimpleCounter(4582);
|
|
world.add_resource(counter);
|
|
}
|
|
|
|
// test that its only possible to get a single mutable borrow
|
|
let counter = world.get_resource_mut::<SimpleCounter>()
|
|
.expect("Counter resource is missing");
|
|
assert_eq!(counter.0, 4582);
|
|
assert!(world.get_resource_mut::<SimpleCounter>().is_none());
|
|
assert_eq!(counter.0, 4582);
|
|
}
|
|
|
|
#[test]
|
|
fn insert_into_existing_archetype() {
|
|
let mut world = World::new();
|
|
let e = world.spawn((Vec2::rand(),));
|
|
world.spawn((Vec2::rand(),Vec3::rand()));
|
|
|
|
world.insert(e, (Vec3::rand(),));
|
|
|
|
assert!(world.view_one::<&Vec3>(e).get().is_some())
|
|
}
|
|
|
|
#[test]
|
|
fn insert_into_new_archetype() {
|
|
let mut world = World::new();
|
|
let e = world.spawn((Vec2::rand(),));
|
|
|
|
world.insert(e, (Vec3::rand(),));
|
|
|
|
assert!(world.view_one::<&Vec3>(e).get().is_some())
|
|
}
|
|
|
|
#[test]
|
|
fn insert_multiple_times() {
|
|
let v2s = &[Vec2::rand(), Vec2::rand(), Vec2::rand()];
|
|
let v3s = &[Vec3::rand(), Vec3::rand(), Vec3::rand()];
|
|
|
|
let mut world = World::new();
|
|
let e1 = world.spawn(v2s[0]);
|
|
let e2 = world.spawn(v2s[1]);
|
|
let e3 = world.spawn(v2s[2]);
|
|
println!("Spawned entities");
|
|
|
|
let ev2 = world.view_one::<&Vec2>(e2).get()
|
|
.expect("Failed to find Vec2 and Vec3 on inserted entity!");
|
|
assert_eq!(*ev2, v2s[1]);
|
|
drop(ev2);
|
|
|
|
let insert_and_assert = |world: &mut World, e: Entity, v2: Vec2, v3: Vec3| {
|
|
println!("inserting entity");
|
|
world.insert(e, (v3,));
|
|
println!("inserted entity");
|
|
|
|
let (ev2, ev3) = world.view_one::<(&Vec2, &Vec3)>(e).get()
|
|
.expect("Failed to find Vec2 and Vec3 on inserted entity!");
|
|
assert_eq!(*ev2, v2);
|
|
assert_eq!(*ev3, v3);
|
|
};
|
|
|
|
insert_and_assert(&mut world, e2, v2s[1], v3s[1]);
|
|
println!("Entity 2 is good");
|
|
insert_and_assert(&mut world, e3, v2s[2], v3s[2]);
|
|
println!("Entity 3 is good");
|
|
assert_eq!(world.archetypes.len(), 2);
|
|
println!("No extra archetypes were created");
|
|
|
|
insert_and_assert(&mut world, e1, v2s[0], v3s[0]);
|
|
println!("Entity 1 is good");
|
|
}
|
|
|
|
#[test]
|
|
fn view_one() {
|
|
let v = Vec2::rand();
|
|
|
|
let mut world = World::new();
|
|
let e = world.spawn((v,));
|
|
|
|
let view = world.view_one::<&Vec2>(e);
|
|
assert_eq!(*view.get().unwrap(), v);
|
|
}
|
|
|
|
#[test]
|
|
fn view_change_tracking() {
|
|
let mut world = World::new();
|
|
|
|
println!("spawning");
|
|
world.spawn((Vec2::new(10.0, 10.0),));
|
|
world.spawn((Vec2::new(5.0, 5.0),));
|
|
println!("spawned");
|
|
|
|
for mut v in world.view_iter::<&mut Vec2>() {
|
|
v.y += 50.0;
|
|
println!("Moved v to {:?}", v);
|
|
}
|
|
|
|
let world_tick = world.current_tick();
|
|
println!("The world tick is {}", *world_tick);
|
|
for (v, tick) in world.view_iter::<(&Vec2, TickOf<Vec2>)>() {
|
|
println!("Is at {:?}, it was changed at {}", v, *tick);
|
|
assert!(v.y > 50.0);
|
|
assert!(tick >= world_tick);
|
|
}
|
|
}
|
|
|
|
/// Tests replacing components using World::insert
|
|
#[test]
|
|
fn entity_insert_replace() {
|
|
let mut world = World::new();
|
|
let first = world.spawn((Vec2::new(10.0, 10.0),));
|
|
let second = world.spawn((Vec2::new(5.0, 5.0),));
|
|
|
|
world.insert(first, Vec2::new(50.0, 50.0));
|
|
|
|
let pos = world.view_one::<&mut Vec2>(first).get().unwrap();
|
|
assert_eq!(*pos, Vec2::new(50.0, 50.0));
|
|
drop(pos);
|
|
|
|
let pos = world.view_one::<&mut Vec2>(second).get().unwrap();
|
|
assert_eq!(*pos, Vec2::new(5.0, 5.0));
|
|
}
|
|
|
|
/// Tests resource change checks
|
|
#[test]
|
|
fn resource_changed() {
|
|
let mut world = World::new();
|
|
world.add_resource(SimpleCounter(50));
|
|
|
|
assert!(world.has_resource_changed::<SimpleCounter>());
|
|
|
|
world.spawn(Vec2::new(50.0, 50.0));
|
|
|
|
assert!(!world.has_resource_changed::<SimpleCounter>());
|
|
|
|
let mut counter = world.get_resource_mut::<SimpleCounter>()
|
|
.expect("Counter resource is missing");
|
|
counter.0 += 100;
|
|
|
|
assert!(world.has_resource_changed::<SimpleCounter>());
|
|
}
|
|
} |