Skip to content

Commit cc65b92

Browse files
committed
Create a basic system for reference-counting entities.
1 parent 304265b commit cc65b92

File tree

2 files changed

+202
-0
lines changed

2 files changed

+202
-0
lines changed

crates/bevy_ecs/src/entity_rc.rs

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
//! This module holds utilities for reference-counting of entities, similar to [`Arc`]. This enables
2+
//! automatic cleanup of entities that can be referenced in multiple places.
3+
4+
use core::{
5+
fmt::{Debug, Formatter},
6+
ops::Deref,
7+
};
8+
9+
use bevy_platform::sync::{Arc, Weak};
10+
use concurrent_queue::ConcurrentQueue;
11+
12+
use crate::{entity::Entity, system::Commands};
13+
14+
/// A reference count for an entity.
15+
///
16+
/// This "handle" also stores some optional data, allowing users to customize any shared data
17+
/// between all references to the entity.
18+
///
19+
/// Once all [`EntityRc`] instances have been dropped, the entity will be queued for destruction.
20+
/// This means it is possible for the entity to still exist, while its [`EntityRc`] has been
21+
/// dropped.
22+
///
23+
/// The reverse is also true: a held [`EntityRc`] does not guarantee that the entity still exists.
24+
/// It can still be explicitly despawned, so users should try to be resilient to this.
25+
///
26+
/// This type has similar semantics to [`Arc`].
27+
#[derive(Debug)]
28+
pub struct EntityRc<T: Send + Sync + 'static = ()>(Arc<EntityRcInner<T>>);
29+
30+
impl<T: Send + Sync + 'static> Clone for EntityRc<T> {
31+
fn clone(&self) -> Self {
32+
Self(self.0.clone())
33+
}
34+
}
35+
36+
impl<T: Send + Sync + 'static> EntityRc<T> {
37+
/// Creates a new [`EntityWeak`] referring to the same entity (and reference count).
38+
pub fn downgrade(this: &Self) -> EntityWeak<T> {
39+
EntityWeak {
40+
entity: this.0.entity,
41+
weak: Arc::downgrade(&this.0),
42+
}
43+
}
44+
45+
/// Returns the entity this reference count refers to.
46+
pub fn entity(&self) -> Entity {
47+
self.0.entity
48+
}
49+
}
50+
51+
impl<T: Send + Sync + 'static> Deref for EntityRc<T> {
52+
type Target = T;
53+
54+
fn deref(&self) -> &Self::Target {
55+
&self.0.payload
56+
}
57+
}
58+
59+
/// A "non-owning" reference to a reference-counted entity.
60+
///
61+
/// Holding this handle does not guarantee that the entity will not be cleaned up. This handle
62+
/// allows "upgrading" to an [`EntityRc`], if the reference count is still positive, which **will**
63+
/// avoid clean ups.
64+
///
65+
/// This type has similar semantics to [`Weak`].
66+
#[derive(Debug)]
67+
pub struct EntityWeak<T: Send + Sync + 'static = ()> {
68+
/// The entity being referenced.
69+
///
70+
/// This allows the entity to be referenced even if the reference count has expired. This is
71+
/// generally useful for cleanup operations.
72+
entity: Entity,
73+
/// The underlying weak reference.
74+
weak: Weak<EntityRcInner<T>>,
75+
}
76+
77+
impl<T: Send + Sync + 'static> Clone for EntityWeak<T> {
78+
fn clone(&self) -> Self {
79+
Self {
80+
entity: self.entity,
81+
weak: self.weak.clone(),
82+
}
83+
}
84+
}
85+
86+
impl<T: Send + Sync + 'static> EntityWeak<T> {
87+
/// Attempts to upgrade the weak reference into an [`EntityRc`], which can keep the entity alive
88+
/// if successful.
89+
///
90+
/// Returns [`None`] if all [`EntityRc`]s were previously dropped. This does not necessarily
91+
/// mean that the entity has been despawned yet.
92+
pub fn upgrade(&self) -> Option<EntityRc<T>> {
93+
self.weak.upgrade().map(EntityRc)
94+
}
95+
96+
/// Returns the entity this weak reference count refers to.
97+
///
98+
/// The entity may or may not have been despawned (since the [`EntityRc`]s may have all been
99+
/// dropped). In order to guarantee the entity remains alive, use [`Self::upgrade`] first. This
100+
/// accessor exists to support cleanup operations.
101+
pub fn entity(&self) -> Entity {
102+
self.entity
103+
}
104+
}
105+
106+
/// Data stored inside the shared data for [`EntityRc`].
107+
struct EntityRcInner<T: Send + Sync + 'static> {
108+
/// The concurrent queue to notify when dropping this type.
109+
drop_notifier: Arc<ConcurrentQueue<Entity>>,
110+
/// The entity this reference count refers to.
111+
entity: Entity,
112+
/// The data that is shared with all reference counts for easy access.
113+
payload: T,
114+
}
115+
116+
// Manual impl of Debug to avoid debugging the drop_notifier.
117+
impl<T: Send + Sync + 'static + Debug> Debug for EntityRcInner<T> {
118+
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
119+
f.debug_struct("EntityRcInner")
120+
.field("entity", &self.entity)
121+
.field("payload", &self.payload)
122+
.finish()
123+
}
124+
}
125+
126+
impl<T: Send + Sync + 'static> Drop for EntityRcInner<T> {
127+
fn drop(&mut self) {
128+
// Try to push the entity. If the notifier is closed for some reason, that's ok.
129+
let _ = self.drop_notifier.push(self.entity);
130+
}
131+
}
132+
133+
/// Allows creating [`EntityRc`] and handles syncing them with the world.
134+
///
135+
/// Note: this can produce [`EntityRc`] containing any "payload", since the payload is not
136+
/// accessible during despawn time. This is because it's possible for the entity to be despawned
137+
/// explicitly even though an [`EntityRc`] is still held - callers should be resilient to this.
138+
pub struct EntityRcSource {
139+
/// The concurrent queue used for communicating drop events of [`EntityRcInner`]s.
140+
// Note: this could be a channel, but `bevy_ecs` already depends on `concurrent_queue`, so use
141+
// it as a simple channel.
142+
drop_notifier: Arc<ConcurrentQueue<Entity>>,
143+
}
144+
145+
impl Default for EntityRcSource {
146+
fn default() -> Self {
147+
Self::new()
148+
}
149+
}
150+
151+
impl EntityRcSource {
152+
/// Creates a new source of [`EntityRc`]s.
153+
///
154+
/// Generally, only one [`EntityRcSource`] is needed, but having separate ones allows clean up
155+
/// operations to occur at different times or different rates.
156+
pub fn new() -> Self {
157+
Self {
158+
drop_notifier: Arc::new(ConcurrentQueue::unbounded()),
159+
}
160+
}
161+
162+
/// Creates a new [`EntityRc`] for `entity`, storing the given `payload` in that [`EntityRc`].
163+
///
164+
/// It is up to the caller to ensure that the provided `entity` does not already have an
165+
/// [`EntityRc`] associated with it. Providing an `entity` which already has an [`EntityRc`]
166+
/// will result in two reference counts tracking the same entity and both attempting to despawn
167+
/// the entity (and more importantly, for a held [`EntityRc`] to have its entity despawned
168+
/// anyway).
169+
///
170+
/// Providing an `entity` allows this method to be compatible with regular entity allocation
171+
/// ([`EntityAllocator`](crate::entity::EntityAllocator)), remote entity allocation
172+
/// ([`RemoteAllocator`](crate::entity::RemoteAllocator)), or even taking an existing entity and
173+
/// making it reference counted.
174+
pub fn create_rc<T: Send + Sync + 'static>(&self, entity: Entity, payload: T) -> EntityRc<T> {
175+
EntityRc(Arc::new(EntityRcInner {
176+
drop_notifier: self.drop_notifier.clone(),
177+
entity,
178+
payload,
179+
}))
180+
}
181+
182+
/// Handles any dropped [`EntityRc`]s and despawns the corresponding entities.
183+
///
184+
/// This must be called regularly in order for reference-counted entities to actually be cleaned
185+
/// up.
186+
///
187+
/// Note: if you have exclusive world access (`&mut World`), you can use
188+
/// [`World::commands`](crate::world::World::commands) to get an instance of [`Commands`].
189+
pub fn handle_dropped_rcs(&self, commands: &mut Commands) {
190+
for entity in self.drop_notifier.try_iter() {
191+
let Ok(mut entity) = commands.get_entity(entity) else {
192+
// We intended to despawn the entity - and the entity is despawned. Someone did our
193+
// work for us!
194+
continue;
195+
};
196+
// Also only try to despawn here - if the entity is despawned when this is run, it's not
197+
// a problem.
198+
entity.try_despawn();
199+
}
200+
}
201+
}

crates/bevy_ecs/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ pub mod change_detection;
3232
pub mod component;
3333
pub mod entity;
3434
pub mod entity_disabling;
35+
pub mod entity_rc;
3536
pub mod error;
3637
pub mod event;
3738
pub mod hierarchy;

0 commit comments

Comments
 (0)