1use crate::{App, AppContext, GpuiBorrow, VisualContext, Window, seal::Sealed};
2use anyhow::{Context as _, Result};
3use collections::FxHashSet;
4use derive_more::{Deref, DerefMut};
5use parking_lot::{RwLock, RwLockUpgradableReadGuard};
6use slotmap::{KeyData, SecondaryMap, SlotMap};
7use std::{
8 any::{Any, TypeId, type_name},
9 cell::RefCell,
10 cmp::Ordering,
11 fmt::{self, Display},
12 hash::{Hash, Hasher},
13 marker::PhantomData,
14 num::NonZeroU64,
15 sync::{
16 Arc, Weak,
17 atomic::{AtomicU64, AtomicUsize, Ordering::SeqCst},
18 },
19 thread::panicking,
20};
21
22use super::Context;
23use crate::util::atomic_incr_if_not_zero;
24#[cfg(any(test, feature = "leak-detection"))]
25use collections::HashMap;
26
27slotmap::new_key_type! {
28 pub struct EntityId;
30}
31
32impl From<u64> for EntityId {
33 fn from(value: u64) -> Self {
34 Self(KeyData::from_ffi(value))
35 }
36}
37
38impl EntityId {
39 pub fn as_non_zero_u64(self) -> NonZeroU64 {
41 NonZeroU64::new(self.0.as_ffi()).unwrap()
42 }
43
44 pub fn as_u64(self) -> u64 {
46 self.0.as_ffi()
47 }
48}
49
50impl Display for EntityId {
51 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52 write!(f, "{}", self.as_u64())
53 }
54}
55
56pub(crate) struct EntityMap {
57 entities: SecondaryMap<EntityId, Box<dyn Any>>,
58 pub accessed_entities: RefCell<FxHashSet<EntityId>>,
59 ref_counts: Arc<RwLock<EntityRefCounts>>,
60}
61
62#[doc(hidden)]
63pub(crate) struct EntityRefCounts {
64 counts: SlotMap<EntityId, AtomicUsize>,
65 dropped_entity_ids: Vec<EntityId>,
66 #[cfg(any(test, feature = "leak-detection"))]
67 leak_detector: LeakDetector,
68}
69
70pub(super) struct LeaseInner {
71 pub(super) entity: Option<Box<dyn Any>>,
72}
73
74impl EntityMap {
75 pub fn new() -> Self {
76 Self {
77 entities: SecondaryMap::new(),
78 accessed_entities: RefCell::new(FxHashSet::default()),
79 ref_counts: Arc::new(RwLock::new(EntityRefCounts {
80 counts: SlotMap::with_key(),
81 dropped_entity_ids: Vec::new(),
82 #[cfg(any(test, feature = "leak-detection"))]
83 leak_detector: LeakDetector {
84 next_handle_id: 0,
85 entity_handles: HashMap::default(),
86 },
87 })),
88 }
89 }
90
91 #[doc(hidden)]
92 pub fn ref_counts_drop_handle(&self) -> Arc<RwLock<EntityRefCounts>> {
93 self.ref_counts.clone()
94 }
95
96 #[cfg(any(test, feature = "leak-detection"))]
102 pub fn leak_detector_snapshot(&self) -> LeakDetectorSnapshot {
103 self.ref_counts.read().leak_detector.snapshot()
104 }
105
106 #[cfg(any(test, feature = "leak-detection"))]
110 pub fn assert_no_new_leaks(&self, snapshot: &LeakDetectorSnapshot) {
111 self.ref_counts
112 .read()
113 .leak_detector
114 .assert_no_new_leaks(snapshot)
115 }
116
117 pub fn reserve<T: 'static>(&self) -> Slot<T> {
119 let id = self.ref_counts.write().counts.insert(1.into());
120 Slot(Entity::new(id, Arc::downgrade(&self.ref_counts)))
121 }
122
123 pub fn insert<T>(&mut self, slot: Slot<T>, entity: T) -> Entity<T>
125 where
126 T: 'static,
127 {
128 let mut accessed_entities = self.accessed_entities.get_mut();
129 accessed_entities.insert(slot.entity_id);
130
131 let handle = slot.0;
132 self.entities.insert(handle.entity_id, Box::new(entity));
133 handle
134 }
135
136 #[track_caller]
138 pub fn lease<T>(&mut self, pointer: &Entity<T>) -> Lease<T> {
139 Lease {
140 inner: self.lease_erased(pointer, type_name::<T>()),
141 id: pointer.entity_id,
142 entity_type: PhantomData,
143 }
144 }
145
146 pub fn end_lease<T>(&mut self, lease: Lease<T>) {
148 self.end_lease_erased(lease.id, lease.inner);
149 }
150
151 #[inline(always)]
152 pub fn read<T: 'static>(&self, entity: &Entity<T>) -> &T {
153 self.assert_valid_context(entity);
154 self.read_inner(entity.entity_id)
155 .and_then(|entity| entity.downcast_ref())
156 .unwrap_or_else(|| double_lease_panic("read", type_name::<T>()))
157 }
158
159 #[track_caller]
160 pub(super) fn lease_erased(&mut self, pointer: &AnyEntity, entity_type: &str) -> LeaseInner {
161 self.assert_valid_context(pointer);
162 let entity = Some(
163 self.lease_inner(pointer.entity_id)
164 .unwrap_or_else(|| double_lease_panic("update", entity_type)),
165 );
166 LeaseInner { entity }
167 }
168
169 pub(super) fn end_lease_erased(&mut self, entity_id: EntityId, mut lease: LeaseInner) {
170 self.end_lease_inner(entity_id, lease.entity.take().unwrap());
171 }
172
173 fn assert_valid_context(&self, entity: &AnyEntity) {
174 debug_assert!(
175 Weak::ptr_eq(&entity.entity_map, &Arc::downgrade(&self.ref_counts)),
176 "used a entity with the wrong context"
177 );
178 }
179
180 pub fn extend_accessed(&mut self, entities: &FxHashSet<EntityId>) {
181 self.accessed_entities
182 .get_mut()
183 .extend(entities.iter().copied());
184 }
185
186 pub fn clear_accessed(&mut self) {
187 self.accessed_entities.get_mut().clear();
188 }
189
190 pub fn take_dropped(&mut self) -> Vec<(EntityId, Box<dyn Any>)> {
191 let mut ref_counts = &mut *self.ref_counts.write();
192 let dropped_entity_ids = ref_counts.dropped_entity_ids.drain(..);
193 let mut accessed_entities = self.accessed_entities.get_mut();
194
195 dropped_entity_ids
196 .filter_map(|entity_id| {
197 let count = ref_counts.counts.remove(entity_id).unwrap();
198 debug_assert_eq!(
199 count.load(SeqCst),
200 0,
201 "dropped an entity that was referenced"
202 );
203 accessed_entities.remove(&entity_id);
204 Some((entity_id, self.entities.remove(entity_id)?))
207 })
208 .collect()
209 }
210
211 #[inline(never)]
212 fn read_inner(&self, entity_id: EntityId) -> Option<&dyn Any> {
213 let mut accessed_entities = self.accessed_entities.borrow_mut();
214 accessed_entities.insert(entity_id);
215 self.entities.get(entity_id).map(Box::as_ref)
216 }
217
218 #[inline(never)]
219 fn lease_inner(&mut self, entity_id: EntityId) -> Option<Box<dyn Any>> {
220 self.accessed_entities.get_mut().insert(entity_id);
221 self.entities.remove(entity_id)
222 }
223
224 #[inline(never)]
225 fn end_lease_inner(&mut self, entity_id: EntityId, entity: Box<dyn Any>) {
226 self.entities.insert(entity_id, entity);
227 }
228}
229
230#[track_caller]
231fn double_lease_panic(operation: &str, entity_type: &str) -> ! {
232 panic!("cannot {operation} {entity_type} while it is already being updated")
233}
234
235pub(crate) struct Lease<T> {
236 pub id: EntityId,
237 inner: LeaseInner,
238 entity_type: PhantomData<T>,
239}
240
241impl<T: 'static> core::ops::Deref for Lease<T> {
242 type Target = T;
243
244 fn deref(&self) -> &Self::Target {
245 self.inner.entity.as_ref().unwrap().downcast_ref().unwrap()
246 }
247}
248
249impl<T: 'static> core::ops::DerefMut for Lease<T> {
250 fn deref_mut(&mut self) -> &mut Self::Target {
251 self.inner.entity.as_mut().unwrap().downcast_mut().unwrap()
252 }
253}
254
255impl Drop for LeaseInner {
256 fn drop(&mut self) {
257 if self.entity.is_some() && !panicking() {
258 panic!("Leases must be ended with EntityMap::end_lease")
259 }
260 }
261}
262
263#[derive(Deref, DerefMut)]
264pub(crate) struct Slot<T>(Entity<T>);
265
266pub struct AnyEntity {
268 pub(crate) entity_id: EntityId,
269 pub(crate) entity_type: TypeId,
270 entity_map: Weak<RwLock<EntityRefCounts>>,
271 #[cfg(any(test, feature = "leak-detection"))]
272 handle_id: HandleId,
273}
274
275impl AnyEntity {
276 fn new(
277 id: EntityId,
278 entity_type: TypeId,
279 entity_map: Weak<RwLock<EntityRefCounts>>,
280 #[cfg(any(test, feature = "leak-detection"))] type_name: &'static str,
281 ) -> Self {
282 Self {
283 entity_id: id,
284 entity_type,
285 #[cfg(any(test, feature = "leak-detection"))]
286 handle_id: entity_map
287 .clone()
288 .upgrade()
289 .unwrap()
290 .write()
291 .leak_detector
292 .handle_created(id, Some(type_name)),
293 entity_map,
294 }
295 }
296
297 #[inline]
299 pub fn entity_id(&self) -> EntityId {
300 self.entity_id
301 }
302
303 #[inline]
305 pub fn entity_type(&self) -> TypeId {
306 self.entity_type
307 }
308
309 pub fn downgrade(&self) -> AnyWeakEntity {
311 AnyWeakEntity {
312 entity_id: self.entity_id,
313 entity_type: self.entity_type,
314 entity_ref_counts: self.entity_map.clone(),
315 }
316 }
317
318 pub fn downcast<T: 'static>(self) -> Result<Entity<T>, AnyEntity> {
321 if TypeId::of::<T>() == self.entity_type {
322 Ok(Entity {
323 any_entity: self,
324 entity_type: PhantomData,
325 })
326 } else {
327 Err(self)
328 }
329 }
330}
331
332impl Clone for AnyEntity {
333 fn clone(&self) -> Self {
334 if let Some(entity_map) = self.entity_map.upgrade() {
335 let entity_map = entity_map.read();
336 let count = entity_map
337 .counts
338 .get(self.entity_id)
339 .expect("detected over-release of a entity");
340 let prev_count = count.fetch_add(1, SeqCst);
341 assert_ne!(prev_count, 0, "Detected over-release of a entity.");
342 }
343
344 Self {
345 entity_id: self.entity_id,
346 entity_type: self.entity_type,
347 entity_map: self.entity_map.clone(),
348 #[cfg(any(test, feature = "leak-detection"))]
349 handle_id: self
350 .entity_map
351 .upgrade()
352 .unwrap()
353 .write()
354 .leak_detector
355 .handle_created(self.entity_id, None),
356 }
357 }
358}
359
360impl Drop for AnyEntity {
361 fn drop(&mut self) {
362 if let Some(entity_map) = self.entity_map.upgrade() {
363 let entity_map = entity_map.upgradable_read();
364 let count = entity_map
365 .counts
366 .get(self.entity_id)
367 .expect("detected over-release of a handle.");
368 let prev_count = count.fetch_sub(1, SeqCst);
369 assert_ne!(prev_count, 0, "Detected over-release of a entity.");
370 if prev_count == 1 {
371 let mut entity_map = RwLockUpgradableReadGuard::upgrade(entity_map);
373 entity_map.dropped_entity_ids.push(self.entity_id);
374 }
375 }
376
377 #[cfg(any(test, feature = "leak-detection"))]
378 if let Some(entity_map) = self.entity_map.upgrade() {
379 entity_map
380 .write()
381 .leak_detector
382 .handle_released(self.entity_id, self.handle_id)
383 }
384 }
385}
386
387impl<T> From<Entity<T>> for AnyEntity {
388 #[inline]
389 fn from(entity: Entity<T>) -> Self {
390 entity.any_entity
391 }
392}
393
394impl Hash for AnyEntity {
395 #[inline]
396 fn hash<H: Hasher>(&self, state: &mut H) {
397 self.entity_id.hash(state);
398 }
399}
400
401impl PartialEq for AnyEntity {
402 #[inline]
403 fn eq(&self, other: &Self) -> bool {
404 self.entity_id == other.entity_id
405 }
406}
407
408impl Eq for AnyEntity {}
409
410impl Ord for AnyEntity {
411 #[inline]
412 fn cmp(&self, other: &Self) -> Ordering {
413 self.entity_id.cmp(&other.entity_id)
414 }
415}
416
417impl PartialOrd for AnyEntity {
418 #[inline]
419 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
420 Some(self.cmp(other))
421 }
422}
423
424impl std::fmt::Debug for AnyEntity {
425 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
426 f.debug_struct("AnyEntity")
427 .field("entity_id", &self.entity_id.as_u64())
428 .finish()
429 }
430}
431
432#[derive(Deref, DerefMut)]
435pub struct Entity<T> {
436 #[deref]
437 #[deref_mut]
438 pub(crate) any_entity: AnyEntity,
439 pub(crate) entity_type: PhantomData<fn(T) -> T>,
440}
441
442impl<T> Sealed for Entity<T> {}
443
444impl<T: 'static> Entity<T> {
445 #[inline]
446 fn new(id: EntityId, entity_map: Weak<RwLock<EntityRefCounts>>) -> Self
447 where
448 T: 'static,
449 {
450 Self {
451 any_entity: AnyEntity::new(
452 id,
453 TypeId::of::<T>(),
454 entity_map,
455 #[cfg(any(test, feature = "leak-detection"))]
456 std::any::type_name::<T>(),
457 ),
458 entity_type: PhantomData,
459 }
460 }
461
462 #[inline]
464 pub fn entity_id(&self) -> EntityId {
465 self.any_entity.entity_id
466 }
467
468 #[inline]
470 pub fn downgrade(&self) -> WeakEntity<T> {
471 WeakEntity {
472 any_entity: self.any_entity.downgrade(),
473 entity_type: self.entity_type,
474 }
475 }
476
477 #[inline]
479 pub fn into_any(self) -> AnyEntity {
480 self.any_entity
481 }
482
483 #[inline]
485 pub fn read<'a>(&self, cx: &'a App) -> &'a T {
486 cx.entities.read(self)
487 }
488
489 #[inline]
491 pub fn read_with<R, C: AppContext>(&self, cx: &C, f: impl FnOnce(&T, &App) -> R) -> R {
492 cx.read_entity(self, f)
493 }
494
495 #[inline]
497 pub fn update<R, C: AppContext>(
498 &self,
499 cx: &mut C,
500 update: impl FnOnce(&mut T, &mut Context<T>) -> R,
501 ) -> R {
502 cx.update_entity(self, update)
503 }
504
505 #[inline]
507 pub fn as_mut<'a, C: AppContext>(&self, cx: &'a mut C) -> GpuiBorrow<'a, T> {
508 cx.as_mut(self)
509 }
510
511 pub fn write<C: AppContext>(&self, cx: &mut C, value: T) {
513 self.update(cx, |entity, cx| {
514 *entity = value;
515 cx.notify();
516 })
517 }
518
519 #[inline]
523 pub fn update_in<R, C: VisualContext>(
524 &self,
525 cx: &mut C,
526 update: impl FnOnce(&mut T, &mut Window, &mut Context<T>) -> R,
527 ) -> C::Result<R> {
528 cx.update_window_entity(self, update)
529 }
530}
531
532impl<T> Clone for Entity<T> {
533 #[inline]
534 fn clone(&self) -> Self {
535 Self {
536 any_entity: self.any_entity.clone(),
537 entity_type: self.entity_type,
538 }
539 }
540}
541
542impl<T> std::fmt::Debug for Entity<T> {
543 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
544 f.debug_struct("Entity")
545 .field("entity_id", &self.any_entity.entity_id)
546 .field("entity_type", &type_name::<T>())
547 .finish()
548 }
549}
550
551impl<T> Hash for Entity<T> {
552 #[inline]
553 fn hash<H: Hasher>(&self, state: &mut H) {
554 self.any_entity.hash(state);
555 }
556}
557
558impl<T> PartialEq for Entity<T> {
559 #[inline]
560 fn eq(&self, other: &Self) -> bool {
561 self.any_entity == other.any_entity
562 }
563}
564
565impl<T> Eq for Entity<T> {}
566
567impl<T> PartialEq<WeakEntity<T>> for Entity<T> {
568 #[inline]
569 fn eq(&self, other: &WeakEntity<T>) -> bool {
570 self.any_entity.entity_id() == other.entity_id()
571 }
572}
573
574impl<T: 'static> Ord for Entity<T> {
575 #[inline]
576 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
577 self.entity_id().cmp(&other.entity_id())
578 }
579}
580
581impl<T: 'static> PartialOrd for Entity<T> {
582 #[inline]
583 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
584 Some(self.cmp(other))
585 }
586}
587
588#[derive(Clone)]
590pub struct AnyWeakEntity {
591 pub(crate) entity_id: EntityId,
592 entity_type: TypeId,
593 entity_ref_counts: Weak<RwLock<EntityRefCounts>>,
594}
595
596impl AnyWeakEntity {
597 #[inline]
599 pub fn entity_id(&self) -> EntityId {
600 self.entity_id
601 }
602
603 pub fn is_upgradable(&self) -> bool {
605 let ref_count = self
606 .entity_ref_counts
607 .upgrade()
608 .and_then(|ref_counts| Some(ref_counts.read().counts.get(self.entity_id)?.load(SeqCst)))
609 .unwrap_or(0);
610 ref_count > 0
611 }
612
613 pub fn upgrade(&self) -> Option<AnyEntity> {
615 let ref_counts = &self.entity_ref_counts.upgrade()?;
616 let ref_counts = ref_counts.read();
617 let ref_count = ref_counts.counts.get(self.entity_id)?;
618
619 if atomic_incr_if_not_zero(ref_count) == 0 {
620 return None;
622 }
623 drop(ref_counts);
624
625 Some(AnyEntity {
626 entity_id: self.entity_id,
627 entity_type: self.entity_type,
628 entity_map: self.entity_ref_counts.clone(),
629 #[cfg(any(test, feature = "leak-detection"))]
630 handle_id: self
631 .entity_ref_counts
632 .upgrade()
633 .unwrap()
634 .write()
635 .leak_detector
636 .handle_created(self.entity_id, None),
637 })
638 }
639
640 #[cfg(any(test, feature = "leak-detection"))]
668 pub fn assert_released(&self) {
669 self.entity_ref_counts
670 .upgrade()
671 .unwrap()
672 .write()
673 .leak_detector
674 .assert_released(self.entity_id);
675
676 if self
677 .entity_ref_counts
678 .upgrade()
679 .and_then(|ref_counts| Some(ref_counts.read().counts.get(self.entity_id)?.load(SeqCst)))
680 .is_some()
681 {
682 panic!(
683 "entity was recently dropped but resources are retained until the end of the effect cycle."
684 )
685 }
686 }
687
688 pub fn new_invalid() -> Self {
690 static UNIQUE_NON_CONFLICTING_ID_GENERATOR: AtomicU64 = AtomicU64::new(u64::MAX);
694 let entity_id = UNIQUE_NON_CONFLICTING_ID_GENERATOR.fetch_sub(1, SeqCst);
695
696 Self {
697 entity_id: entity_id.into(),
707 entity_type: TypeId::of::<()>(),
708 entity_ref_counts: Weak::new(),
709 }
710 }
711}
712
713impl std::fmt::Debug for AnyWeakEntity {
714 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
715 f.debug_struct(type_name::<Self>())
716 .field("entity_id", &self.entity_id)
717 .field("entity_type", &self.entity_type)
718 .finish()
719 }
720}
721
722impl<T> From<WeakEntity<T>> for AnyWeakEntity {
723 #[inline]
724 fn from(entity: WeakEntity<T>) -> Self {
725 entity.any_entity
726 }
727}
728
729impl Hash for AnyWeakEntity {
730 #[inline]
731 fn hash<H: Hasher>(&self, state: &mut H) {
732 self.entity_id.hash(state);
733 }
734}
735
736impl PartialEq for AnyWeakEntity {
737 #[inline]
738 fn eq(&self, other: &Self) -> bool {
739 self.entity_id == other.entity_id
740 }
741}
742
743impl Eq for AnyWeakEntity {}
744
745impl Ord for AnyWeakEntity {
746 #[inline]
747 fn cmp(&self, other: &Self) -> Ordering {
748 self.entity_id.cmp(&other.entity_id)
749 }
750}
751
752impl PartialOrd for AnyWeakEntity {
753 #[inline]
754 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
755 Some(self.cmp(other))
756 }
757}
758
759#[derive(Deref, DerefMut)]
761pub struct WeakEntity<T> {
762 #[deref]
763 #[deref_mut]
764 any_entity: AnyWeakEntity,
765 entity_type: PhantomData<fn(T) -> T>,
766}
767
768impl<T> std::fmt::Debug for WeakEntity<T> {
769 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
770 f.debug_struct(type_name::<Self>())
771 .field("entity_id", &self.any_entity.entity_id)
772 .field("entity_type", &type_name::<T>())
773 .finish()
774 }
775}
776
777impl<T> Clone for WeakEntity<T> {
778 fn clone(&self) -> Self {
779 Self {
780 any_entity: self.any_entity.clone(),
781 entity_type: self.entity_type,
782 }
783 }
784}
785
786impl<T: 'static> WeakEntity<T> {
787 pub fn upgrade(&self) -> Option<Entity<T>> {
789 Some(Entity {
790 any_entity: self.any_entity.upgrade()?,
791 entity_type: self.entity_type,
792 })
793 }
794
795 #[inline(always)]
799 pub fn update<C, R>(
800 &self,
801 cx: &mut C,
802 update: impl FnOnce(&mut T, &mut Context<T>) -> R,
803 ) -> Result<R>
804 where
805 C: AppContext,
806 {
807 let entity = self.upgrade().context("entity released")?;
808 Ok(cx.update_entity(&entity, update))
809 }
810
811 #[inline(always)]
815 pub fn update_in<C, R>(
816 &self,
817 cx: &mut C,
818 update: impl FnOnce(&mut T, &mut Window, &mut Context<T>) -> R,
819 ) -> Result<R>
820 where
821 C: AppContext,
822 {
823 let entity = self.upgrade().context("entity released")?;
824 cx.with_window(entity.entity_id(), |window, app| {
825 entity.update(app, |entity, cx| update(entity, window, cx))
826 })
827 .context("entity has no current window")
828 }
829
830 #[inline(always)]
834 pub fn read_with<C, R>(&self, cx: &C, read: impl FnOnce(&T, &App) -> R) -> Result<R>
835 where
836 C: AppContext,
837 {
838 let entity = self.upgrade().context("entity released")?;
839 Ok(cx.read_entity(&entity, read))
840 }
841
842 #[inline]
844 pub fn new_invalid() -> Self {
845 Self {
846 any_entity: AnyWeakEntity::new_invalid(),
847 entity_type: PhantomData,
848 }
849 }
850}
851
852impl<T> Hash for WeakEntity<T> {
853 #[inline]
854 fn hash<H: Hasher>(&self, state: &mut H) {
855 self.any_entity.hash(state);
856 }
857}
858
859impl<T> PartialEq for WeakEntity<T> {
860 #[inline]
861 fn eq(&self, other: &Self) -> bool {
862 self.any_entity == other.any_entity
863 }
864}
865
866impl<T> Eq for WeakEntity<T> {}
867
868impl<T> PartialEq<Entity<T>> for WeakEntity<T> {
869 #[inline]
870 fn eq(&self, other: &Entity<T>) -> bool {
871 self.entity_id() == other.any_entity.entity_id()
872 }
873}
874
875impl<T: 'static> Ord for WeakEntity<T> {
876 #[inline]
877 fn cmp(&self, other: &Self) -> Ordering {
878 self.entity_id().cmp(&other.entity_id())
879 }
880}
881
882impl<T: 'static> PartialOrd for WeakEntity<T> {
883 #[inline]
884 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
885 Some(self.cmp(other))
886 }
887}
888
889#[cfg(any(test, feature = "leak-detection"))]
894static LEAK_BACKTRACE: std::sync::LazyLock<bool> =
895 std::sync::LazyLock::new(|| std::env::var("LEAK_BACKTRACE").is_ok_and(|b| !b.is_empty()));
896
897#[cfg(any(test, feature = "leak-detection"))]
902#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq)]
903pub(crate) struct HandleId {
904 id: u64,
905}
906
907#[cfg(any(test, feature = "leak-detection"))]
957pub(crate) struct LeakDetector {
958 next_handle_id: u64,
959 entity_handles: HashMap<EntityId, EntityLeakData>,
960}
961
962#[cfg(any(test, feature = "leak-detection"))]
968pub struct LeakDetectorSnapshot {
969 entity_ids: collections::HashSet<EntityId>,
970}
971
972#[cfg(any(test, feature = "leak-detection"))]
973struct EntityLeakData {
974 handles: HashMap<HandleId, Option<backtrace::Backtrace>>,
975 type_name: &'static str,
976}
977
978#[cfg(any(test, feature = "leak-detection"))]
979impl LeakDetector {
980 #[track_caller]
986 pub fn handle_created(
987 &mut self,
988 entity_id: EntityId,
989 type_name: Option<&'static str>,
990 ) -> HandleId {
991 let id = gpui_util::post_inc(&mut self.next_handle_id);
992 let handle_id = HandleId { id };
993 let handles = self
994 .entity_handles
995 .entry(entity_id)
996 .or_insert_with(|| EntityLeakData {
997 handles: HashMap::default(),
998 type_name: type_name.unwrap_or("<unknown>"),
999 });
1000 handles.handles.insert(
1001 handle_id,
1002 LEAK_BACKTRACE.then(backtrace::Backtrace::new_unresolved),
1003 );
1004 handle_id
1005 }
1006
1007 pub fn handle_released(&mut self, entity_id: EntityId, handle_id: HandleId) {
1012 if let std::collections::hash_map::Entry::Occupied(mut data) =
1013 self.entity_handles.entry(entity_id)
1014 {
1015 data.get_mut().handles.remove(&handle_id);
1016 if data.get().handles.is_empty() {
1017 data.remove();
1018 }
1019 }
1020 }
1021
1022 pub fn assert_released(&mut self, entity_id: EntityId) {
1030 use std::fmt::Write as _;
1031
1032 if let Some(data) = self.entity_handles.remove(&entity_id) {
1033 let mut out = String::new();
1034 for (_, backtrace) in data.handles {
1035 if let Some(mut backtrace) = backtrace {
1036 backtrace.resolve();
1037 let backtrace = BacktraceFormatter(backtrace);
1038 writeln!(out, "Leaked handle:\n{:?}", backtrace).unwrap();
1039 } else {
1040 writeln!(
1041 out,
1042 "Leaked handle: (export LEAK_BACKTRACE to find allocation site)"
1043 )
1044 .unwrap();
1045 }
1046 }
1047 panic!("Handles for {} leaked:\n{out}", data.type_name);
1048 }
1049 }
1050
1051 pub fn snapshot(&self) -> LeakDetectorSnapshot {
1057 LeakDetectorSnapshot {
1058 entity_ids: self.entity_handles.keys().copied().collect(),
1059 }
1060 }
1061
1062 pub fn assert_no_new_leaks(&self, snapshot: &LeakDetectorSnapshot) {
1074 use std::fmt::Write as _;
1075
1076 let mut out = String::new();
1077 for (entity_id, data) in &self.entity_handles {
1078 if snapshot.entity_ids.contains(entity_id) {
1079 continue;
1080 }
1081 for (_, backtrace) in &data.handles {
1082 if let Some(backtrace) = backtrace {
1083 let mut backtrace = backtrace.clone();
1084 backtrace.resolve();
1085 let backtrace = BacktraceFormatter(backtrace);
1086 writeln!(
1087 out,
1088 "Leaked handle for entity {} ({entity_id:?}):\n{:?}",
1089 data.type_name, backtrace
1090 )
1091 .unwrap();
1092 } else {
1093 writeln!(
1094 out,
1095 "Leaked handle for entity {} ({entity_id:?}): (export LEAK_BACKTRACE to find allocation site)",
1096 data.type_name
1097 )
1098 .unwrap();
1099 }
1100 }
1101 }
1102
1103 if !out.is_empty() {
1104 panic!("New entity leaks detected since snapshot:\n{out}");
1105 }
1106 }
1107}
1108
1109#[cfg(any(test, feature = "leak-detection"))]
1110impl Drop for LeakDetector {
1111 fn drop(&mut self) {
1112 use std::fmt::Write;
1113
1114 if self.entity_handles.is_empty() || std::thread::panicking() {
1115 return;
1116 }
1117
1118 let mut out = String::new();
1119 for (entity_id, data) in self.entity_handles.drain() {
1120 for (_handle, backtrace) in data.handles {
1121 if let Some(mut backtrace) = backtrace {
1122 backtrace.resolve();
1123 let backtrace = BacktraceFormatter(backtrace);
1124 writeln!(
1125 out,
1126 "Leaked handle for entity {} ({entity_id:?}):\n{:?}",
1127 data.type_name, backtrace
1128 )
1129 .unwrap();
1130 } else {
1131 writeln!(
1132 out,
1133 "Leaked handle for entity {} ({entity_id:?}): (export LEAK_BACKTRACE to find allocation site)",
1134 data.type_name
1135 )
1136 .unwrap();
1137 }
1138 }
1139 }
1140 panic!("Exited with leaked handles:\n{out}");
1141 }
1142}
1143
1144#[cfg(any(test, feature = "leak-detection"))]
1145struct BacktraceFormatter(backtrace::Backtrace);
1146
1147#[cfg(any(test, feature = "leak-detection"))]
1148impl fmt::Debug for BacktraceFormatter {
1149 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
1150 use backtrace::{BacktraceFmt, BytesOrWideString, PrintFmt};
1151
1152 let style = if fmt.alternate() {
1153 PrintFmt::Full
1154 } else {
1155 PrintFmt::Short
1156 };
1157
1158 let cwd = std::env::current_dir();
1163 let mut print_path = move |fmt: &mut fmt::Formatter<'_>, path: BytesOrWideString<'_>| {
1164 let path = path.into_path_buf();
1165 if style != PrintFmt::Full {
1166 if let Ok(cwd) = &cwd {
1167 if let Ok(suffix) = path.strip_prefix(cwd) {
1168 return fmt::Display::fmt(&suffix.display(), fmt);
1169 }
1170 }
1171 }
1172 fmt::Display::fmt(&path.display(), fmt)
1173 };
1174
1175 let mut f = BacktraceFmt::new(fmt, style, &mut print_path);
1176 f.add_context()?;
1177 let mut strip = true;
1178 for frame in self.0.frames() {
1179 if let [symbol, ..] = frame.symbols()
1180 && let Some(name) = symbol.name()
1181 && let Some(filename) = name.as_str()
1182 {
1183 match filename {
1184 "test::run_test_in_process"
1185 | "scheduler::executor::spawn_local_with_source_location::impl$1::poll<core::pin::Pin<alloc::boxed::Box<dyn$<core::future::future::Future<assoc$<Output,enum2$<core::result::Result<workspace::OpenResult,anyhow::Error> > > > >,alloc::alloc::Global> > >" => {
1186 strip = true
1187 }
1188 "gpui::app::entity_map::LeakDetector::handle_created" => {
1189 strip = false;
1190 continue;
1191 }
1192 "zed::main" => {
1193 strip = true;
1194 f.frame().backtrace_frame(frame)?;
1195 }
1196 _ => {}
1197 }
1198 }
1199 if strip {
1200 continue;
1201 }
1202 f.frame().backtrace_frame(frame)?;
1203 }
1204 f.finish()?;
1205 Ok(())
1206 }
1207}
1208
1209#[cfg(test)]
1210mod test {
1211 use crate::EntityMap;
1212
1213 struct TestEntity {
1214 pub i: i32,
1215 }
1216
1217 #[test]
1218 fn test_entity_map_slot_assignment_before_cleanup() {
1219 let mut entity_map = EntityMap::new();
1221
1222 let slot = entity_map.reserve::<TestEntity>();
1223 entity_map.insert(slot, TestEntity { i: 1 });
1224
1225 let slot = entity_map.reserve::<TestEntity>();
1226 entity_map.insert(slot, TestEntity { i: 2 });
1227
1228 let dropped = entity_map.take_dropped();
1229 assert_eq!(dropped.len(), 2);
1230
1231 assert_eq!(
1232 dropped
1233 .into_iter()
1234 .map(|(_, entity)| entity.downcast::<TestEntity>().unwrap().i)
1235 .collect::<Vec<i32>>(),
1236 vec![1, 2],
1237 );
1238 }
1239
1240 #[test]
1241 fn test_entity_map_weak_upgrade_before_cleanup() {
1242 let mut entity_map = EntityMap::new();
1244
1245 let slot = entity_map.reserve::<TestEntity>();
1246 let handle = entity_map.insert(slot, TestEntity { i: 1 });
1247 let weak = handle.downgrade();
1248 drop(handle);
1249
1250 let strong = weak.upgrade();
1251 assert_eq!(strong, None);
1252
1253 let dropped = entity_map.take_dropped();
1254 assert_eq!(dropped.len(), 1);
1255
1256 assert_eq!(
1257 dropped
1258 .into_iter()
1259 .map(|(_, entity)| entity.downcast::<TestEntity>().unwrap().i)
1260 .collect::<Vec<i32>>(),
1261 vec![1],
1262 );
1263 }
1264
1265 #[test]
1266 fn test_leak_detector_snapshot_no_leaks() {
1267 let mut entity_map = EntityMap::new();
1268
1269 let slot = entity_map.reserve::<TestEntity>();
1270 let pre_existing = entity_map.insert(slot, TestEntity { i: 1 });
1271
1272 let snapshot = entity_map.leak_detector_snapshot();
1273
1274 let slot = entity_map.reserve::<TestEntity>();
1275 let temporary = entity_map.insert(slot, TestEntity { i: 2 });
1276 drop(temporary);
1277
1278 entity_map.assert_no_new_leaks(&snapshot);
1279
1280 drop(pre_existing);
1281 }
1282
1283 #[test]
1284 #[should_panic(expected = "New entity leaks detected since snapshot")]
1285 fn test_leak_detector_snapshot_detects_new_leak() {
1286 let mut entity_map = EntityMap::new();
1287
1288 let slot = entity_map.reserve::<TestEntity>();
1289 let pre_existing = entity_map.insert(slot, TestEntity { i: 1 });
1290
1291 let snapshot = entity_map.leak_detector_snapshot();
1292
1293 let slot = entity_map.reserve::<TestEntity>();
1294 let leaked = entity_map.insert(slot, TestEntity { i: 2 });
1295
1296 entity_map.assert_no_new_leaks(&snapshot);
1298
1299 drop(pre_existing);
1300 drop(leaked);
1301 }
1302}