Skip to content

Commit 51b2fbb

Browse files
authored
Allow despawning while running queries (#37)
* Manual rustfmt * Allow debug-querying list of components * Some simplification of the common entity removal code path * Allow despawning while running queries
1 parent 2a8f23b commit 51b2fbb

8 files changed

Lines changed: 113 additions & 37 deletions

File tree

src/query.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,18 @@ impl<C: 'static> SparseSetGetter for &mut C {
9595
macro_rules! impl_query {
9696
($($T:ident),*) => {
9797
impl<A: SparseSetGetter + Always, $($T: SparseSetGetter,)* Z> Query<(A, $($T,)*)> for Z
98-
where
99-
Z: FnMut(Entity, A::Short<'_>, $($T::Short<'_>,)*) + FnMut(Entity, A, $($T,)*),{
98+
where
99+
Z: FnMut(Entity, A::Short<'_>, $($T::Short<'_>,)*),
100+
Z: FnMut(Entity, A, $($T,)*),
101+
{
100102
#[track_caller]
101103
fn get_components(world: &World, mut f: Z) {
102104
#[allow(non_snake_case)]
103105
if let (Some(mut a), $(Some(mut $T),)*) = (A::get_set(world), $($T::get_set(world),)*) {
104106
for (entity, a) in A::iter(&mut a) {
107+
if world.is_despawning(entity) {
108+
continue;
109+
}
105110
$(let Some($T) = $T::get_entity(&mut $T, entity) else { continue };)*
106111
f(entity, a, $($T,)*);
107112

src/scheduler.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ impl<'a, RES> Scheduler<'a, RES> {
5555
let mut sys = sys.take().unwrap();
5656
drop(guard);
5757
sys(world, res);
58+
world.flush_despawned();
5859
let mut guard = self.systems.borrow_mut();
5960
let Some((_, entry)) = guard.get_mut(i) else {
6061
break;

src/sparse_set.rs

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,16 @@ impl<C> SparseSet<C> {
3535
let idx = self.sparse.remove(&entity)?;
3636
let last = self.dense.len() - 1;
3737

38-
if idx != last {
39-
self.dense.swap(idx, last);
40-
let entity = *self.ids.last().unwrap();
41-
self.ids.swap(idx, last);
38+
if idx == last {
39+
self.ids.pop();
40+
self.dense.pop()
41+
} else {
42+
self.ids.swap_remove(idx);
4243

43-
let _prev = self.sparse.insert(entity, idx);
44+
let _prev = self.sparse.insert(self.ids[idx], idx);
4445
debug_assert_eq!(_prev, Some(last));
46+
Some(self.dense.swap_remove(idx))
4547
}
46-
47-
self.ids.pop();
48-
self.dense.pop()
4948
}
5049

5150
pub fn get(&self, entity: Entity) -> Option<&C> {
@@ -74,20 +73,16 @@ impl<C> SparseSet<C> {
7473
}
7574

7675
trait Set: Any {
77-
#[cfg(any(debug_assertions, feature = "track_dead_entities"))]
78-
fn remove(&mut self, entity: Entity) -> Option<&'static str>;
76+
fn debug(&mut self, entity: Entity) -> Option<&'static str>;
7977

80-
#[cfg(not(any(debug_assertions, feature = "track_dead_entities")))]
8178
fn remove(&mut self, entity: Entity);
8279
}
8380

8481
impl<C: Any> Set for SparseSet<C> {
85-
#[cfg(any(debug_assertions, feature = "track_dead_entities"))]
86-
fn remove(&mut self, entity: Entity) -> Option<&'static str> {
87-
self.remove(entity).map(|_| type_name::<C>())
82+
fn debug(&mut self, entity: Entity) -> Option<&'static str> {
83+
self.get(entity).map(|_| type_name::<C>())
8884
}
8985

90-
#[cfg(not(any(debug_assertions, feature = "track_dead_entities")))]
9186
fn remove(&mut self, entity: Entity) {
9287
self.remove(entity);
9388
}
@@ -99,12 +94,6 @@ pub struct SparseSets {
9994
sets: FrozenVec<Box<RefCell<dyn Set>>>,
10095
}
10196

102-
#[cfg(any(debug_assertions, feature = "track_dead_entities"))]
103-
pub(crate) type RemoveType = String;
104-
105-
#[cfg(not(any(debug_assertions, feature = "track_dead_entities")))]
106-
pub(crate) type RemoveType = ();
107-
10897
impl SparseSets {
10998
pub fn insert<C: Any>(&self, entity: Entity, component: C) {
11099
let component = Box::new(RefCell::new(SparseSet::new(entity, component)));
@@ -117,7 +106,7 @@ impl SparseSets {
117106
}
118107

119108
#[track_caller]
120-
pub fn remove(&self, entity: Entity) -> RemoveType {
109+
pub fn debug(&self, entity: Entity) -> String {
121110
#[cfg(any(debug_assertions, feature = "track_dead_entities"))]
122111
let mut component = String::new();
123112

@@ -129,7 +118,7 @@ impl SparseSets {
129118
};
130119

131120
#[cfg(any(debug_assertions, feature = "track_dead_entities"))]
132-
if let Some(c) = guard.remove(entity) {
121+
if let Some(c) = guard.debug(entity) {
133122
component.push_str(c);
134123
component.push_str(", ");
135124
}
@@ -140,6 +129,19 @@ impl SparseSets {
140129
component
141130
}
142131

132+
#[track_caller]
133+
pub fn remove(&self, entity: Entity) {
134+
for set in self.sets.iter() {
135+
let Ok(mut guard) = set.try_borrow_mut() else {
136+
panic!(
137+
"Tried to access component mutably, but it is already being read or written to",
138+
)
139+
};
140+
141+
guard.remove(entity);
142+
}
143+
}
144+
143145
#[track_caller]
144146
pub fn get<C: 'static>(&self) -> Option<Ref<SparseSet<C>>> {
145147
let i = *self.set_access.borrow().get(&TypeId::of::<C>())?;

src/world.rs

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use std::{
1313
use crate::{
1414
query::Query,
1515
scheduler::{Scheduler, SysId},
16-
sparse_set::{RemoveType, SparseSets},
16+
sparse_set::SparseSets,
1717
};
1818

1919
/// An opaque id for an entity.
@@ -56,7 +56,13 @@ impl Default for EntityCounter {
5656
pub struct World {
5757
entities: EntityCounter,
5858
#[cfg(any(debug_assertions, feature = "track_dead_entities"))]
59-
dead_entities: RefCell<BTreeMap<Entity, (&'static Location<'static>, String)>>,
59+
dead_entity_locations: RefCell<BTreeMap<Entity, &'static Location<'static>>>,
60+
#[cfg(any(debug_assertions, feature = "track_dead_entities"))]
61+
dead_entity_components: RefCell<BTreeMap<Entity, String>>,
62+
/// Entities that are being despawned.
63+
/// As we cannot mutate the sparse sets to remove all components while queries are running, we instead do
64+
/// so whenever a system finishes
65+
despawning: RefCell<Vec<Entity>>,
6066
pub(crate) sparse_sets: SparseSets,
6167
scheduler: Scheduler<'static, ()>,
6268
}
@@ -65,7 +71,8 @@ impl World {
6571
#[track_caller]
6672
fn insert<C: Any>(&self, entity: Entity, component: C) {
6773
#[cfg(any(debug_assertions, feature = "track_dead_entities"))]
68-
if let Some((loc, components)) = self.dead_entities.borrow().get(&entity) {
74+
if let Some(components) = self.dead_entity_components.borrow().get(&entity) {
75+
let loc = self.dead_entity_locations.borrow()[&entity];
6976
panic!(
7077
"Attaching `{}` to despawned entity (despawned at {loc}).Components at despawn time: {components}",
7178
type_name::<C>(),
@@ -94,11 +101,29 @@ impl World {
94101
/// Destroy an entity and all its components. Future attempts to use this entity in any way will panic.
95102
#[track_caller]
96103
pub fn despawn(&self, entity: Entity) {
97-
let _detach_info = self.detach_all(entity);
104+
self.despawning.borrow_mut().push(entity);
98105
#[cfg(any(debug_assertions, feature = "track_dead_entities"))]
99-
self.dead_entities
106+
self.dead_entity_locations
100107
.borrow_mut()
101-
.insert(entity, (Location::caller(), _detach_info));
108+
.insert(entity, Location::caller());
109+
}
110+
111+
/// Ensure all despawned entities actually got removed
112+
#[track_caller]
113+
pub fn flush_despawned(&self) {
114+
for entity in self.despawning.borrow_mut().drain(..) {
115+
#[cfg(any(debug_assertions, feature = "track_dead_entities"))]
116+
let components = self.debug_components(entity);
117+
self.detach_all(entity);
118+
#[cfg(any(debug_assertions, feature = "track_dead_entities"))]
119+
self.dead_entity_components
120+
.borrow_mut()
121+
.insert(entity, components);
122+
}
123+
}
124+
125+
pub(crate) fn is_despawning(&self, entity: Entity) -> bool {
126+
self.despawning.borrow().contains(&entity)
102127
}
103128

104129
/// Attach multiple components to an entity at once.
@@ -111,7 +136,8 @@ impl World {
111136
#[track_caller]
112137
pub fn detach<C: 'static>(&self, entity: Entity) -> Option<C> {
113138
#[cfg(any(debug_assertions, feature = "track_dead_entities"))]
114-
if let Some((loc, components)) = self.dead_entities.borrow().get(&entity) {
139+
if let Some(components) = self.dead_entity_components.borrow().get(&entity) {
140+
let loc = self.dead_entity_locations.borrow()[&entity];
115141
panic!(
116142
"Detaching `{}` from despawned entity (despawned at {loc})\nComponents at despawn time: {components}",
117143
type_name::<C>(),
@@ -124,16 +150,24 @@ impl World {
124150
/// Detach all components from an entity and drop them.
125151
/// If you want to extract specific components, call [Self::detach] first.
126152
#[track_caller]
127-
pub fn detach_all(&self, entity: Entity) -> RemoveType {
153+
pub fn detach_all(&self, entity: Entity) {
128154
#[cfg(any(debug_assertions, feature = "track_dead_entities"))]
129-
if let Some((loc, components)) = self.dead_entities.borrow().get(&entity) {
155+
if let Some(components) = self.dead_entity_components.borrow().get(&entity) {
156+
let loc = self.dead_entity_locations.borrow()[&entity];
130157
panic!(
131158
"Detaching all components from despawned entity (despawned at {loc})\nComponents at despawn time: {components}"
132159
);
133160
}
134161
self.sparse_sets.remove(entity)
135162
}
136163

164+
/// Detach all components from an entity and drop them.
165+
/// If you want to extract specific components, call [Self::detach] first.
166+
#[track_caller]
167+
pub fn debug_components(&self, entity: Entity) -> String {
168+
self.sparse_sets.debug(entity)
169+
}
170+
137171
/// Detach all components of a specific type from all entities and drop them.
138172
///
139173
/// This method removes all components of type `C` from every entity in the world.
@@ -169,7 +203,8 @@ impl World {
169203
#[track_caller]
170204
pub fn get<C: 'static>(&self, entity: Entity) -> Option<Ref<C>> {
171205
#[cfg(any(debug_assertions, feature = "track_dead_entities"))]
172-
if let Some((loc, components)) = self.dead_entities.borrow().get(&entity) {
206+
if let Some(components) = self.dead_entity_components.borrow().get(&entity) {
207+
let loc = self.dead_entity_locations.borrow()[&entity];
173208
panic!(
174209
"Getting `{}` from despawned entity (despawned at {loc})\nComponents at despawn time: {components}",
175210
type_name::<C>(),
@@ -187,7 +222,8 @@ impl World {
187222
#[track_caller]
188223
pub fn get_mut<C: 'static>(&self, entity: Entity) -> Option<RefMut<C>> {
189224
#[cfg(any(debug_assertions, feature = "track_dead_entities"))]
190-
if let Some((loc, components)) = self.dead_entities.borrow().get(&entity) {
225+
if let Some(components) = self.dead_entity_components.borrow().get(&entity) {
226+
let loc = self.dead_entity_locations.borrow()[&entity];
191227
panic!(
192228
"Getting `{}` from despawned entity (despawned at {loc})\nComponents at despawn time: {components}",
193229
type_name::<C>(),

tests/entity.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,16 @@ fn spawn_related_in_query() {
5757
fn attach_to_despawned() {
5858
let world = World::default();
5959

60+
let entity = world.spawn((1_u32,));
61+
world.despawn(entity);
62+
world.flush_despawned();
63+
world.attach(entity, (String::new(),));
64+
}
65+
66+
#[test]
67+
fn attach_to_despawned_no_flush() {
68+
let world = World::default();
69+
6070
let entity = world.spawn((1_u32,));
6171
world.despawn(entity);
6272
world.attach(entity, (String::new(),));

tests/system.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,27 @@ fn despawn() {
4343
assert_eq!(&results[..], &[(10, Some("foo"))]);
4444
}
4545

46+
#[test]
47+
fn despawn_in_query() {
48+
let world = World::default();
49+
50+
let id = world.spawn((1_u32,));
51+
world.spawn((10_u32, "foo"));
52+
world.despawn(id);
53+
54+
let mut results = vec![];
55+
world.query(|entity, i: &u32, s: Option<&&'static str>| {
56+
results.push((*i, s.map(|s| *s)));
57+
world.despawn(entity);
58+
});
59+
assert_eq!(&results[..], &[(10, Some("foo"))]);
60+
let mut results = vec![];
61+
world.query(|_, i: &u32, s: Option<&&'static str>| {
62+
results.push((*i, s.map(|s| *s)));
63+
});
64+
assert_eq!(&results[..], &[]);
65+
}
66+
4667
#[test]
4768
fn get() {
4869
let world = World::default();

tests/ui/panic_location.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ fn main() {
88
let id = world.spawn((1_u32,));
99
world.spawn((10_u32, "foo"));
1010
world.despawn(id);
11+
world.flush_despawned();
1112

1213
world.get::<u32>(id);
1314
}

tests/ui/panic_location.run.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

2-
thread 'main' panicked at tests/ui/panic_location.rs:12:11:
2+
thread 'main' panicked at tests/ui/panic_location.rs:13:11:
33
Getting `u32` from despawned entity (despawned at tests/ui/panic_location.rs:10:11)
44
Components at despawn time: u32,
55
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

0 commit comments

Comments
 (0)