diff --git a/crates/gpui2/src/arena.rs b/crates/gpui2/src/arena.rs new file mode 100644 index 0000000000..c99f09d29f --- /dev/null +++ b/crates/gpui2/src/arena.rs @@ -0,0 +1,178 @@ +use std::{ + alloc, + cell::Cell, + ops::{Deref, DerefMut}, + ptr::{self, NonNull}, + rc::Rc, +}; + +struct ArenaElement { + value: NonNull, + drop: unsafe fn(NonNull), +} + +impl Drop for ArenaElement { + #[inline(always)] + fn drop(&mut self) { + unsafe { + (self.drop)(self.value); + } + } +} + +pub struct Arena { + start: NonNull, + offset: usize, + elements: Vec, + valid: Rc>, +} + +impl Arena { + pub fn new(size_in_bytes: usize) -> Self { + unsafe { + let layout = alloc::Layout::from_size_align(size_in_bytes, 1).unwrap(); + let ptr = alloc::alloc(layout); + Self { + start: NonNull::new_unchecked(ptr), + offset: 0, + elements: Vec::new(), + valid: Rc::new(Cell::new(true)), + } + } + } + + pub fn clear(&mut self) { + self.valid.set(false); + self.valid = Rc::new(Cell::new(true)); + self.elements.clear(); + self.offset = 0; + } + + #[inline(always)] + pub fn alloc(&mut self, f: impl FnOnce() -> T) -> ArenaRef { + #[inline(always)] + unsafe fn inner_writer(ptr: *mut T, f: F) + where + F: FnOnce() -> T, + { + ptr::write(ptr, f()); + } + + unsafe fn drop(ptr: NonNull) { + std::ptr::drop_in_place(ptr.cast::().as_ptr()); + } + + unsafe { + let layout = alloc::Layout::new::().pad_to_align(); + let ptr = NonNull::new_unchecked(self.start.as_ptr().add(self.offset).cast::()); + inner_writer(ptr.as_ptr(), f); + + self.elements.push(ArenaElement { + value: ptr.cast(), + drop: drop::, + }); + self.offset += layout.size(); + ArenaRef { + ptr, + valid: self.valid.clone(), + } + } + } +} + +impl Drop for Arena { + fn drop(&mut self) { + self.clear(); + } +} + +pub struct ArenaRef { + ptr: NonNull, + valid: Rc>, +} + +impl Clone for ArenaRef { + fn clone(&self) -> Self { + Self { + ptr: self.ptr, + valid: self.valid.clone(), + } + } +} + +impl ArenaRef { + #[inline(always)] + pub fn map(mut self, f: impl FnOnce(&mut T) -> &mut U) -> ArenaRef { + ArenaRef { + ptr: unsafe { NonNull::new_unchecked(f(&mut *self)) }, + valid: self.valid, + } + } + + fn validate(&self) { + assert!( + self.valid.get(), + "attempted to dereference an ArenaRef after its Arena was cleared" + ); + } +} + +impl Deref for ArenaRef { + type Target = T; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + self.validate(); + unsafe { self.ptr.as_ref() } + } +} + +impl DerefMut for ArenaRef { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + self.validate(); + unsafe { self.ptr.as_mut() } + } +} + +#[cfg(test)] +mod tests { + use std::{cell::Cell, rc::Rc}; + + use super::*; + + #[test] + fn test_arena() { + let mut arena = Arena::new(1024); + let a = arena.alloc(|| 1u64); + let b = arena.alloc(|| 2u32); + let c = arena.alloc(|| 3u16); + let d = arena.alloc(|| 4u8); + assert_eq!(*a, 1); + assert_eq!(*b, 2); + assert_eq!(*c, 3); + assert_eq!(*d, 4); + + arena.clear(); + let a = arena.alloc(|| 5u64); + let b = arena.alloc(|| 6u32); + let c = arena.alloc(|| 7u16); + let d = arena.alloc(|| 8u8); + assert_eq!(*a, 5); + assert_eq!(*b, 6); + assert_eq!(*c, 7); + assert_eq!(*d, 8); + + // Ensure drop gets called. + let dropped = Rc::new(Cell::new(false)); + struct DropGuard(Rc>); + impl Drop for DropGuard { + fn drop(&mut self) { + self.0.set(true); + } + } + arena.alloc(|| DropGuard(dropped.clone())); + arena.clear(); + assert!(dropped.get()); + } +} diff --git a/crates/gpui2/src/element.rs b/crates/gpui2/src/element.rs index b446c2fe86..6f739d8e33 100644 --- a/crates/gpui2/src/element.rs +++ b/crates/gpui2/src/element.rs @@ -1,6 +1,6 @@ use crate::{ - AvailableSpace, BorrowWindow, Bounds, ElementId, LayoutId, Pixels, Point, Size, ViewContext, - WindowContext, + frame_alloc, ArenaRef, AvailableSpace, BorrowWindow, Bounds, ElementId, LayoutId, Pixels, + Point, Size, ViewContext, WindowContext, }; use derive_more::{Deref, DerefMut}; pub(crate) use smallvec::SmallVec; @@ -405,7 +405,7 @@ where } } -pub struct AnyElement(Box); +pub struct AnyElement(ArenaRef); impl AnyElement { pub fn new(element: E) -> Self @@ -413,7 +413,9 @@ impl AnyElement { E: 'static + Element, E::State: Any, { - AnyElement(Box::new(Some(DrawableElement::new(element))) as Box) + let element = frame_alloc(|| Some(DrawableElement::new(element))) + .map(|element| element as &mut dyn ElementObject); + AnyElement(element) } pub fn layout(&mut self, cx: &mut WindowContext) -> LayoutId { @@ -443,11 +445,6 @@ impl AnyElement { self.0.draw(origin, available_space, cx) } - /// Converts this `AnyElement` into a trait object that can be stored and manipulated. - pub fn into_any(self) -> AnyElement { - AnyElement::new(self) - } - pub fn inner_id(&self) -> Option { self.0.element_id() } @@ -480,6 +477,10 @@ impl IntoElement for AnyElement { fn into_element(self) -> Self::Element { self } + + fn into_any_element(self) -> AnyElement { + self + } } /// The empty element, which renders nothing. diff --git a/crates/gpui2/src/gpui2.rs b/crates/gpui2/src/gpui2.rs index c711d1f5a2..6af274ef5d 100644 --- a/crates/gpui2/src/gpui2.rs +++ b/crates/gpui2/src/gpui2.rs @@ -2,6 +2,7 @@ mod action; mod app; +mod arena; mod assets; mod color; mod element; @@ -38,6 +39,7 @@ mod private { pub use action::*; pub use anyhow::Result; pub use app::*; +pub(crate) use arena::*; pub use assets::*; pub use color::*; pub use ctor::ctor; diff --git a/crates/gpui2/src/key_dispatch.rs b/crates/gpui2/src/key_dispatch.rs index ddb1f1e6ca..a9d717ea1a 100644 --- a/crates/gpui2/src/key_dispatch.rs +++ b/crates/gpui2/src/key_dispatch.rs @@ -1,6 +1,6 @@ use crate::{ - Action, ActionRegistry, DispatchPhase, FocusId, KeyBinding, KeyContext, KeyMatch, Keymap, - Keystroke, KeystrokeMatcher, WindowContext, + arena::ArenaRef, Action, ActionRegistry, DispatchPhase, FocusId, KeyBinding, KeyContext, + KeyMatch, Keymap, Keystroke, KeystrokeMatcher, WindowContext, }; use collections::HashMap; use parking_lot::Mutex; @@ -33,12 +33,12 @@ pub(crate) struct DispatchNode { parent: Option, } -type KeyListener = Rc; +type KeyListener = ArenaRef; #[derive(Clone)] pub(crate) struct DispatchActionListener { pub(crate) action_type: TypeId, - pub(crate) listener: Rc, + pub(crate) listener: ArenaRef, } impl DispatchTree { @@ -117,7 +117,7 @@ impl DispatchTree { pub fn on_action( &mut self, action_type: TypeId, - listener: Rc, + listener: ArenaRef, ) { self.active_node() .action_listeners diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 74c7204048..337f61272f 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1,15 +1,17 @@ use crate::{ - key_dispatch::DispatchActionListener, px, size, transparent_black, Action, AnyDrag, AnyView, - AppContext, AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, - CursorStyle, DevicePixels, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, - EntityId, EventEmitter, FileDropEvent, Flatten, FontId, GlobalElementId, GlyphId, Hsla, - ImageData, InputEvent, IsZero, KeyBinding, KeyContext, KeyDownEvent, KeystrokeEvent, LayoutId, - Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseMoveEvent, MouseUpEvent, - Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, - PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams, - RenderSvgParams, ScaledPixels, Scene, SceneBuilder, Shadow, SharedString, Size, Style, - SubscriberSet, Subscription, Surface, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, - VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, + arena::{Arena, ArenaRef}, + key_dispatch::DispatchActionListener, + px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, AsyncWindowContext, + AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle, DevicePixels, DispatchNodeId, + DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, Flatten, + FontId, GlobalElementId, GlyphId, Hsla, ImageData, InputEvent, IsZero, KeyBinding, KeyContext, + KeyDownEvent, KeystrokeEvent, LayoutId, Model, ModelContext, Modifiers, MonochromeSprite, + MouseButton, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, + PlatformInputHandler, PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render, + RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, Scene, SceneBuilder, + Shadow, SharedString, Size, Style, SubscriberSet, Subscription, Surface, TaffyLayoutEngine, + Task, Underline, UnderlineStyle, View, VisualContext, WeakView, WindowBounds, WindowOptions, + SUBPIXEL_VARIANTS, }; use anyhow::{anyhow, Context as _, Result}; use collections::FxHashMap; @@ -25,6 +27,7 @@ use smallvec::SmallVec; use std::{ any::{Any, TypeId}, borrow::{Borrow, BorrowMut, Cow}, + cell::RefCell, fmt::Debug, future::Future, hash::{Hash, Hasher}, @@ -85,7 +88,7 @@ impl DispatchPhase { } type AnyObserver = Box bool + 'static>; -type AnyMouseListener = Box; +type AnyMouseListener = ArenaRef; type AnyWindowFocusListener = Box bool + 'static>; struct FocusEvent { @@ -95,6 +98,15 @@ struct FocusEvent { slotmap::new_key_type! { pub struct FocusId; } +thread_local! { + pub static FRAME_ARENA: RefCell = RefCell::new(Arena::new(16 * 1024 * 1024)); +} + +#[inline(always)] +pub(crate) fn frame_alloc(f: impl FnOnce() -> T) -> ArenaRef { + FRAME_ARENA.with_borrow_mut(|arena| arena.alloc(f)) +} + impl FocusId { /// Obtains whether the element associated with this handle is currently focused. pub fn is_focused(&self, cx: &WindowContext) -> bool { @@ -268,7 +280,6 @@ pub(crate) struct ElementStateBox { type_name: &'static str, } -// #[derive(Default)] pub(crate) struct Frame { focus: Option, pub(crate) element_states: FxHashMap, @@ -818,25 +829,23 @@ impl<'a> WindowContext<'a> { /// Register a mouse event listener on the window for the next frame. The type of event /// is determined by the first parameter of the given listener. When the next frame is rendered /// the listener will be cleared. - /// - /// This is a fairly low-level method, so prefer using event handlers on elements unless you have - /// a specific need to register a global listener. pub fn on_mouse_event( &mut self, mut handler: impl FnMut(&Event, DispatchPhase, &mut WindowContext) + 'static, ) { let order = self.window.next_frame.z_index_stack.clone(); + let handler = frame_alloc(|| { + move |event: &dyn Any, phase: DispatchPhase, cx: &mut WindowContext<'_>| { + handler(event.downcast_ref().unwrap(), phase, cx) + } + }) + .map(|handler| handler as _); self.window .next_frame .mouse_listeners .entry(TypeId::of::()) .or_default() - .push(( - order, - Box::new(move |event: &dyn Any, phase, cx| { - handler(event.downcast_ref().unwrap(), phase, cx) - }), - )) + .push((order, handler)) } /// Register a key event listener on the window for the next frame. The type of event @@ -847,16 +856,17 @@ impl<'a> WindowContext<'a> { /// a specific need to register a global listener. pub fn on_key_event( &mut self, - handler: impl Fn(&Event, DispatchPhase, &mut WindowContext) + 'static, + listener: impl Fn(&Event, DispatchPhase, &mut WindowContext) + 'static, ) { - self.window - .next_frame - .dispatch_tree - .on_key_event(Rc::new(move |event, phase, cx| { + let listener = frame_alloc(|| { + move |event: &dyn Any, phase, cx: &mut WindowContext<'_>| { if let Some(event) = event.downcast_ref::() { - handler(event, phase, cx) + listener(event, phase, cx) } - })); + } + }) + .map(|handler| handler as _); + self.window.next_frame.dispatch_tree.on_key_event(listener); } /// Register an action listener on the window for the next frame. The type of action @@ -868,12 +878,13 @@ impl<'a> WindowContext<'a> { pub fn on_action( &mut self, action_type: TypeId, - handler: impl Fn(&dyn Any, DispatchPhase, &mut WindowContext) + 'static, + listener: impl Fn(&dyn Any, DispatchPhase, &mut WindowContext) + 'static, ) { - self.window.next_frame.dispatch_tree.on_action( - action_type, - Rc::new(move |action, phase, cx| handler(action, phase, cx)), - ); + let listener = frame_alloc(|| listener).map(|handler| handler as _); + self.window + .next_frame + .dispatch_tree + .on_action(action_type, listener); } pub fn is_action_available(&self, action: &dyn Action) -> bool { @@ -1267,16 +1278,23 @@ impl<'a> WindowContext<'a> { self.window.platform_window.clear_input_handler(); self.window.layout_engine.as_mut().unwrap().clear(); self.window.next_frame.clear(); + FRAME_ARENA.with_borrow_mut(|arena| arena.clear()); let root_view = self.window.root_view.take().unwrap(); self.with_z_index(0, |cx| { cx.with_key_dispatch(Some(KeyContext::default()), None, |_, cx| { for (action_type, action_listeners) in &cx.app.global_action_listeners { for action_listener in action_listeners.iter().cloned() { - cx.window.next_frame.dispatch_tree.on_action( - *action_type, - Rc::new(move |action, phase, cx| action_listener(action, phase, cx)), - ) + let listener = frame_alloc(|| { + move |action: &dyn Any, phase, cx: &mut WindowContext<'_>| { + action_listener(action, phase, cx) + } + }) + .map(|listener| listener as _); + cx.window + .next_frame + .dispatch_tree + .on_action(*action_type, listener) } } @@ -2591,13 +2609,13 @@ impl<'a, V: 'static> ViewContext<'a, V> { pub fn on_action( &mut self, action_type: TypeId, - handler: impl Fn(&mut V, &dyn Any, DispatchPhase, &mut ViewContext) + 'static, + listener: impl Fn(&mut V, &dyn Any, DispatchPhase, &mut ViewContext) + 'static, ) { let handle = self.view().clone(); self.window_cx .on_action(action_type, move |action, phase, cx| { handle.update(cx, |view, cx| { - handler(view, action, phase, cx); + listener(view, action, phase, cx); }) }); }