From a1e080d4950dfef7f5b7bec5e9a87928caa93112 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 25 Sep 2023 11:47:37 -0600 Subject: [PATCH] Checkpoint --- crates/gpui3/src/app.rs | 148 +++++++++++++++++++++--- crates/gpui3/src/elements/stateless.rs | 2 +- crates/gpui3/src/gpui3.rs | 6 +- crates/gpui3/src/platform.rs | 3 +- crates/gpui3/src/platform/mac/window.rs | 60 +++------- crates/gpui3/src/view.rs | 6 +- crates/gpui3/src/window.rs | 70 ++++++++--- 7 files changed, 212 insertions(+), 83 deletions(-) diff --git a/crates/gpui3/src/app.rs b/crates/gpui3/src/app.rs index 377e9701d03f7b09b1417b8117771b71a27f6f2f..28bc0b636e841da5e9be3eac39089421c498f737 100644 --- a/crates/gpui3/src/app.rs +++ b/crates/gpui3/src/app.rs @@ -1,11 +1,13 @@ use crate::{ - current_platform, Context, LayoutId, MainThreadOnly, Platform, Reference, RootView, TextSystem, - Window, WindowContext, WindowHandle, WindowId, + current_platform, AnyWindowHandle, Context, LayoutId, MainThreadOnly, Platform, Reference, + RootView, TextSystem, Window, WindowContext, WindowHandle, WindowId, }; use anyhow::{anyhow, Result}; +use collections::{HashMap, VecDeque}; use futures::{future, Future}; use parking_lot::Mutex; use slotmap::SlotMap; +use smallvec::SmallVec; use std::{ any::Any, marker::PhantomData, @@ -38,6 +40,9 @@ impl App { unit_entity_id, entities, windows: SlotMap::with_key(), + pending_updates: 0, + pending_effects: Default::default(), + observers: Default::default(), layout_id_buffer: Default::default(), }) })) @@ -56,6 +61,8 @@ impl App { } } +type Handlers = SmallVec<[Arc bool + Send + Sync + 'static>; 2]>; + pub struct AppContext { this: Weak>, platform: MainThreadOnly, @@ -63,6 +70,9 @@ pub struct AppContext { pub(crate) unit_entity_id: EntityId, pub(crate) entities: SlotMap>>, pub(crate) windows: SlotMap>, + pending_updates: usize, + pub(crate) pending_effects: VecDeque, + pub(crate) observers: HashMap, // We recycle this memory across layout requests. pub(crate) layout_id_buffer: Vec, } @@ -105,31 +115,60 @@ impl AppContext { pub(crate) fn update_window( &mut self, - window_id: WindowId, + handle: AnyWindowHandle, update: impl FnOnce(&mut WindowContext) -> R, ) -> Result { let mut window = self .windows - .get_mut(window_id) + .get_mut(handle.id) .ok_or_else(|| anyhow!("window not found"))? .take() .unwrap(); let result = update(&mut WindowContext::mutable(self, &mut window)); + window.dirty = true; self.windows - .get_mut(window_id) + .get_mut(handle.id) .ok_or_else(|| anyhow!("window not found"))? .replace(window); Ok(result) } + + fn update(&mut self, update: impl FnOnce(&mut Self) -> R) -> R { + self.pending_updates += 1; + let result = update(self); + self.pending_updates -= 1; + if self.pending_updates == 0 { + self.flush_effects(); + } + result + } + + fn flush_effects(&mut self) { + while let Some(effect) = self.pending_effects.pop_front() { + match effect { + Effect::Notify(entity_id) => self.apply_notify_effect(entity_id), + } + } + } + + fn apply_notify_effect(&mut self, updated_entity: EntityId) { + if let Some(mut handlers) = self.observers.remove(&updated_entity) { + handlers.retain(|handler| handler(self)); + if let Some(new_handlers) = self.observers.remove(&updated_entity) { + handlers.extend(new_handlers); + } + self.observers.insert(updated_entity, handlers); + } + } } impl Context for AppContext { - type EntityContext<'a, 'w, T: Send + 'static> = ModelContext<'a, T>; + type EntityContext<'a, 'w, T: Send + Sync + 'static> = ModelContext<'a, T>; - fn entity( + fn entity( &mut self, build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T, ) -> Handle { @@ -140,7 +179,7 @@ impl Context for AppContext { Handle::new(id) } - fn update_entity( + fn update_entity( &mut self, handle: &Handle, update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R, @@ -166,7 +205,7 @@ pub struct ModelContext<'a, T> { entity_id: EntityId, } -impl<'a, T: 'static> ModelContext<'a, T> { +impl<'a, T: Send + Sync + 'static> ModelContext<'a, T> { pub(crate) fn mutable(app: &'a mut AppContext, entity_id: EntityId) -> Self { Self { app: Reference::Mutable(app), @@ -199,19 +238,53 @@ impl<'a, T: 'static> ModelContext<'a, T> { .replace(entity); result } + + pub fn handle(&self) -> WeakHandle { + WeakHandle { + id: self.entity_id, + entity_type: PhantomData, + } + } + + pub fn observe( + &mut self, + handle: &Handle, + on_notify: impl Fn(&mut T, Handle, &mut ModelContext<'_, T>) + Send + Sync + 'static, + ) { + let this = self.handle(); + let handle = handle.downgrade(); + self.app + .observers + .entry(handle.id) + .or_default() + .push(Arc::new(move |cx| { + if let Some((this, handle)) = this.upgrade(cx).zip(handle.upgrade(cx)) { + this.update(cx, |this, cx| on_notify(this, handle, cx)); + true + } else { + false + } + })); + } + + pub fn notify(&mut self) { + self.app + .pending_effects + .push_back(Effect::Notify(self.entity_id)); + } } impl<'a, T: 'static> Context for ModelContext<'a, T> { - type EntityContext<'b, 'c, U: Send + 'static> = ModelContext<'b, U>; + type EntityContext<'b, 'c, U: Send + Sync + 'static> = ModelContext<'b, U>; - fn entity( + fn entity( &mut self, build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, U>) -> U, ) -> Handle { self.app.entity(build_entity) } - fn update_entity( + fn update_entity( &mut self, handle: &Handle, update: impl FnOnce(&mut U, &mut Self::EntityContext<'_, '_, U>) -> R, @@ -220,14 +293,14 @@ impl<'a, T: 'static> Context for ModelContext<'a, T> { } } +slotmap::new_key_type! { pub struct EntityId; } + pub struct Handle { pub(crate) id: EntityId, pub(crate) entity_type: PhantomData, } -slotmap::new_key_type! { pub struct EntityId; } - -impl Handle { +impl Handle { fn new(id: EntityId) -> Self { Self { id, @@ -235,6 +308,13 @@ impl Handle { } } + 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. @@ -258,6 +338,44 @@ impl Clone for Handle { } } +pub struct WeakHandle { + pub(crate) id: EntityId, + pub(crate) entity_type: PhantomData, +} + +impl WeakHandle { + pub fn upgrade(&self, cx: &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 { + if let Some(this) = self.upgrade(cx) { + Ok(cx.update_entity(&this, update)) + } else { + Err(anyhow!("entity released")) + } + } +} + +pub(crate) enum Effect { + Notify(EntityId), +} + #[cfg(test)] mod tests { use super::AppContext; diff --git a/crates/gpui3/src/elements/stateless.rs b/crates/gpui3/src/elements/stateless.rs index 3d1110af74648432e16765659bab564f65370938..c4850d28cdccbccb082fe0d3c1adf5a5a7a8b150 100644 --- a/crates/gpui3/src/elements/stateless.rs +++ b/crates/gpui3/src/elements/stateless.rs @@ -7,7 +7,7 @@ pub struct Stateless, S> { parent_state_type: PhantomData, } -impl, S: 'static> Element for Stateless { +impl, S: Send + Sync + 'static> Element for Stateless { type State = S; type FrameState = E::FrameState; diff --git a/crates/gpui3/src/gpui3.rs b/crates/gpui3/src/gpui3.rs index f95187c2d9336dfd9a407344fc1df1dabc2a407c..537c5490a5f42d4350d252de0fd49912ee52aa38 100644 --- a/crates/gpui3/src/gpui3.rs +++ b/crates/gpui3/src/gpui3.rs @@ -46,14 +46,14 @@ pub use view::*; pub use window::*; pub trait Context { - type EntityContext<'a, 'w, T: Send + 'static>; + type EntityContext<'a, 'w, T: Send + Sync + 'static>; - fn entity( + fn entity( &mut self, build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T, ) -> Handle; - fn update_entity( + fn update_entity( &mut self, handle: &Handle, update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R, diff --git a/crates/gpui3/src/platform.rs b/crates/gpui3/src/platform.rs index 9c6e087323dff879e18988ec9bf1a35f664d2d8f..37ce0458160432d6764f119d029b6bceaad9053c 100644 --- a/crates/gpui3/src/platform.rs +++ b/crates/gpui3/src/platform.rs @@ -7,7 +7,7 @@ mod test; use crate::{ AnyWindowHandle, Bounds, FontFeatures, FontId, FontMetrics, FontStyle, FontWeight, GlyphId, - LineLayout, Pixels, Point, Result, RunStyle, SharedString, Size, + LineLayout, Pixels, Point, Result, RunStyle, Scene, SharedString, Size, }; use anyhow::anyhow; use async_task::Runnable; @@ -145,6 +145,7 @@ pub trait PlatformWindow { fn on_close(&mut self, callback: Box); fn on_appearance_changed(&mut self, callback: Box); fn is_topmost_for_position(&self, position: Point) -> bool; + fn draw(&self, scene: Scene); } pub trait PlatformDispatcher: Send + Sync { diff --git a/crates/gpui3/src/platform/mac/window.rs b/crates/gpui3/src/platform/mac/window.rs index 4571d41d4a9e0aa0414d9af78371ec1fd6cc9c46..6542fb50808cfa2eb44f08db4bfd5cfa3b1e016a 100644 --- a/crates/gpui3/src/platform/mac/window.rs +++ b/crates/gpui3/src/platform/mac/window.rs @@ -3,7 +3,7 @@ use crate::{ point, px, size, AnyWindowHandle, Bounds, Event, InputHandler, KeyDownEvent, Keystroke, MacScreen, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMovedEvent, MouseUpEvent, NSRectExt, Pixels, Platform, PlatformDispatcher, PlatformScreen, PlatformWindow, - Point, Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions, + Point, Scene, Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions, WindowPromptLevel, }; use block::ConcreteBlock; @@ -282,6 +282,7 @@ struct MacWindowState { dispatcher: Arc, native_window: id, renderer: MetalRenderer, + scene_to_render: Option, kind: WindowKind, event_callback: Option bool>>, activate_callback: Option>, @@ -481,6 +482,7 @@ impl MacWindow { dispatcher: platform.dispatcher(), native_window, renderer: MetalRenderer::new(true), + scene_to_render: None, kind: options.kind, event_callback: None, activate_callback: None, @@ -875,6 +877,14 @@ impl PlatformWindow for MacWindow { } } } + + fn draw(&self, scene: crate::Scene) { + let mut this = self.0.lock(); + this.scene_to_render = Some(scene); + unsafe { + let _: () = msg_send![this.native_window.contentView(), setNeedsDisplay: YES]; + } + } } fn get_scale_factor(native_window: id) -> f32 { @@ -1347,51 +1357,9 @@ extern "C" fn display_layer(this: &Object, _: Sel, _: id) { unsafe { let window_state = get_window_state(this); let mut window_state = window_state.as_ref().lock(); - - let scale_factor = window_state.scale_factor(); - let mut scene = crate::Scene::new(scale_factor); - scene.insert(crate::Quad { - order: 2, - bounds: Bounds { - origin: point(10., 10.).map(px), - size: size(100., 100.).map(px), - }, - clip_bounds: Bounds { - origin: point(20., 20.).map(px), - size: size(100., 100.).map(px), - }, - clip_corner_radii: crate::Corners { - top_left: px(10.), - ..Default::default() - }, - background: crate::rgb(0x00ff00).into(), - border_color: Default::default(), - corner_radii: crate::Corners { - top_left: px(9.), - top_right: px(3.), - bottom_right: px(20.), - bottom_left: px(50.), - }, - border_widths: Default::default(), - }); - scene.insert(crate::Quad { - order: 1, - bounds: Bounds { - origin: point(50., 10.).map(px), - size: size(100., 100.).map(px), - }, - clip_bounds: Bounds { - origin: point(10., 10.).map(px), - size: size(100., 100.).map(px), - }, - clip_corner_radii: Default::default(), - background: crate::rgb(0xff0000).into(), - border_color: Default::default(), - corner_radii: Default::default(), - border_widths: Default::default(), - }); - dbg!("!!!!!!!!!"); - window_state.renderer.draw(&scene); + if let Some(scene) = window_state.scene_to_render.take() { + window_state.renderer.draw(&scene); + } } } diff --git a/crates/gpui3/src/view.rs b/crates/gpui3/src/view.rs index af92760ac4de6ee9f7b26fecf49a70b907470cca..be38dcb4e5abcd4942f103ab7486aaa23484c3ae 100644 --- a/crates/gpui3/src/view.rs +++ b/crates/gpui3/src/view.rs @@ -33,7 +33,7 @@ pub fn view>( } } -impl View { +impl View { pub fn into_any(self) -> AnyView { AnyView { view: Rc::new(RefCell::new(self)), @@ -42,7 +42,7 @@ impl View { } } -impl Element for View { +impl Element for View { type State = P; type FrameState = AnyElement; @@ -80,7 +80,7 @@ trait ViewObject { ) -> Result<()>; } -impl ViewObject for View { +impl ViewObject for View { fn layout(&mut self, cx: &mut WindowContext) -> Result<(LayoutId, Box)> { self.state.update(cx, |state, cx| { let mut element = (self.render)(state, cx); diff --git a/crates/gpui3/src/window.rs b/crates/gpui3/src/window.rs index 77ea466e8abd14140a9c20ae8c8bbc3b96a69832..b1e1407aa60a2dda98c5bf3d54779671d264bc4b 100644 --- a/crates/gpui3/src/window.rs +++ b/crates/gpui3/src/window.rs @@ -1,7 +1,7 @@ use crate::{ - px, AppContext, AvailableSpace, Bounds, Context, EntityId, Handle, LayoutId, MainThreadOnly, - Pixels, Platform, PlatformWindow, Point, Reference, Size, Style, TaffyLayoutEngine, TextStyle, - TextStyleRefinement, WindowOptions, + px, AppContext, AvailableSpace, Bounds, Context, Effect, EntityId, Handle, LayoutId, + MainThreadOnly, Pixels, Platform, PlatformWindow, Point, Reference, Size, Style, + TaffyLayoutEngine, TextStyle, TextStyleRefinement, WeakHandle, WindowOptions, }; use anyhow::Result; use derive_more::{Deref, DerefMut}; @@ -22,6 +22,7 @@ pub struct Window { text_style_stack: Vec, pub(crate) root_view: Option>, mouse_position: Point, + pub(crate) dirty: bool, } impl Window { @@ -37,6 +38,7 @@ impl Window { text_style_stack: Vec::new(), root_view: None, mouse_position, + dirty: true, } } } @@ -125,21 +127,21 @@ impl<'a, 'w> WindowContext<'a, 'w> { fn update_window( &mut self, - window_id: WindowId, + window_handle: AnyWindowHandle, update: impl FnOnce(&mut WindowContext) -> R, ) -> Result { - if window_id == self.window.handle.id { + if window_handle == self.window.handle { Ok(update(self)) } else { - self.app.update_window(window_id, update) + self.app.update_window(window_handle, update) } } } impl Context for WindowContext<'_, '_> { - type EntityContext<'a, 'w, T: Send + 'static> = ViewContext<'a, 'w, T>; + type EntityContext<'a, 'w, T: Send + Sync + 'static> = ViewContext<'a, 'w, T>; - fn entity( + fn entity( &mut self, build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T, ) -> Handle { @@ -157,7 +159,7 @@ impl Context for WindowContext<'_, '_> { } } - fn update_entity( + fn update_entity( &mut self, handle: &Handle, update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R, @@ -196,7 +198,7 @@ pub struct ViewContext<'a, 'w, T> { entity_id: EntityId, } -impl<'a, 'w, T: 'static> ViewContext<'a, 'w, T> { +impl<'a, 'w, T: Send + Sync + 'static> ViewContext<'a, 'w, T> { // fn update(&mut self, update: impl FnOnce(&mut T, &mut Self) -> R) -> R { // self.window_cx.update_entity(handle, update) @@ -235,19 +237,59 @@ impl<'a, 'w, T: 'static> ViewContext<'a, 'w, T> { ); f(&mut cx) } + + pub fn handle(&self) -> WeakHandle { + WeakHandle { + id: self.entity_id, + entity_type: PhantomData, + } + } + + pub fn observe( + &mut self, + handle: &Handle, + on_notify: impl Fn(&mut T, Handle, &mut ViewContext<'_, '_, T>) + Send + Sync + 'static, + ) { + let this = self.handle(); + let handle = handle.downgrade(); + let window_handle = self.window.handle; + self.app + .observers + .entry(handle.id) + .or_default() + .push(Arc::new(move |cx| { + cx.update_window(window_handle, |cx| { + if let Some(handle) = handle.upgrade(cx) { + this.update(cx, |this, cx| on_notify(this, handle, cx)) + .is_ok() + } else { + false + } + }) + .unwrap_or(false) + })); + } + + pub fn notify(&mut self) { + let entity_id = self.entity_id; + self.app + .pending_effects + .push_back(Effect::Notify(entity_id)); + self.window.dirty = true; + } } impl<'a, 'w, T: 'static> Context for ViewContext<'a, 'w, T> { - type EntityContext<'b, 'c, U: Send + 'static> = ViewContext<'b, 'c, U>; + type EntityContext<'b, 'c, U: Send + Sync + 'static> = ViewContext<'b, 'c, U>; - fn entity( + fn entity( &mut self, build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T2>) -> T2, ) -> Handle { self.window_cx.entity(build_entity) } - fn update_entity( + fn update_entity( &mut self, handle: &Handle, update: impl FnOnce(&mut U, &mut Self::EntityContext<'_, '_, U>) -> R, @@ -296,7 +338,7 @@ impl Into for WindowHandle { #[derive(Copy, Clone, PartialEq, Eq)] pub struct AnyWindowHandle { - id: WindowId, + pub(crate) id: WindowId, state_type: TypeId, }