Skip to content

Commit

Permalink
ecs: initial component change tracking
Browse files Browse the repository at this point in the history
(changing entity archetypes currently breaks tracking)
  • Loading branch information
cart committed Jul 18, 2020
1 parent 81df34a commit 31d00ad
Show file tree
Hide file tree
Showing 8 changed files with 267 additions and 31 deletions.
84 changes: 61 additions & 23 deletions crates/bevy_ecs/hecs/src/archetype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,7 @@ pub struct Archetype {
impl Archetype {
#[allow(missing_docs)]
pub fn new(types: Vec<TypeInfo>) -> Self {
debug_assert!(
types.windows(2).all(|x| x[0] < x[1]),
"type info unsorted or contains duplicates"
);
Self {
types,
state: HashMap::default(),
entities: Box::new([]),
len: 0,
data: UnsafeCell::new(NonNull::dangling()),
data_size: 0,
grow_size: 64,
}
Self::with_grow(types, 64)
}

#[allow(missing_docs)]
Expand All @@ -71,9 +59,13 @@ impl Archetype {
types.windows(2).all(|x| x[0] < x[1]),
"type info unsorted or contains duplicates"
);
let mut state = HashMap::with_capacity(types.len());
for ty in &types {
state.insert(ty.id, TypeState::new());
}
Self {
state,
types,
state: HashMap::default(),
entities: Box::new([]),
len: 0,
data: UnsafeCell::new(NonNull::dangling()),
Expand All @@ -97,14 +89,16 @@ impl Archetype {
self.len = 0;
}

pub(crate) fn has<T: Component>(&self) -> bool {
#[allow(missing_docs)]
pub fn has<T: Component>(&self) -> bool {
self.has_dynamic(TypeId::of::<T>())
}

pub(crate) fn has_dynamic(&self, id: TypeId) -> bool {
self.state.contains_key(&id)
}

// TODO: this should be unsafe i think
#[allow(missing_docs)]
#[inline]
pub fn get<T: Component>(&self) -> Option<NonNull<T>> {
Expand All @@ -116,6 +110,29 @@ impl Archetype {
})
}

// TODO: this should be unsafe i think
#[allow(missing_docs)]
#[inline]
pub fn get_with_modified<T: Component>(&self) -> Option<(NonNull<T>, NonNull<bool>)> {
let state = self.state.get(&TypeId::of::<T>())?;
Some(unsafe {
(
NonNull::new_unchecked(
(*self.data.get()).as_ptr().add(state.offset).cast::<T>() as *mut T
),
NonNull::new_unchecked(state.modified_entities.as_ptr() as *mut bool),
)
})
}

// TODO: this should be unsafe i think
#[allow(missing_docs)]
#[inline]
pub fn get_modified<T: Component>(&self) -> Option<NonNull<bool>> {
let state = self.state.get(&TypeId::of::<T>())?;
Some(unsafe { NonNull::new_unchecked(state.modified_entities.as_ptr() as *mut bool) })
}

#[allow(missing_docs)]
pub fn borrow<T: Component>(&self) {
if self
Expand Down Expand Up @@ -212,6 +229,12 @@ impl Archetype {
self.entities.len() as u32
}

pub fn clear_trackers(&mut self) {
for type_state in self.state.values_mut() {
type_state.clear_trackers();
}
}

fn grow(&mut self, increment: u32) {
unsafe {
let old_count = self.len as usize;
Expand All @@ -220,11 +243,17 @@ impl Archetype {
new_entities[0..old_count].copy_from_slice(&self.entities[0..old_count]);
self.entities = new_entities;

for type_state in self.state.values_mut() {
type_state.modified_entities.resize_with(count, || false);
}

let old_data_size = mem::replace(&mut self.data_size, 0);
let mut state = HashMap::with_capacity(self.types.len());
let mut old_offsets = Vec::with_capacity(self.types.len());
for ty in &self.types {
self.data_size = align(self.data_size, ty.layout.align());
state.insert(ty.id, TypeState::new(self.data_size));
let ty_state = self.state.get_mut(&ty.id).unwrap();
old_offsets.push(ty_state.offset);
ty_state.offset = self.data_size;
self.data_size += ty.layout.size() * count;
}
let new_data = if self.data_size == 0 {
Expand All @@ -240,9 +269,9 @@ impl Archetype {
.unwrap()
};
if old_data_size != 0 {
for ty in &self.types {
let old_off = self.state.get(&ty.id).unwrap().offset;
let new_off = state.get(&ty.id).unwrap().offset;
for (i, ty) in self.types.iter().enumerate() {
let old_off = old_offsets[i];
let new_off = self.state.get(&ty.id).unwrap().offset;
ptr::copy_nonoverlapping(
(*self.data.get()).as_ptr().add(old_off),
new_data.as_ptr().add(new_off),
Expand All @@ -252,7 +281,6 @@ impl Archetype {
}

self.data = UnsafeCell::new(new_data);
self.state = state;
}
}

Expand All @@ -266,6 +294,7 @@ impl Archetype {
.as_ptr();
(ty.drop)(removed);
if index != last {
// TODO: copy component tracker state here
ptr::copy_nonoverlapping(
self.get_dynamic(ty.id, ty.layout.size(), last)
.unwrap()
Expand Down Expand Up @@ -297,6 +326,7 @@ impl Archetype {
.unwrap()
.as_ptr();
f(moved, ty.id(), ty.layout().size());
// TODO: copy component tracker state here
if index != last {
ptr::copy_nonoverlapping(
self.get_dynamic(ty.id, ty.layout.size(), last)
Expand Down Expand Up @@ -352,13 +382,21 @@ impl Drop for Archetype {
struct TypeState {
offset: usize,
borrow: AtomicBorrow,
modified_entities: Vec<bool>,
}

impl TypeState {
fn new(offset: usize) -> Self {
fn new() -> Self {
Self {
offset,
offset: 0,
borrow: AtomicBorrow::new(),
modified_entities: Vec::new(),
}
}

fn clear_trackers(&mut self) {
for modified in self.modified_entities.iter_mut() {
*modified = false;
}
}
}
Expand Down
22 changes: 18 additions & 4 deletions crates/bevy_ecs/hecs/src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ pub trait Fetch<'a>: Sized {
/// Release dynamic borrows acquired by `borrow`
fn release(archetype: &Archetype);

/// if this returns true, the current item will be skipped during iteration
unsafe fn should_skip(&self) -> bool {
false
}

/// Access the next item in this archetype without bounds checking
///
/// # Safety
Expand Down Expand Up @@ -519,11 +524,20 @@ struct ChunkIter<Q: Query> {

impl<Q: Query> ChunkIter<Q> {
unsafe fn next<'a, 'w>(&mut self) -> Option<<Q::Fetch as Fetch<'a>>::Item> {
if self.len == 0 {
return None;
loop {
if self.len == 0 {
return None;
}

self.len -= 1;
if self.fetch.should_skip() {
// we still need to progress the iterator
let _ = self.fetch.next();
continue;
}

break Some(self.fetch.next())
}
self.len -= 1;
Some(self.fetch.next())
}
}

Expand Down
7 changes: 7 additions & 0 deletions crates/bevy_ecs/hecs/src/world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,13 @@ impl World {
pub fn get_entity_location(&self, entity: Entity) -> Option<Location> {
self.entities.get(entity).ok()
}

/// Clears each entity's tracker state. For example, each entity's component "modified" state will be reset to `false`.
pub fn clear_trackers(&mut self) {
for archetype in self.archetypes.iter_mut() {
archetype.clear_trackers();
}
}
}

unsafe impl Send for World {}
Expand Down
6 changes: 3 additions & 3 deletions crates/bevy_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@ pub use hecs::{Query as HecsQuery, *};
mod resource;
mod schedule;
mod system;
mod world_builder;
mod world;

pub use resource::*;
pub use schedule::*;
pub use system::{*, Query};
pub use world_builder::*;
pub use world::*;

pub mod prelude {
pub use crate::{
resource::{FromResources, Local, Res, ResMut, Resource, Resources},
system::{
Commands, IntoForEachSystem, IntoQuerySystem, IntoThreadLocalSystem, Query, System,
},
world_builder::WorldBuilderSource,
world::{WorldBuilderSource, ComMut},
Bundle, Component, Entity, Ref, RefMut, With, Without, World,
};
}
2 changes: 1 addition & 1 deletion crates/bevy_ecs/src/resource/resource_query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ impl<'a, T: Component> Deref for Res<'a, T> {
}
}

/// Unique borrow of an entity's component
/// Unique borrow of a resource
pub struct ResMut<'a, T: Component> {
archetype: &'a Archetype,
target: NonNull<T>,
Expand Down
Loading

0 comments on commit 31d00ad

Please sign in to comment.