diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 5f91a9d7ed..f4a23b921d 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1154,6 +1154,7 @@ impl App { self.pending_effects.push_back(Effect::RefreshWindows); } + #[inline(always)] pub(crate) fn update(&mut self, update: impl FnOnce(&mut Self) -> R) -> R { self.start_update(); let result = update(self); @@ -1165,6 +1166,7 @@ impl App { self.pending_updates += 1; } + #[inline(never)] pub(crate) fn finish_update(&mut self) { if !self.flushing_effects && self.pending_updates == 1 { self.flushing_effects = true; @@ -1962,6 +1964,7 @@ impl App { /// it has yet to be rendered. Returns `None` if the entity has no /// current window, or if that window has been closed, or if it is /// already on the update stack. + #[inline(always)] pub fn with_window( &mut self, entity_id: EntityId, @@ -1978,61 +1981,22 @@ impl App { .or_insert(window); } + #[inline(always)] pub(crate) fn update_window_id(&mut self, id: WindowId, update: F) -> Result where F: FnOnce(AnyView, &mut Window, &mut App) -> T, { - self.update(|cx| { - let mut window = cx.windows.get_mut(id)?.take()?; - - let root_view = window.root.clone().unwrap(); - - cx.window_update_stack.push(window.handle.id); - let result = update(root_view, &mut window, cx); - fn trail(id: WindowId, window: Box, cx: &mut App) -> Option<()> { - cx.window_update_stack.pop(); - - if window.removed { - cx.end_platform_drag(id); - cx.window_handles.remove(&id); - cx.windows.remove(id); - if let Some(tracked) = cx.tracked_entities.remove(&id) { - for entity_id in tracked { - if let Some(windows) = - cx.window_invalidators_by_entity.get_mut(&entity_id) - { - windows.remove(&id); - } - if cx.current_window_by_entity.get(&entity_id) == Some(&id) { - cx.current_window_by_entity.remove(&entity_id); - } - } - } - - cx.window_closed_observers.clone().retain(&(), |callback| { - callback(cx, id); - true - }); - - let quit_on_empty = match cx.quit_mode { - QuitMode::Explicit => false, - QuitMode::LastWindowClosed => true, - QuitMode::Default => cfg!(not(target_os = "macos")), - }; - - if quit_on_empty && cx.windows.is_empty() { - cx.quit(); - } - } else { - cx.windows.get_mut(id)?.replace(window); - } - Some(()) + let mut update = Some(update); + let mut result = None; + self.update_window_erased(id, &mut |arguments| { + if let Some((root_view, window, cx)) = arguments { + result = Some(update.take().unwrap()(root_view, window, cx)); + } else { + drop(update.take()); + drop(result.take()); } - trail(id, window, cx)?; - - Some(result) - }) - .context("window not found") + }); + result.context("window not found") } /// Creates an `AsyncApp`, which can be cloned and has a static lifetime @@ -2068,16 +2032,13 @@ impl App { /// Spawns the future returned by the given function on the main thread. The closure will be invoked /// with [AsyncApp], which allows the application state to be accessed across await points. #[track_caller] + #[inline(always)] pub fn spawn(&self, f: AsyncFn) -> Task where AsyncFn: AsyncFnOnce(&mut AsyncApp) -> R + 'static, R: 'static, { - if self.quitting { - debug_panic!("Can't spawn on main thread after on_app_quit") - }; - - let mut cx = self.to_async(); + let mut cx = self.prepare_spawn(); self.foreground_executor .spawn(async move { f(&mut cx).await }.boxed_local()) @@ -2912,6 +2873,90 @@ impl App { pub fn init_colors(&mut self) { self.set_global(GlobalColors(Arc::new(Colors::default()))); } + + #[inline(never)] + fn update_window_erased( + &mut self, + window_id: WindowId, + update: &mut dyn FnMut(Option<(AnyView, &mut Window, &mut App)>), + ) { + self.update(|cx| { + let Some(mut window) = cx.windows.get_mut(window_id).and_then(Option::take) else { + update(None); + return; + }; + + let root_view = window.root.clone().unwrap(); + + cx.window_update_stack.push(window.handle.id); + update(Some((root_view, &mut window, cx))); + fn trail(window_id: WindowId, window: Box, cx: &mut App) -> Option<()> { + cx.window_update_stack.pop(); + + if window.removed { + cx.end_platform_drag(window_id); + cx.window_handles.remove(&window_id); + cx.windows.remove(window_id); + if let Some(tracked) = cx.tracked_entities.remove(&window_id) { + for entity_id in tracked { + if let Some(windows) = + cx.window_invalidators_by_entity.get_mut(&entity_id) + { + windows.remove(&window_id); + } + if cx.current_window_by_entity.get(&entity_id) == Some(&window_id) { + cx.current_window_by_entity.remove(&entity_id); + } + } + } + + cx.window_closed_observers.clone().retain(&(), |callback| { + callback(cx, window_id); + true + }); + + let quit_on_empty = match cx.quit_mode { + QuitMode::Explicit => false, + QuitMode::LastWindowClosed => true, + QuitMode::Default => cfg!(not(target_os = "macos")), + }; + + if quit_on_empty && cx.windows.is_empty() { + cx.quit(); + } + } else { + cx.windows.get_mut(window_id)?.replace(window); + } + Some(()) + } + if trail(window_id, window, cx).is_none() { + update(None); + } + }); + } + + #[inline(never)] + fn update_entity_erased( + &mut self, + handle: &AnyEntity, + entity_type: &str, + update: &mut dyn FnMut(&mut dyn Any, &mut App), + ) { + self.update(|cx| { + let mut lease = cx.entities.lease_erased(handle, entity_type); + update(lease.entity.as_deref_mut().unwrap(), cx); + cx.entities.end_lease_erased(handle.entity_id, lease); + }); + } + + #[inline(never)] + #[track_caller] + fn prepare_spawn(&self) -> AsyncApp { + if self.quitting { + debug_panic!("Can't spawn on main thread after on_app_quit") + }; + self.to_async() + } } impl AppContext for App { @@ -2953,20 +2998,22 @@ impl AppContext for App { /// Updates the entity referenced by the given handle. The function is passed a mutable reference to the /// entity along with a `Context` for the entity. + #[inline(always)] fn update_entity( &mut self, handle: &Entity, update: impl FnOnce(&mut T, &mut Context) -> R, ) -> R { - self.update(|cx| { - let mut entity = cx.entities.lease(handle); - let result = update( - &mut entity, + let mut update = Some(update); + let mut result = None; + self.update_entity_erased(handle, type_name::(), &mut |entity, cx| { + let value = update.take().unwrap()( + entity.downcast_mut::().unwrap(), &mut Context::new_context(cx, handle.downgrade()), ); - cx.entities.end_lease(entity); - result - }) + result = Some(value); + }); + result.unwrap() } fn as_mut<'a, T>(&'a mut self, handle: &Entity) -> GpuiBorrow<'a, T> @@ -2976,6 +3023,7 @@ impl AppContext for App { GpuiBorrow::new(handle.clone(), self) } + #[inline(always)] fn read_entity(&self, handle: &Entity, read: impl FnOnce(&T, &App) -> R) -> R where T: 'static, diff --git a/crates/gpui/src/app/async_context.rs b/crates/gpui/src/app/async_context.rs index 48c084a66f..c3af6028be 100644 --- a/crates/gpui/src/app/async_context.rs +++ b/crates/gpui/src/app/async_context.rs @@ -56,6 +56,7 @@ impl AppContext for AsyncApp { app.insert_entity(reservation, build_entity) } + #[inline(always)] fn update_entity( &mut self, handle: &Entity, @@ -73,6 +74,7 @@ impl AppContext for AsyncApp { panic!("Cannot as_mut with an async context. Try calling update() first") } + #[inline(always)] fn read_entity(&self, handle: &Entity, callback: impl FnOnce(&T, &App) -> R) -> R where T: 'static, @@ -82,6 +84,7 @@ impl AppContext for AsyncApp { lock.read_entity(handle, callback) } + #[inline(always)] fn update_window(&mut self, window: AnyWindowHandle, f: F) -> Result where F: FnOnce(AnyView, &mut Window, &mut App) -> T, @@ -94,6 +97,7 @@ impl AppContext for AsyncApp { lock.update_window(window, f) } + #[inline(always)] fn with_window( &mut self, entity_id: EntityId, @@ -162,6 +166,7 @@ impl AsyncApp { } /// Invoke the given function in the context of the app, then flush any effects produced during its invocation. + #[inline(always)] pub fn update(&self, f: impl FnOnce(&mut App) -> R) -> R { let app = self.app(); let mut lock = app.borrow_mut(); @@ -203,6 +208,7 @@ impl AsyncApp { /// Schedule a future to be polled in the foreground. #[track_caller] + #[inline(always)] pub fn spawn(&self, f: AsyncFn) -> Task where AsyncFn: AsyncFnOnce(&mut AsyncApp) -> R + 'static, @@ -223,6 +229,7 @@ impl AsyncApp { /// Reads the global state of the specified type, passing it to the given callback. /// /// Panics if no global state of the specified type has been assigned. + #[inline(always)] pub fn read_global(&self, read: impl FnOnce(&G, &App) -> R) -> R { let app = self.app(); let app = app.borrow_mut(); @@ -298,12 +305,14 @@ impl AsyncWindowContext { } /// A convenience method for [`App::update_window`]. + #[inline(always)] pub fn update(&mut self, update: impl FnOnce(&mut Window, &mut App) -> R) -> Result { self.app .update_window(self.window, |_, window, cx| update(window, cx)) } /// A convenience method for [`App::update_window`]. + #[inline(always)] pub fn update_root( &mut self, update: impl FnOnce(AnyView, &mut Window, &mut App) -> R, @@ -344,6 +353,7 @@ impl AsyncWindowContext { /// Schedule a future to be executed on the main thread. This is used for collecting /// the results of background tasks and updating the UI. #[track_caller] + #[inline(always)] pub fn spawn(&self, f: AsyncFn) -> Task where AsyncFn: AsyncFnOnce(&mut AsyncWindowContext) -> R + 'static, @@ -421,6 +431,7 @@ impl AppContext for AsyncWindowContext { } } + #[inline(always)] fn update_entity( &mut self, handle: &Entity, @@ -436,6 +447,7 @@ impl AppContext for AsyncWindowContext { panic!("Cannot use as_mut() from an async context, call `update`") } + #[inline(always)] fn read_entity(&self, handle: &Entity, read: impl FnOnce(&T, &App) -> R) -> R where T: 'static, @@ -443,6 +455,7 @@ impl AppContext for AsyncWindowContext { self.app.read_entity(handle, read) } + #[inline(always)] fn update_window(&mut self, window: AnyWindowHandle, update: F) -> Result where F: FnOnce(AnyView, &mut Window, &mut App) -> T, @@ -450,6 +463,7 @@ impl AppContext for AsyncWindowContext { self.app.update_window(window, update) } + #[inline(always)] fn with_window( &mut self, entity_id: EntityId, diff --git a/crates/gpui/src/app/context.rs b/crates/gpui/src/app/context.rs index bc77a9236f..c982abefec 100644 --- a/crates/gpui/src/app/context.rs +++ b/crates/gpui/src/app/context.rs @@ -70,14 +70,9 @@ impl<'a, T: 'static> Context<'a, T> { T: 'static, W: 'static, { - let this = self.weak_entity(); - self.app.observe_internal(entity, move |e, cx| { - if let Some(this) = this.upgrade() { - this.update(cx, |this, cx| on_notify(this, e, cx)); - true - } else { - false - } + let observer = self.weak_entity(); + self.app.observe_internal(entity, move |entity, cx| { + invoke_observer(&observer, entity, cx, &mut on_notify) }) } @@ -106,15 +101,11 @@ impl<'a, T: 'static> Context<'a, T> { T2: 'static + EventEmitter, Evt: 'static, { - let this = self.weak_entity(); - self.app.subscribe_internal(entity, move |e, event, cx| { - if let Some(this) = this.upgrade() { - this.update(cx, |this, cx| on_event(this, e, event, cx)); - true - } else { - false - } - }) + let subscriber = self.weak_entity(); + self.app + .subscribe_internal(entity, move |entity, event, cx| { + invoke_subscriber(&subscriber, entity, event, cx, &mut on_event) + }) } /// Subscribe to an event type from ourself @@ -235,6 +226,7 @@ impl<'a, T: 'static> Context<'a, T> { /// The function is provided a weak handle to the entity owned by this context and a context that can be held across await points. /// The returned task must be held or detached. #[track_caller] + #[inline(always)] pub fn spawn(&self, f: AsyncFn) -> Task where T: 'static, @@ -250,13 +242,16 @@ impl<'a, T: 'static> Context<'a, T> { /// Many GPUI callbacks take the form of `Fn(&E, &mut Window, &mut App)`, /// but it's often useful to be able to access view state in these /// callbacks. This method provides a convenient way to do so. + #[inline(always)] pub fn listener( &self, - f: impl Fn(&mut T, &E, &mut Window, &mut Context) + 'static, + listener: impl Fn(&mut T, &E, &mut Window, &mut Context) + 'static, ) -> impl Fn(&E, &mut Window, &mut App) + 'static { let view = self.entity().downgrade(); - move |e: &E, window: &mut Window, cx: &mut App| { - view.update(cx, |view, cx| f(view, e, window, cx)).ok(); + move |event: &E, window: &mut Window, cx: &mut App| { + invoke_listener(&view, window, cx, &|view, window, cx| { + listener(view, event, window, cx); + }); } } @@ -336,17 +331,7 @@ impl<'a, T: 'static> Context<'a, T> { self.ensure_window(observer_id, window.handle.id); self.new_observer( observed_id, - Box::new(move |cx| { - let Some((observer, observed)) = observer.upgrade().zip(observed.upgrade()) else { - return false; - }; - cx.with_window(observer_id, |window, cx| { - observer.update(cx, |observer, cx| { - on_notify(observer, observed, window, cx); - }); - }); - true - }), + Box::new(move |cx| invoke_observer_in(&observer, &observed, cx, &mut on_notify)), ) } @@ -372,17 +357,7 @@ impl<'a, T: 'static> Context<'a, T> { ( TypeId::of::(), Box::new(move |event, cx| { - let Some((subscriber, emitter)) = subscriber.upgrade().zip(emitter.upgrade()) - else { - return false; - }; - let event = event.downcast_ref().expect("invalid event type"); - cx.with_window(subscriber_id, |window, cx| { - subscriber.update(cx, |subscriber, cx| { - on_event(subscriber, &emitter, event, window, cx); - }); - }); - true + invoke_subscriber_in(&subscriber, &emitter, event, cx, &mut on_event) }), ), ) @@ -693,6 +668,7 @@ impl<'a, T: 'static> Context<'a, T> { /// It's also given an [`AsyncWindowContext`], which can be used to access the state of the entity across await points. /// The returned future will be polled on the main thread. #[track_caller] + #[inline(always)] pub fn spawn_in(&self, window: &Window, f: AsyncFn) -> Task where R: 'static, @@ -707,6 +683,7 @@ impl<'a, T: 'static> Context<'a, T> { /// It's also given an [`AsyncWindowContext`], which can be used to access the state of the entity across await points. /// The returned future will be polled on the main thread. #[track_caller] + #[inline(always)] pub fn spawn_in_with_priority( &self, priority: Priority, @@ -901,3 +878,84 @@ impl BorrowMut for Context<'_, T> { self.app } } + +#[inline(never)] +fn invoke_observer( + observer: &WeakEntity, + observed: Entity, + cx: &mut App, + on_notify: &mut dyn FnMut(&mut T, Entity, &mut Context), +) -> bool { + if let Some(observer) = observer.upgrade() { + observer.update(cx, |observer, cx| on_notify(observer, observed, cx)); + true + } else { + false + } +} + +#[inline(never)] +fn invoke_subscriber( + subscriber: &WeakEntity, + emitter: Entity, + event: &Event, + cx: &mut App, + on_event: &mut dyn FnMut(&mut T, Entity, &Event, &mut Context), +) -> bool { + if let Some(subscriber) = subscriber.upgrade() { + subscriber.update(cx, |subscriber, cx| { + on_event(subscriber, emitter, event, cx) + }); + true + } else { + false + } +} + +#[inline(never)] +fn invoke_observer_in( + observer: &WeakEntity, + observed: &WeakEntity, + cx: &mut App, + on_notify: &mut dyn FnMut(&mut T, Entity, &mut Window, &mut Context), +) -> bool { + let Some((observer, observed)) = observer.upgrade().zip(observed.upgrade()) else { + return false; + }; + cx.with_window(observer.entity_id(), |window, cx| { + observer.update(cx, |observer, cx| { + on_notify(observer, observed, window, cx); + }); + }); + true +} + +#[inline(never)] +fn invoke_subscriber_in( + subscriber: &WeakEntity, + emitter: &WeakEntity, + event: &dyn Any, + cx: &mut App, + on_event: &mut dyn FnMut(&mut T, &Entity, &Event, &mut Window, &mut Context), +) -> bool { + let Some((subscriber, emitter)) = subscriber.upgrade().zip(emitter.upgrade()) else { + return false; + }; + let event = event.downcast_ref().expect("invalid event type"); + cx.with_window(subscriber.entity_id(), |window, cx| { + subscriber.update(cx, |subscriber, cx| { + on_event(subscriber, &emitter, event, window, cx); + }); + }); + true +} + +#[inline(never)] +fn invoke_listener( + view: &WeakEntity, + window: &mut Window, + cx: &mut App, + listener: &dyn Fn(&mut T, &mut Window, &mut Context), +) { + view.update(cx, |view, cx| listener(view, window, cx)).ok(); +} diff --git a/crates/gpui/src/app/entity_map.rs b/crates/gpui/src/app/entity_map.rs index e4e9f3b58a..4184503ba3 100644 --- a/crates/gpui/src/app/entity_map.rs +++ b/crates/gpui/src/app/entity_map.rs @@ -67,6 +67,10 @@ pub(crate) struct EntityRefCounts { leak_detector: LeakDetector, } +pub(super) struct LeaseInner { + pub(super) entity: Option>, +} + impl EntityMap { pub fn new() -> Self { Self { @@ -132,36 +136,38 @@ impl EntityMap { /// Move an entity to the stack. #[track_caller] pub fn lease(&mut self, pointer: &Entity) -> Lease { - self.assert_valid_context(pointer); - let mut accessed_entities = self.accessed_entities.get_mut(); - accessed_entities.insert(pointer.entity_id); - - let entity = Some( - self.entities - .remove(pointer.entity_id) - .unwrap_or_else(|| double_lease_panic::("update")), - ); Lease { - entity, + inner: self.lease_erased(pointer, type_name::()), id: pointer.entity_id, entity_type: PhantomData, } } /// Returns an entity after moving it to the stack. - pub fn end_lease(&mut self, mut lease: Lease) { - self.entities.insert(lease.id, lease.entity.take().unwrap()); + pub fn end_lease(&mut self, lease: Lease) { + self.end_lease_erased(lease.id, lease.inner); } + #[inline(always)] pub fn read(&self, entity: &Entity) -> &T { self.assert_valid_context(entity); - let mut accessed_entities = self.accessed_entities.borrow_mut(); - accessed_entities.insert(entity.entity_id); - - self.entities - .get(entity.entity_id) + self.read_inner(entity.entity_id) .and_then(|entity| entity.downcast_ref()) - .unwrap_or_else(|| double_lease_panic::("read")) + .unwrap_or_else(|| double_lease_panic("read", type_name::())) + } + + #[track_caller] + pub(super) fn lease_erased(&mut self, pointer: &AnyEntity, entity_type: &str) -> LeaseInner { + self.assert_valid_context(pointer); + let entity = Some( + self.lease_inner(pointer.entity_id) + .unwrap_or_else(|| double_lease_panic("update", entity_type)), + ); + LeaseInner { entity } + } + + pub(super) fn end_lease_erased(&mut self, entity_id: EntityId, mut lease: LeaseInner) { + self.end_lease_inner(entity_id, lease.entity.take().unwrap()); } fn assert_valid_context(&self, entity: &AnyEntity) { @@ -201,19 +207,34 @@ impl EntityMap { }) .collect() } + + #[inline(never)] + fn read_inner(&self, entity_id: EntityId) -> Option<&dyn Any> { + let mut accessed_entities = self.accessed_entities.borrow_mut(); + accessed_entities.insert(entity_id); + self.entities.get(entity_id).map(Box::as_ref) + } + + #[inline(never)] + fn lease_inner(&mut self, entity_id: EntityId) -> Option> { + self.accessed_entities.get_mut().insert(entity_id); + self.entities.remove(entity_id) + } + + #[inline(never)] + fn end_lease_inner(&mut self, entity_id: EntityId, entity: Box) { + self.entities.insert(entity_id, entity); + } } #[track_caller] -fn double_lease_panic(operation: &str) -> ! { - panic!( - "cannot {operation} {} while it is already being updated", - std::any::type_name::() - ) +fn double_lease_panic(operation: &str, entity_type: &str) -> ! { + panic!("cannot {operation} {entity_type} while it is already being updated") } pub(crate) struct Lease { - entity: Option>, pub id: EntityId, + inner: LeaseInner, entity_type: PhantomData, } @@ -221,17 +242,17 @@ impl core::ops::Deref for Lease { type Target = T; fn deref(&self) -> &Self::Target { - self.entity.as_ref().unwrap().downcast_ref().unwrap() + self.inner.entity.as_ref().unwrap().downcast_ref().unwrap() } } impl core::ops::DerefMut for Lease { fn deref_mut(&mut self) -> &mut Self::Target { - self.entity.as_mut().unwrap().downcast_mut().unwrap() + self.inner.entity.as_mut().unwrap().downcast_mut().unwrap() } } -impl Drop for Lease { +impl Drop for LeaseInner { fn drop(&mut self) { if self.entity.is_some() && !panicking() { panic!("Leases must be ended with EntityMap::end_lease") @@ -774,6 +795,7 @@ impl WeakEntity { /// Updates 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. + #[inline(always)] pub fn update( &self, cx: &mut C, @@ -789,6 +811,7 @@ impl WeakEntity { /// Updates the entity referenced by this handle with the given function if /// the referenced entity still exists, within a visual context that has a window. /// Returns an error if the entity has been released. + #[inline(always)] pub fn update_in( &self, cx: &mut C, @@ -807,6 +830,7 @@ impl WeakEntity { /// Reads 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. + #[inline(always)] pub fn read_with(&self, cx: &C, read: impl FnOnce(&T, &App) -> R) -> Result where C: AppContext, diff --git a/crates/gpui/src/arena.rs b/crates/gpui/src/arena.rs index 2ba7a2f605..ea5968f84e 100644 --- a/crates/gpui/src/arena.rs +++ b/crates/gpui/src/arena.rs @@ -172,29 +172,11 @@ impl Arena { } let layout = alloc::Layout::new::(); - let mut current_chunk = &mut self.chunks[self.current_chunk_index]; + let current_chunk = &mut self.chunks[self.current_chunk_index]; let ptr = if let Some(ptr) = current_chunk.allocate(layout) { ptr.as_ptr() } else { - self.current_chunk_index += 1; - if self.current_chunk_index >= self.chunks.len() { - self.chunks.push(Chunk::new(self.chunk_size)); - assert_eq!(self.current_chunk_index, self.chunks.len() - 1); - log::trace!( - "increased element arena capacity to {}kb", - self.capacity() / 1024, - ); - } - current_chunk = &mut self.chunks[self.current_chunk_index]; - if let Some(ptr) = current_chunk.allocate(layout) { - ptr.as_ptr() - } else { - panic!( - "Arena chunk_size of {} is too small to allocate {} bytes", - self.chunk_size, - layout.size() - ); - } + self.allocate_next_chunk(layout) }; unsafe { inner_writer(ptr.cast(), f) }; @@ -208,6 +190,29 @@ impl Arena { valid: self.valid.clone(), } } + + #[inline(never)] + fn allocate_next_chunk(&mut self, layout: alloc::Layout) -> *mut u8 { + self.current_chunk_index += 1; + if self.current_chunk_index >= self.chunks.len() { + self.chunks.push(Chunk::new(self.chunk_size)); + assert_eq!(self.current_chunk_index, self.chunks.len() - 1); + log::trace!( + "increased element arena capacity to {}kb", + self.capacity() / 1024, + ); + } + let current_chunk = &mut self.chunks[self.current_chunk_index]; + if let Some(pointer) = current_chunk.allocate(layout) { + pointer.as_ptr() + } else { + panic!( + "Arena chunk_size of {} is too small to allocate {} bytes", + self.chunk_size, + layout.size() + ); + } + } } pub struct ArenaBox { diff --git a/crates/gpui/src/element.rs b/crates/gpui/src/element.rs index b29b26a777..d6556258e1 100644 --- a/crates/gpui/src/element.rs +++ b/crates/gpui/src/element.rs @@ -31,6 +31,8 @@ //! elements when you need to take manual control of the layout and painting process, such as when using //! your own custom layout algorithm or rendering a code editor. +#[cfg(any(feature = "inspector", debug_assertions))] +use crate::InspectorElementPath; use crate::{ A11ySubtreeBuilder, App, ArenaBox, AvailableSpace, Bounds, Context, DispatchNodeId, ElementId, FocusHandle, InspectorElementId, LayoutId, Pixels, Point, Size, Style, Window, @@ -297,22 +299,18 @@ impl Drawable { fn request_layout(&mut self, window: &mut Window, cx: &mut App) -> LayoutId { match mem::take(&mut self.phase) { ElementDrawPhase::Start => { - let global_id = self.element.id().map(|element_id| { - window.element_id_stack.push(element_id); - GlobalElementId(Arc::from(&*window.element_id_stack)) - }); + let global_id = self + .element + .id() + .map(|element_id| prepare_element_id(element_id, window)); let inspector_id; #[cfg(any(feature = "inspector", debug_assertions))] { inspector_id = if window.inspector_enabled() { - self.element.source_location().map(|source| { - let path = crate::InspectorElementPath { - global_id: GlobalElementId(Arc::from(&*window.element_id_stack)), - source_location: source, - }; - window.build_inspector_element_id(path) - }) + self.element + .source_location() + .map(|source| prepare_inspector_id(source, window)) } else { None }; @@ -794,3 +792,22 @@ impl Element for Empty { ) { } } + +#[inline(never)] +fn prepare_element_id(element_id: ElementId, window: &mut Window) -> GlobalElementId { + window.element_id_stack.push(element_id); + GlobalElementId(Arc::from(&*window.element_id_stack)) +} + +#[cfg(any(feature = "inspector", debug_assertions))] +#[inline(never)] +fn prepare_inspector_id( + source: &'static panic::Location<'static>, + window: &mut Window, +) -> InspectorElementId { + let path = InspectorElementPath { + global_id: GlobalElementId(Arc::from(&*window.element_id_stack)), + source_location: source, + }; + window.build_inspector_element_id(path) +} diff --git a/crates/gpui/src/elements/div.rs b/crates/gpui/src/elements/div.rs index c730ef2722..c32ac995ec 100644 --- a/crates/gpui/src/elements/div.rs +++ b/crates/gpui/src/elements/div.rs @@ -454,6 +454,7 @@ impl Interactivity { /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. #[track_caller] + #[inline(always)] pub fn on_action(&mut self, listener: impl Fn(&A, &mut Window, &mut App) + 'static) { self.action_listeners.push(( TypeId::of::(), @@ -557,10 +558,7 @@ impl Interactivity { &mut self, listener: impl Fn(&ModifiersChangedEvent, &mut Window, &mut App) + 'static, ) { - self.modifiers_changed_listeners - .push(Box::new(move |event, window, cx| { - listener(event, window, cx) - })); + self.modifiers_changed_listeners.push(Box::new(listener)); } /// Bind the given callback to drop events of the given type, whether or not the drag started on this element. @@ -589,13 +587,12 @@ impl Interactivity { /// The imperative API equivalent to [`StatefulInteractiveElement::on_click`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. + #[inline(always)] pub fn on_click(&mut self, listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) where Self: Sized, { - self.click_listeners.push(Rc::new(move |event, window, cx| { - listener(event, window, cx) - })); + self.click_listeners.push(Rc::new(listener)); } /// Bind the given callback to non-primary click events of this element. @@ -606,10 +603,7 @@ impl Interactivity { where Self: Sized, { - self.aux_click_listeners - .push(Rc::new(move |event, window, cx| { - listener(event, window, cx) - })); + self.aux_click_listeners.push(Rc::new(listener)); } /// On drag initiation, this callback will be used to create a new view to render the dragged value for a @@ -871,6 +865,7 @@ pub trait InteractiveElement: Sized { /// The fluent API equivalent to [`Interactivity::on_mouse_down`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to the view state from this callback. + #[inline(always)] fn on_mouse_down( mut self, button: MouseButton, @@ -1101,6 +1096,7 @@ pub trait InteractiveElement: Sized { /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. #[track_caller] + #[inline(always)] fn on_action( mut self, listener: impl Fn(&A, &mut Window, &mut App) + 'static, @@ -1176,6 +1172,7 @@ pub trait InteractiveElement: Sized { /// The fluent API equivalent to [`Interactivity::on_modifiers_changed`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. + #[inline(always)] fn on_modifiers_changed( mut self, listener: impl Fn(&ModifiersChangedEvent, &mut Window, &mut App) + 'static, @@ -1496,6 +1493,7 @@ pub trait StatefulInteractiveElement: InteractiveElement { /// The handler is called when a screen reader requests the given action. /// /// See the [accessibility guide](crate::_accessibility) for an overview. + #[inline(always)] fn on_a11y_action( mut self, action: accesskit::Action, @@ -1582,6 +1580,7 @@ pub trait StatefulInteractiveElement: InteractiveElement { /// The fluent API equivalent to [`Interactivity::on_click`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. + #[inline(always)] fn on_click(mut self, listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self where Self: Sized, diff --git a/crates/gpui/src/view.rs b/crates/gpui/src/view.rs index 7f75c7d320..f24a4da9b7 100644 --- a/crates/gpui/src/view.rs +++ b/crates/gpui/src/view.rs @@ -1,14 +1,19 @@ use crate::{ - AnyElement, AnyEntity, AnyWeakEntity, App, Bounds, ContentMask, Context, Element, ElementId, - Entity, EntityId, GlobalElementId, InspectorElementId, IntoElement, LayoutId, PaintIndex, - Pixels, PrepaintStateIndex, Render, RenderOnce, Style, StyleRefinement, TextStyle, WeakEntity, + AnyElement, AnyEntity, AnyWeakEntity, App, AvailableSpace, Bounds, ContentMask, Context, + Element, ElementId, Entity, EntityId, GlobalElementId, InspectorElementId, IntoElement, + LayoutId, PaintIndex, Pixels, PrepaintStateIndex, Render, RenderOnce, Size, Style, + StyleRefinement, TextStyle, WeakEntity, }; use crate::{Empty, Window}; use anyhow::Result; use collections::FxHashSet; use refineable::Refineable; use std::mem; -use std::{any::TypeId, fmt, ops::Range}; +use std::{ + any::{TypeId, type_name}, + fmt, + ops::Range, +}; /// A dynamically-typed view handle that can be downcast to a specific `Entity`. /// @@ -98,6 +103,11 @@ impl IntoElement for Entity { fn into_element(self) -> Self::Element { ViewElement::new(self) } + + #[inline(never)] + fn into_any_element(self) -> AnyElement { + self.into_element().into_any() + } } impl IntoElement for AnyView { @@ -320,42 +330,23 @@ impl Element for ViewElement { ) -> (LayoutId, Self::RequestLayoutState) { if let Some(entity_id) = self.entity_id { // Stateful path: create a reactive boundary. - window.with_rendered_view(entity_id, |window| { - let caching_disabled = window.is_inspector_picking(cx); - match self.cached_style.as_ref() { - Some(style) if !caching_disabled => { - let mut root_style = Style::default(); - root_style.refine(style); - let layout_id = window.request_layout(root_style, None, cx); - (layout_id, None) - } - _ => { - let mut element = self - .view - .take() - .unwrap() - .render(window, cx) - .into_any_element(); - let layout_id = element.request_layout(window, cx); - (layout_id, Some(element)) - } - } - }) + let view = &mut self.view; + request_layout_view( + entity_id, + self.cached_style.as_ref(), + window, + cx, + &mut |window, cx| view.take().unwrap().render(window, cx).into_any_element(), + ) } else { // Stateless path: isolate subtree via type name (no entity identity). - window.with_id( - ElementId::Name(std::any::type_name::().into()), - |window| { - let mut element = self - .view - .take() - .unwrap() - .render(window, cx) - .into_any_element(); - let layout_id = element.request_layout(window, cx); - (layout_id, Some(element)) - }, - ) + request_layout_component(type_name::(), window, cx, &mut |window, cx| { + self.view + .take() + .unwrap() + .render(window, cx) + .into_any_element() + }) } } @@ -370,78 +361,24 @@ impl Element for ViewElement { ) -> Option { if let Some(entity_id) = self.entity_id { // Stateful path. - window.set_view_id(entity_id); - window.with_rendered_view(entity_id, |window| { - if let Some(mut element) = element.take() { - element.prepaint(window, cx); - return Some(element); - } - - window.with_element_state::( - global_id.unwrap(), - |element_state, window| { - let content_mask = window.content_mask(); - let text_style = window.text_style(); - - if let Some(mut element_state) = element_state - && element_state.cache_key.bounds == bounds - && element_state.cache_key.content_mask == content_mask - && element_state.cache_key.text_style == text_style - && !window.dirty_views.contains(&entity_id) - && !window.refreshing - { - let prepaint_start = window.prepaint_index(); - window.reuse_prepaint(element_state.prepaint_range.clone()); - cx.entities - .extend_accessed(&element_state.accessed_entities); - let prepaint_end = window.prepaint_index(); - element_state.prepaint_range = prepaint_start..prepaint_end; - - return (None, element_state); - } - - let refreshing = mem::replace(&mut window.refreshing, true); - let prepaint_start = window.prepaint_index(); - let (mut element, accessed_entities) = cx.detect_accessed_entities(|cx| { - let mut element = self - .view - .take() - .unwrap() - .render(window, cx) - .into_any_element(); - element.layout_as_root(bounds.size.into(), window, cx); - element.prepaint_at(bounds.origin, window, cx); - element - }); - - let prepaint_end = window.prepaint_index(); - window.refreshing = refreshing; - - ( - Some(element), - ViewElementState { - accessed_entities, - prepaint_range: prepaint_start..prepaint_end, - paint_range: PaintIndex::default()..PaintIndex::default(), - cache_key: ViewElementCacheKey { - bounds, - content_mask, - text_style, - }, - }, - ) - }, - ) - }) + prepaint_view( + entity_id, + global_id, + bounds, + element, + window, + cx, + &mut |window, cx| { + self.view + .take() + .unwrap() + .render(window, cx) + .into_any_element() + }, + ) } else { // Stateless path: just prepaint the element. - window.with_id( - ElementId::Name(std::any::type_name::().into()), - |window| { - element.as_mut().unwrap().prepaint(window, cx); - }, - ); - Some(element.take().unwrap()) + prepaint_component(type_name::(), element, window, cx) } } @@ -481,6 +418,129 @@ impl Render for EmptyView { } } +#[inline(never)] +fn request_layout_view( + entity_id: EntityId, + cached_style: Option<&StyleRefinement>, + window: &mut Window, + cx: &mut App, + render: &mut dyn FnMut(&mut Window, &mut App) -> AnyElement, +) -> (LayoutId, Option) { + window.with_rendered_view(entity_id, |window| { + let caching_disabled = window.is_inspector_picking(cx); + match cached_style { + Some(style) if !caching_disabled => { + let mut root_style = Style::default(); + root_style.refine(style); + let layout_id = window.request_layout(root_style, None, cx); + (layout_id, None) + } + _ => { + let mut element = render(window, cx); + let layout_id = element.request_layout(window, cx); + (layout_id, Some(element)) + } + } + }) +} + +#[inline(never)] +fn request_layout_component( + name: &'static str, + window: &mut Window, + cx: &mut App, + render: &mut dyn FnMut(&mut Window, &mut App) -> AnyElement, +) -> (LayoutId, Option) { + window.with_id(ElementId::from(name), |window| { + let mut element = render(window, cx); + let layout_id = element.request_layout(window, cx); + (layout_id, Some(element)) + }) +} + +#[inline(never)] +fn prepaint_view( + entity_id: EntityId, + global_id: Option<&GlobalElementId>, + bounds: Bounds, + element: &mut Option, + window: &mut Window, + cx: &mut App, + render: &mut dyn FnMut(&mut Window, &mut App) -> AnyElement, +) -> Option { + window.set_view_id(entity_id); + window.with_rendered_view(entity_id, |window| { + if let Some(mut element) = element.take() { + element.prepaint(window, cx); + return Some(element); + } + + window.with_element_state::( + global_id.unwrap(), + |element_state, window| { + let content_mask = window.content_mask(); + let text_style = window.text_style(); + + if let Some(mut element_state) = element_state + && element_state.cache_key.bounds == bounds + && element_state.cache_key.content_mask == content_mask + && element_state.cache_key.text_style == text_style + && !window.dirty_views.contains(&entity_id) + && !window.refreshing + { + let prepaint_start = window.prepaint_index(); + window.reuse_prepaint(element_state.prepaint_range.clone()); + cx.entities + .extend_accessed(&element_state.accessed_entities); + let prepaint_end = window.prepaint_index(); + element_state.prepaint_range = prepaint_start..prepaint_end; + + return (None, element_state); + } + + let refreshing = mem::replace(&mut window.refreshing, true); + let prepaint_start = window.prepaint_index(); + let (element, accessed_entities) = cx.detect_accessed_entities(|cx| { + let mut element = render(window, cx); + element.layout_as_root(Size::::from(bounds.size), window, cx); + element.prepaint_at(bounds.origin, window, cx); + element + }); + + let prepaint_end = window.prepaint_index(); + window.refreshing = refreshing; + + ( + Some(element), + ViewElementState { + accessed_entities, + prepaint_range: prepaint_start..prepaint_end, + paint_range: PaintIndex::default()..PaintIndex::default(), + cache_key: ViewElementCacheKey { + bounds, + content_mask, + text_style, + }, + }, + ) + }, + ) + }) +} + +#[inline(never)] +fn prepaint_component( + name: &'static str, + element: &mut Option, + window: &mut Window, + cx: &mut App, +) -> Option { + window.with_id(ElementId::from(name), |window| { + element.as_mut().unwrap().prepaint(window, cx); + }); + Some(element.take().unwrap()) +} + #[inline(never)] fn paint_view( entity_id: EntityId, diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 45fabc3c94..636cb6da21 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -58,6 +58,7 @@ use std::{ Arc, Weak, atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst}, }, + thread::AccessError, time::Duration, }; use uuid::Uuid; @@ -354,17 +355,16 @@ fn draw_in_progress() -> bool { /// Allocates an element in the current arena. Uses the app-specific arena if one /// is active (during draw), otherwise falls back to the thread-local ELEMENT_ARENA. -pub(crate) fn with_element_arena(f: impl FnOnce(&mut Arena) -> R) -> R { - CURRENT_ELEMENT_ARENA.with(|current| { - if let Some(arena_ptr) = current.get() { - // SAFETY: The pointer is valid for the duration of the draw operation - // that set it, and we're being called during that same draw. - let arena_cell = unsafe { &*arena_ptr }; - f(&mut arena_cell.borrow_mut()) - } else { - ELEMENT_ARENA.with_borrow_mut(f) - } - }) +#[inline(always)] +pub(crate) fn with_element_arena(callback: impl FnOnce(&mut Arena) -> R) -> R { + let mut callback = Some(callback); + let mut result = None; + let access = with_element_arena_erased(&mut |arena| { + result = Some(callback.take().expect("arena callback runs once")(arena)); + }); + drop(callback); + access.expect("cannot access a Thread Local Storage value during or after destruction"); + result.expect("arena callback produces a result") } /// Scope guard that sets CURRENT_ELEMENT_ARENA for the duration of a draw @@ -4174,10 +4174,7 @@ impl Window { (state.clone(), state) } else { let new_state = cx.new(|cx| init(window, cx)); - cx.observe(&new_state, move |_, cx| { - cx.notify(current_view); - }) - .detach(); + Self::observe_keyed_state(&new_state, current_view, cx); (new_state.clone(), new_state) } }) @@ -4206,6 +4203,7 @@ impl Window { /// frames. If an element with this ID existed in the rendered frame, its state will be passed /// to the given closure. The state returned by the closure will be stored so it can be referenced /// when drawing the next frame. This method should only be called as part of element drawing. + #[inline(always)] pub fn with_element_state( &mut self, global_id: &GlobalElementId, @@ -4216,15 +4214,9 @@ impl Window { { self.invalidator.debug_assert_paint_or_prepaint(); - let key = (global_id.clone(), TypeId::of::()); - self.next_frame.accessed_element_states.push(key.clone()); + let (key, state) = self.take_element_state(global_id, TypeId::of::()); - if let Some(any) = self - .next_frame - .element_states - .remove(&key) - .or_else(|| self.rendered_frame.element_states.remove(&key)) - { + if let Some(any) = state { let ElementStateBox { inner, #[cfg(debug_assertions)] @@ -4258,7 +4250,7 @@ impl Window { ); let (result, state) = f(Some(state), self); state_box.replace(state); - self.next_frame.element_states.insert( + self.insert_element_state( key, ElementStateBox { inner: state_box, @@ -4269,7 +4261,7 @@ impl Window { result } else { let (result, state) = f(None, self); - self.next_frame.element_states.insert( + self.insert_element_state( key, ElementStateBox { inner: Box::new(Some(state)), @@ -7088,6 +7080,39 @@ impl Window { }); let _ = self.dispatch_event(event, cx); } + + #[inline(never)] + fn take_element_state( + &mut self, + global_id: &GlobalElementId, + state_type: TypeId, + ) -> ((GlobalElementId, TypeId), Option) { + let key = (global_id.clone(), state_type); + self.next_frame.accessed_element_states.push(key.clone()); + let state = self + .next_frame + .element_states + .remove(&key) + .or_else(|| self.rendered_frame.element_states.remove(&key)); + (key, state) + } + + #[inline(never)] + fn insert_element_state( + &mut self, + key: (GlobalElementId, TypeId), + state: ElementStateBox, + ) -> Option { + self.next_frame.element_states.insert(key, state) + } + + #[inline(never)] + fn observe_keyed_state(state: &Entity, current_view: EntityId, cx: &mut App) { + cx.observe(state, move |_, cx| { + cx.notify(current_view); + }) + .detach(); + } } // #[derive(Clone, Copy, Eq, PartialEq, Hash)] @@ -7596,6 +7621,19 @@ pub fn outline( } } +#[inline(never)] +fn with_element_arena_erased(callback: &mut dyn FnMut(&mut Arena)) -> Result<(), AccessError> { + if let Some(arena_pointer) = CURRENT_ELEMENT_ARENA.try_with(Cell::get)? { + // SAFETY: The pointer is valid for the duration of the draw operation + // that set it, and we're being called during that same draw. + let arena_cell = unsafe { &*arena_pointer }; + callback(&mut arena_cell.borrow_mut()); + Ok(()) + } else { + ELEMENT_ARENA.try_with(|arena| callback(&mut arena.borrow_mut())) + } +} + #[cfg(test)] mod tests { use std::{ diff --git a/crates/scheduler/src/executor.rs b/crates/scheduler/src/executor.rs index bacf9fbda9..ad2ea929dc 100644 --- a/crates/scheduler/src/executor.rs +++ b/crates/scheduler/src/executor.rs @@ -65,11 +65,11 @@ impl LocalExecutor { F: Future + 'static, F::Output: 'static, { - let dispatch = self.dispatch.clone(); + let schedule = self.schedule(); let location = Location::caller(); let (runnable, task) = spawn_local_with_source_location( future, - move |runnable| dispatch(runnable), + schedule, RunnableMeta { location, spawned: crate::SpawnTime(Instant::now()), @@ -181,6 +181,11 @@ impl LocalExecutor { .spawn_dedicated(box_dedicated(f)) .downcast::() } + + fn schedule(&self) -> impl Fn(Runnable) + Send + Sync + 'static { + let dispatch = self.dispatch.clone(); + move |runnable| dispatch(runnable) + } } /// Boxes the user-supplied dedicated closure into the type-erased shape @@ -229,21 +234,14 @@ impl BackgroundExecutor { F: Future + Send + 'static, F::Output: Send + 'static, { - let scheduler = Arc::downgrade(&self.scheduler); + let schedule = self.schedule_with_priority(priority); let location = Location::caller(); let (runnable, task) = async_task::Builder::new() .metadata(RunnableMeta { location, spawned: crate::SpawnTime(Instant::now()), }) - .spawn( - move |_| future, - move |runnable| { - if let Some(scheduler) = scheduler.upgrade() { - scheduler.schedule_background_with_priority(runnable, priority); - } - }, - ); + .spawn(move |_| future, schedule); runnable.schedule(); Task(TaskState::Spawned(task)) } @@ -312,6 +310,18 @@ impl BackgroundExecutor { .spawn_dedicated(box_dedicated(f)) .downcast::() } + + fn schedule_with_priority( + &self, + priority: Priority, + ) -> impl Fn(Runnable) + Send + Sync + 'static { + let scheduler = Arc::downgrade(&self.scheduler); + move |runnable| { + if let Some(scheduler) = scheduler.upgrade() { + scheduler.schedule_background_with_priority(runnable, priority); + } + } + } } /// A long-lived handle to one dedicated session: every future spawned on it