diff --git a/crates/gpui3/src/app.rs b/crates/gpui3/src/app.rs index a03d141783..6ddf01791f 100644 --- a/crates/gpui3/src/app.rs +++ b/crates/gpui3/src/app.rs @@ -1,7 +1,9 @@ mod async_context; +mod entities; mod model_context; pub use async_context::*; +pub use entities::*; pub use model_context::*; use crate::{ @@ -14,11 +16,7 @@ use futures::{future, Future}; use parking_lot::Mutex; use slotmap::SlotMap; use smallvec::SmallVec; -use std::{ - any::Any, - marker::PhantomData, - sync::{Arc, Weak}, -}; +use std::sync::{Arc, Weak}; use util::ResultExt; #[derive(Clone)] @@ -37,8 +35,8 @@ impl App { fn new(platform: Arc) -> Self { let dispatcher = platform.dispatcher(); let text_system = Arc::new(TextSystem::new(platform.text_system())); - let mut entities = SlotMap::with_key(); - let unit_entity = Handle::new(entities.insert(Some(Box::new(()) as Box))); + let entities = EntityMap::new(); + let unit_entity = entities.redeem(entities.reserve(), ()); Self(Arc::new_cyclic(|this| { Mutex::new(AppContext { this: this.clone(), @@ -76,7 +74,7 @@ pub struct AppContext { text_system: Arc, pending_updates: usize, pub(crate) unit_entity: Handle<()>, - pub(crate) entities: SlotMap>>, + pub(crate) entities: EntityMap, pub(crate) windows: SlotMap>, pub(crate) pending_effects: VecDeque, pub(crate) observers: HashMap, @@ -204,11 +202,9 @@ impl Context for AppContext { &mut self, build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T, ) -> Handle { - let id = self.entities.insert(None); - let entity = Box::new(build_entity(&mut ModelContext::mutable(self, id))); - self.entities.get_mut(id).unwrap().replace(entity); - - Handle::new(id) + let slot = self.entities.reserve(); + let entity = build_entity(&mut ModelContext::mutable(self, slot.id)); + self.entities.redeem(slot, entity) } fn update_entity( @@ -216,103 +212,13 @@ impl Context for AppContext { handle: &Handle, update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R, ) -> R { - let mut entity = self - .entities - .get_mut(handle.id) - .unwrap() - .take() - .unwrap() - .downcast::() - .unwrap(); - + let mut entity = self.entities.lease(handle); let result = update(&mut *entity, &mut ModelContext::mutable(self, handle.id)); - self.entities.get_mut(handle.id).unwrap().replace(entity); + self.entities.end_lease(entity); result } } -slotmap::new_key_type! { pub struct EntityId; } - -pub struct Handle { - pub(crate) id: EntityId, - pub(crate) entity_type: PhantomData, -} - -impl Handle { - fn new(id: EntityId) -> Self { - Self { - id, - entity_type: PhantomData, - } - } - - pub fn downgrade(&self) -> WeakHandle { - WeakHandle { - id: self.id, - entity_type: self.entity_type, - } - } - - /// Update the entity referenced by this handle with the given function. - /// - /// The update function receives a context appropriate for its environment. - /// When updating in an `AppContext`, it receives a `ModelContext`. - /// When updating an a `WindowContext`, it receives a `ViewContext`. - pub fn update( - &self, - cx: &mut C, - update: impl FnOnce(&mut T, &mut C::EntityContext<'_, '_, T>) -> R, - ) -> C::Result { - cx.update_entity(self, update) - } -} - -impl Clone for Handle { - fn clone(&self) -> Self { - Self { - id: self.id, - entity_type: PhantomData, - } - } -} - -pub struct WeakHandle { - pub(crate) id: EntityId, - pub(crate) entity_type: PhantomData, -} - -impl WeakHandle { - pub fn upgrade(&self, _: &impl Context) -> Option> { - // todo!("Actually upgrade") - Some(Handle { - id: self.id, - entity_type: self.entity_type, - }) - } - - /// Update the entity referenced by this handle with the given function if - /// the referenced entity still exists. Returns an error if the entity has - /// been released. - /// - /// The update function receives a context appropriate for its environment. - /// When updating in an `AppContext`, it receives a `ModelContext`. - /// When updating an a `WindowContext`, it receives a `ViewContext`. - pub fn update( - &self, - cx: &mut C, - update: impl FnOnce(&mut T, &mut C::EntityContext<'_, '_, T>) -> R, - ) -> Result - where - Result>: crate::Flatten, - { - crate::Flatten::flatten( - self.upgrade(cx) - .ok_or_else(|| anyhow!("entity release")) - .map(|this| cx.update_entity(&this, update)), - ) - } -} - pub(crate) enum Effect { Notify(EntityId), } diff --git a/crates/gpui3/src/app/entities.rs b/crates/gpui3/src/app/entities.rs new file mode 100644 index 0000000000..4e98883d0e --- /dev/null +++ b/crates/gpui3/src/app/entities.rs @@ -0,0 +1,143 @@ +use crate::Context; +use anyhow::{anyhow, Result}; +use derive_more::{Deref, DerefMut}; +use parking_lot::Mutex; +use slotmap::{SecondaryMap, SlotMap}; +use std::{any::Any, marker::PhantomData, sync::Arc}; + +slotmap::new_key_type! { pub struct EntityId; } + +#[derive(Deref, DerefMut)] +pub struct Lease { + #[deref] + #[deref_mut] + entity: Box, + pub id: EntityId, +} + +pub(crate) struct EntityMap { + ref_counts: Arc>>, + entities: Arc>>>, +} + +impl EntityMap { + pub fn new() -> Self { + Self { + ref_counts: Arc::new(Mutex::new(SlotMap::with_key())), + entities: Arc::new(Mutex::new(SecondaryMap::new())), + } + } + + pub fn reserve(&self) -> Slot { + let id = self.ref_counts.lock().insert(1); + Slot(Handle { + id, + entity_type: PhantomData, + }) + } + + pub fn redeem(&self, slot: Slot, entity: T) -> Handle { + let handle = slot.0; + self.entities.lock().insert(handle.id, Box::new(entity)); + handle + } + + pub fn lease(&self, handle: &Handle) -> Lease { + let id = handle.id; + let entity = self + .entities + .lock() + .remove(id) + .expect("Circular entity lease. Is the entity already being updated?") + .downcast::() + .unwrap(); + Lease { id, entity } + } + + pub fn end_lease(&mut self, lease: Lease) { + self.entities.lock().insert(lease.id, lease.entity); + } +} + +#[derive(Deref, DerefMut)] +pub struct Slot(Handle); + +pub struct Handle { + pub(crate) id: EntityId, + pub(crate) entity_type: PhantomData, +} + +impl Handle { + pub fn new(id: EntityId) -> Self { + Self { + id, + entity_type: PhantomData, + } + } + + pub fn downgrade(&self) -> WeakHandle { + WeakHandle { + id: self.id, + entity_type: self.entity_type, + } + } + + /// Update the entity referenced by this handle with the given function. + /// + /// The update function receives a context appropriate for its environment. + /// When updating in an `AppContext`, it receives a `ModelContext`. + /// When updating an a `WindowContext`, it receives a `ViewContext`. + pub fn update( + &self, + cx: &mut C, + update: impl FnOnce(&mut T, &mut C::EntityContext<'_, '_, T>) -> R, + ) -> C::Result { + cx.update_entity(self, update) + } +} + +impl Clone for Handle { + fn clone(&self) -> Self { + Self { + id: self.id, + entity_type: PhantomData, + } + } +} + +pub struct WeakHandle { + pub(crate) id: EntityId, + pub(crate) entity_type: PhantomData, +} + +impl WeakHandle { + pub fn upgrade(&self, _: &impl Context) -> Option> { + // todo!("Actually upgrade") + Some(Handle { + id: self.id, + entity_type: self.entity_type, + }) + } + + /// Update the entity referenced by this handle with the given function if + /// the referenced entity still exists. Returns an error if the entity has + /// been released. + /// + /// The update function receives a context appropriate for its environment. + /// When updating in an `AppContext`, it receives a `ModelContext`. + /// When updating an a `WindowContext`, it receives a `ViewContext`. + pub fn update( + &self, + cx: &mut C, + update: impl FnOnce(&mut T, &mut C::EntityContext<'_, '_, T>) -> R, + ) -> Result + where + Result>: crate::Flatten, + { + crate::Flatten::flatten( + self.upgrade(cx) + .ok_or_else(|| anyhow!("entity release")) + .map(|this| cx.update_entity(&this, update)), + ) + } +} diff --git a/crates/gpui3/src/window.rs b/crates/gpui3/src/window.rs index 7c2416b146..745ac217f0 100644 --- a/crates/gpui3/src/window.rs +++ b/crates/gpui3/src/window.rs @@ -214,18 +214,13 @@ impl Context for WindowContext<'_, '_> { &mut self, build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T, ) -> Handle { - let id = self.entities.insert(None); - let entity = Box::new(build_entity(&mut ViewContext::mutable( + let slot = self.entities.reserve(); + let entity = build_entity(&mut ViewContext::mutable( &mut *self.app, &mut self.window, - id, - ))); - self.entities.get_mut(id).unwrap().replace(entity); - - Handle { - id, - entity_type: PhantomData, - } + slot.id, + )); + self.entities.redeem(slot, entity) } fn update_entity( @@ -233,27 +228,12 @@ impl Context for WindowContext<'_, '_> { handle: &Handle, update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R, ) -> R { - let mut entity = self - .app - .entities - .get_mut(handle.id) - .unwrap() - .take() - .unwrap() - .downcast::() - .unwrap(); - + let mut entity = self.entities.lease(handle); let result = update( &mut *entity, &mut ViewContext::mutable(&mut *self.app, &mut *self.window, handle.id), ); - - self.app - .entities - .get_mut(handle.id) - .unwrap() - .replace(entity); - + self.entities.end_lease(entity); result } }