From bc1801fb03ae8be11a8d57cc7828edcb35ba1093 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 4 Oct 2023 16:42:28 +0200 Subject: [PATCH 01/10] Checkpoint --- crates/gpui3/src/elements/img.rs | 20 ++++++++++++++++---- crates/gpui3/src/geometry.rs | 22 ++++++++++++++++------ crates/gpui3/src/style.rs | 7 +++++-- 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/crates/gpui3/src/elements/img.rs b/crates/gpui3/src/elements/img.rs index 1d2a7bd1d44a363bd0e186231a1a2f33b5fe71f0..81164f1bdc17a1fd78dd3ec304826feeea6d700a 100644 --- a/crates/gpui3/src/elements/img.rs +++ b/crates/gpui3/src/elements/img.rs @@ -1,5 +1,6 @@ use crate::{ - Element, Layout, LayoutId, Result, SharedString, Style, StyleHelpers, Styled, ViewContext, + BorrowWindow, ContentMask, Element, IsZero, Layout, LayoutId, Result, SharedString, Style, + StyleHelpers, Styled, ViewContext, }; use futures::FutureExt; use refineable::RefinementCascade; @@ -64,14 +65,25 @@ impl Element for Img { style.paint(order, bounds, cx); - if let Some(uri) = &self.uri { - let image_future = cx.image_cache.get(uri.clone()); + if let Some(uri) = self.uri.clone() { + let image_future = cx.image_cache.get(uri); if let Some(data) = image_future .clone() .now_or_never() .and_then(ResultExt::log_err) { - cx.paint_image(bounds, order, data, self.grayscale)?; + let corner_radii = style.corner_radii.to_pixels(bounds, cx.rem_size()); + if corner_radii.is_zero() { + cx.paint_image(bounds, order, data, self.grayscale)?; + } else { + cx.with_content_mask( + ContentMask { + bounds, + corner_radii, + }, + |cx| cx.paint_image(bounds, order, data, self.grayscale), + )?; + } } else { log::warn!("image not loaded yet"); // cx.spawn(|this, mut cx| async move { diff --git a/crates/gpui3/src/geometry.rs b/crates/gpui3/src/geometry.rs index 19d1c262b1b57010e0d394130b09de01bbf4159c..c7af290e61ae039889fde14cea3a1e7ed0c9be54 100644 --- a/crates/gpui3/src/geometry.rs +++ b/crates/gpui3/src/geometry.rs @@ -433,12 +433,13 @@ pub struct Corners { } impl Corners { - pub fn to_pixels(&self, rem_size: Pixels) -> Corners { + pub fn to_pixels(&self, bounds: Bounds, rem_size: Pixels) -> Corners { + let max = bounds.size.width.max(bounds.size.height) / 2.; Corners { - top_left: self.top_left.to_pixels(rem_size), - top_right: self.top_right.to_pixels(rem_size), - bottom_right: self.bottom_right.to_pixels(rem_size), - bottom_left: self.bottom_left.to_pixels(rem_size), + top_left: self.top_left.to_pixels(rem_size).min(max), + top_right: self.top_right.to_pixels(rem_size).min(max), + bottom_right: self.bottom_right.to_pixels(rem_size).min(max), + bottom_left: self.bottom_left.to_pixels(rem_size).min(max), } } } @@ -925,6 +926,15 @@ impl IsZero for Size { impl IsZero for Bounds { fn is_zero(&self) -> bool { - self.origin.is_zero() && self.size.is_zero() + self.size.is_zero() + } +} + +impl IsZero for Corners { + fn is_zero(&self) -> bool { + self.top_left.is_zero() + && self.top_right.is_zero() + && self.bottom_right.is_zero() + && self.bottom_left.is_zero() } } diff --git a/crates/gpui3/src/style.rs b/crates/gpui3/src/style.rs index c4ce8250c0bbde2c7298698116bc912f8dc2bc9e..33da5e3602a945f8873b973ae8e1ee4a57d0e603 100644 --- a/crates/gpui3/src/style.rs +++ b/crates/gpui3/src/style.rs @@ -202,7 +202,7 @@ impl Style { let min = current_mask.bounds.origin; let max = current_mask.bounds.lower_right(); - let mask_corner_radii = Corners::default(); + let mut mask_corner_radii = Corners::default(); let mask_bounds = match ( self.overflow.x == Overflow::Visible, self.overflow.y == Overflow::Visible, @@ -220,7 +220,10 @@ impl Style { point(bounds.lower_right().x, max.y), ), // both hidden - (false, false) => bounds, + (false, false) => { + mask_corner_radii = self.corner_radii.to_pixels(bounds, cx.rem_size()); + bounds + } }; let mask = ContentMask { bounds: mask_bounds, From f9646208e90da99d15a01585d0f6f6879b39ff88 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 4 Oct 2023 17:48:28 +0200 Subject: [PATCH 02/10] Checkpoint --- crates/gpui3/src/app.rs | 32 ++++++++++------ crates/gpui3/src/executor.rs | 10 ++++- crates/gpui3/src/gpui3.rs | 37 +++++++++++++++++++ crates/gpui3/src/window.rs | 71 +++++++++++++++++++++++++----------- 4 files changed, 117 insertions(+), 33 deletions(-) diff --git a/crates/gpui3/src/app.rs b/crates/gpui3/src/app.rs index 8fbccaa83828ada97ae45060dc3eb4a8d8b7ea3b..94025ff9a844391b5153bf15ca1c20510001accb 100644 --- a/crates/gpui3/src/app.rs +++ b/crates/gpui3/src/app.rs @@ -14,7 +14,7 @@ use crate::{ }; use anyhow::{anyhow, Result}; use collections::{HashMap, VecDeque}; -use futures::Future; +use futures::{channel::oneshot, Future}; use parking_lot::Mutex; use slotmap::SlotMap; use smallvec::SmallVec; @@ -93,9 +93,9 @@ type Handlers = SmallVec<[Arc bool + Send + Sync + 's pub struct AppContext { this: Weak>>, platform: MainThreadOnly, - dispatcher: Arc, text_system: Arc, pending_updates: usize, + pub(crate) dispatcher: Arc, pub(crate) svg_renderer: SvgRenderer, pub(crate) image_cache: ImageCache, pub(crate) text_style_stack: Vec, @@ -185,19 +185,29 @@ impl AppContext { } pub fn run_on_main( - &self, + &mut self, f: impl FnOnce(&mut MainThread) -> R + Send + 'static, ) -> impl Future where R: Send + 'static, { - let this = self.this.upgrade().unwrap(); - run_on_main(self.dispatcher.clone(), move || { - let cx = &mut *this.lock(); - cx.update(|cx| { - f(unsafe { mem::transmute::<&mut AppContext, &mut MainThread>(cx) }) - }) - }) + let (tx, rx) = oneshot::channel(); + if self.dispatcher.is_main_thread() { + let _ = tx.send(f(unsafe { + mem::transmute::<&mut AppContext, &mut MainThread>(self) + })); + } else { + let this = self.this.upgrade().unwrap(); + let _ = run_on_main(self.dispatcher.clone(), move || { + let cx = &mut *this.lock(); + cx.update(|cx| { + let _ = tx.send(f(unsafe { + mem::transmute::<&mut Self, &mut MainThread>(cx) + })); + }) + }); + } + async move { rx.await.unwrap() } } pub fn spawn_on_main( @@ -307,7 +317,7 @@ impl MainThread { pub(crate) fn update_window( &mut self, id: WindowId, - update: impl FnOnce(&mut WindowContext) -> R, + update: impl FnOnce(&mut MainThread) -> R, ) -> Result { self.0.update_window(id, |cx| { update(unsafe { diff --git a/crates/gpui3/src/executor.rs b/crates/gpui3/src/executor.rs index 81df5a3738d9396412c5cb6c8ab08953fc0bb9d0..6a4f7a13e9bd278a7afe415673b61cd315b5253d 100644 --- a/crates/gpui3/src/executor.rs +++ b/crates/gpui3/src/executor.rs @@ -27,7 +27,15 @@ where F: FnOnce() -> R + Send + 'static, R: Send + 'static, { - spawn_on_main(dispatcher, move || async move { func() }) + let (tx, rx) = oneshot::channel(); + if dispatcher.is_main_thread() { + let _ = tx.send(func()); + } else { + let _ = spawn_on_main(dispatcher, move || async move { + let _ = tx.send(func()); + }); + } + async move { rx.await.unwrap() } } /// Enqueues the given closure to be run on the application's event loop. The diff --git a/crates/gpui3/src/gpui3.rs b/crates/gpui3/src/gpui3.rs index a429541cb8a28e61b83d7f59a0082e551019dd42..7c8c7c498c3303c9cd3e5c226d1375151c7f03e8 100644 --- a/crates/gpui3/src/gpui3.rs +++ b/crates/gpui3/src/gpui3.rs @@ -37,6 +37,7 @@ pub use serde_json; pub use smallvec; pub use smol::Timer; use std::{ + mem, ops::{Deref, DerefMut}, sync::Arc, }; @@ -89,6 +90,42 @@ impl DerefMut for MainThread { } } +impl Context for MainThread { + type EntityContext<'a, 'w, T: 'static + Send + Sync> = MainThread>; + type Result = C::Result; + + fn entity( + &mut self, + build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T, + ) -> Self::Result> { + self.0.entity(|cx| { + let cx = unsafe { + mem::transmute::< + &mut C::EntityContext<'_, '_, T>, + &mut MainThread>, + >(cx) + }; + build_entity(cx) + }) + } + + fn update_entity( + &mut self, + handle: &Handle, + update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R, + ) -> Self::Result { + self.0.update_entity(handle, |entity, cx| { + let cx = unsafe { + mem::transmute::< + &mut C::EntityContext<'_, '_, T>, + &mut MainThread>, + >(cx) + }; + update(entity, cx) + }) + } +} + pub trait BorrowAppContext { fn app_mut(&mut self) -> &mut AppContext; diff --git a/crates/gpui3/src/window.rs b/crates/gpui3/src/window.rs index 2e53016f65788414f0c4d777900a217903eb6930..12bc21d9f0e4eeed271d4b6a0f2082ea4efaba2d 100644 --- a/crates/gpui3/src/window.rs +++ b/crates/gpui3/src/window.rs @@ -7,7 +7,7 @@ use crate::{ WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::Result; -use futures::Future; +use futures::{channel::oneshot, Future}; use smallvec::SmallVec; use std::{any::TypeId, borrow::Cow, marker::PhantomData, mem, sync::Arc}; use util::ResultExt; @@ -112,6 +112,29 @@ impl<'a, 'w> WindowContext<'a, 'w> { self.window.dirty = true; } + fn run_on_main( + &mut self, + f: impl FnOnce(&mut MainThread>) -> R + Send + 'static, + ) -> impl Future + where + R: Send + 'static, + { + let (tx, rx) = oneshot::channel(); + if self.dispatcher.is_main_thread() { + let _ = tx.send(f(unsafe { + mem::transmute::<&mut Self, &mut MainThread>(self) + })); + } else { + let id = self.window.handle.id; + let _ = self.app.run_on_main(move |cx| { + cx.update_window(id, |cx| { + let _ = tx.send(f(cx)); + }) + }); + } + async move { rx.await.unwrap() } + } + pub fn request_layout( &mut self, style: Style, @@ -191,23 +214,6 @@ impl<'a, 'w> WindowContext<'a, 'w> { self.window.current_layer_id.clone() } - pub fn run_on_main( - &self, - f: impl FnOnce(&mut MainThread) -> R + Send + 'static, - ) -> impl Future> - where - R: Send + 'static, - { - let id = self.window.handle.id; - self.app.run_on_main(move |cx| { - cx.update_window(id, |cx| { - f(unsafe { - mem::transmute::<&mut WindowContext, &mut MainThread>(cx) - }) - }) - }) - } - pub fn paint_glyph( &mut self, origin: Point, @@ -389,7 +395,7 @@ impl<'a, 'w> WindowContext<'a, 'w> { pub(crate) fn draw(&mut self) -> Result<()> { let unit_entity = self.unit_entity.clone(); - self.update_entity(&unit_entity, |_, cx| { + self.update_entity(&unit_entity, |view, cx| { let mut root_view = cx.window.root_view.take().unwrap(); let (root_layout_id, mut frame_state) = root_view.layout(&mut (), cx)?; let available_space = cx.window.content_size.map(Into::into); @@ -403,14 +409,14 @@ impl<'a, 'w> WindowContext<'a, 'w> { cx.window.root_view = Some(root_view); let scene = cx.window.scene.take(); - let _ = cx.run_on_main(|cx| { + let _ = cx.run_on_main(view, |_, cx| { cx.window .platform_window .borrow_on_main_thread() .draw(scene); + cx.window.dirty = false; }); - cx.window.dirty = false; Ok(()) }) } @@ -596,6 +602,29 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> { .push_back(Effect::Notify(self.entity_id)); } + pub fn run_on_main( + &mut self, + view: &mut S, + f: impl FnOnce(&mut S, &mut MainThread>) -> R + Send + 'static, + ) -> impl Future + where + R: Send + 'static, + { + let (tx, rx) = oneshot::channel(); + if self.dispatcher.is_main_thread() { + let cx = unsafe { mem::transmute::<&mut Self, &mut MainThread>(self) }; + let _ = tx.send(f(view, cx)); + } else { + let handle = self.handle().upgrade(self).unwrap(); + let _ = self.window_cx.run_on_main(move |cx| { + handle.update(cx, |view, cx| { + let _ = tx.send(f(view, cx)); + }) + }); + } + async move { rx.await.unwrap() } + } + pub(crate) fn erase_state(&mut self, f: impl FnOnce(&mut ViewContext<()>) -> R) -> R { let entity_id = self.unit_entity.id; let mut cx = ViewContext::mutable( From 133c3a330c0c706b87d26dde860b785e27391334 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 4 Oct 2023 17:59:29 +0200 Subject: [PATCH 03/10] Checkpoint --- crates/gpui3/src/geometry.rs | 32 +++++++++++++++++++++++++++++--- crates/gpui3/src/window.rs | 10 ++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/crates/gpui3/src/geometry.rs b/crates/gpui3/src/geometry.rs index c7af290e61ae039889fde14cea3a1e7ed0c9be54..d60866816d6e3513929f530d2ae9bd50eb97966a 100644 --- a/crates/gpui3/src/geometry.rs +++ b/crates/gpui3/src/geometry.rs @@ -1,7 +1,10 @@ use core::fmt::Debug; use derive_more::{Add, AddAssign, Div, Mul, Sub, SubAssign}; use refineable::Refineable; -use std::ops::{Add, AddAssign, Div, Mul, MulAssign, Sub, SubAssign}; +use std::{ + cmp, + ops::{Add, AddAssign, Div, Mul, MulAssign, Sub, SubAssign}, +}; #[derive(Refineable, Default, Add, AddAssign, Sub, SubAssign, Copy, Debug, PartialEq, Eq, Hash)] #[refineable(debug)] @@ -84,7 +87,7 @@ impl, S: Clone> Div for Point { } } -impl Point { +impl Point { pub fn max(&self, other: &Self) -> Self { Point { x: if self.x >= other.x { @@ -99,6 +102,21 @@ impl Point { }, } } + + pub fn min(&self, other: &Self) -> Self { + Point { + x: if self.x <= other.x { + self.x.clone() + } else { + other.x.clone() + }, + y: if self.y <= other.y { + self.y.clone() + } else { + other.y.clone() + }, + } + } } impl Clone for Point { @@ -239,6 +257,14 @@ impl> Bounds { } } +impl + Sub> Bounds { + pub fn intersect(&self, other: &Self) -> Self { + let upper_left = self.origin.max(&other.origin); + let lower_right = self.lower_right().min(&other.lower_right()); + Self::from_corners(upper_left, lower_right) + } +} + impl Mul for Bounds where T: Mul + Clone + Debug, @@ -537,7 +563,7 @@ impl Mul for Pixels { impl Eq for Pixels {} impl Ord for Pixels { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { + fn cmp(&self, other: &Self) -> cmp::Ordering { self.0.partial_cmp(&other.0).unwrap() } } diff --git a/crates/gpui3/src/window.rs b/crates/gpui3/src/window.rs index 12bc21d9f0e4eeed271d4b6a0f2082ea4efaba2d..b92edb8ecefee09c1a89934b0ad45eef7b615dcb 100644 --- a/crates/gpui3/src/window.rs +++ b/crates/gpui3/src/window.rs @@ -86,6 +86,15 @@ impl ContentMask { corner_radii: self.corner_radii.scale(factor), } } + + pub fn intersect(&self, other: &Self) -> Self { + let bounds = self.bounds.intersect(&other.bounds); + // todo!("intersect corner radii") + ContentMask { + bounds, + corner_radii: self.corner_radii, + } + } } #[derive(Clone, Debug, PartialEq, Eq)] @@ -479,6 +488,7 @@ pub trait BorrowWindow: BorrowAppContext { fn window_mut(&mut self) -> &mut Window; fn with_content_mask(&mut self, mask: ContentMask, f: impl FnOnce(&mut Self) -> R) -> R { + let mask = mask.intersect(&self.content_mask()); self.window_mut().content_mask_stack.push(mask); let result = f(self); self.window_mut().content_mask_stack.pop(); From 5aa45607eb9f1b23d3857754867ed40fd5e9c04a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 4 Oct 2023 18:38:08 +0200 Subject: [PATCH 04/10] Checkpoint --- crates/gpui3/src/elements/img.rs | 16 ++--------- crates/gpui3/src/platform/mac/shaders.metal | 27 ++++++++++-------- crates/gpui3/src/scene.rs | 1 + crates/gpui3/src/style.rs | 7 +---- crates/gpui3/src/window.rs | 31 ++++----------------- 5 files changed, 25 insertions(+), 57 deletions(-) diff --git a/crates/gpui3/src/elements/img.rs b/crates/gpui3/src/elements/img.rs index 81164f1bdc17a1fd78dd3ec304826feeea6d700a..1932045885ce0e30f3f4c6c18d652638c65d775f 100644 --- a/crates/gpui3/src/elements/img.rs +++ b/crates/gpui3/src/elements/img.rs @@ -1,6 +1,6 @@ use crate::{ - BorrowWindow, ContentMask, Element, IsZero, Layout, LayoutId, Result, SharedString, Style, - StyleHelpers, Styled, ViewContext, + BorrowWindow, Element, Layout, LayoutId, Result, SharedString, Style, StyleHelpers, Styled, + ViewContext, }; use futures::FutureExt; use refineable::RefinementCascade; @@ -73,17 +73,7 @@ impl Element for Img { .and_then(ResultExt::log_err) { let corner_radii = style.corner_radii.to_pixels(bounds, cx.rem_size()); - if corner_radii.is_zero() { - cx.paint_image(bounds, order, data, self.grayscale)?; - } else { - cx.with_content_mask( - ContentMask { - bounds, - corner_radii, - }, - |cx| cx.paint_image(bounds, order, data, self.grayscale), - )?; - } + cx.paint_image(bounds, corner_radii, order, data, self.grayscale)?; } else { log::warn!("image not loaded yet"); // cx.spawn(|this, mut cx| async move { diff --git a/crates/gpui3/src/platform/mac/shaders.metal b/crates/gpui3/src/platform/mac/shaders.metal index 313feec7f11902c430a8861e379643b0c9fe62a5..b4614bef4d58fc08f8f8a24008e0ae79b86e8f02 100644 --- a/crates/gpui3/src/platform/mac/shaders.metal +++ b/crates/gpui3/src/platform/mac/shaders.metal @@ -106,11 +106,7 @@ fragment float4 quad_fragment(QuadVertexOutput input [[stage_in]], color = float4(premultiplied_output_rgb, output_alpha); } - float clip_distance = - quad_sdf(input.position.xy, quad.clip_bounds, quad.clip_corner_radii); - return color * - float4(1., 1., 1., - saturate(0.5 - distance) * saturate(0.5 - clip_distance)); + return color * float4(1., 1., 1., saturate(0.5 - distance)); } struct MonochromeSpriteVertexOutput { @@ -131,8 +127,9 @@ vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex( float2 unit_vertex = unit_vertices[unit_vertex_id]; MonochromeSprite sprite = sprites[sprite_id]; + // Don't apply content mask at the vertex level because we don't have time to make sampling from the texture match the mask. float4 device_position = to_device_position( - unit_vertex, sprite.bounds, sprite.content_mask.bounds, viewport_size); + unit_vertex, sprite.bounds, sprite.bounds, viewport_size); float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size); float4 color = hsla_to_rgba(sprite.color); return MonochromeSpriteVertexOutput{device_position, tile_position, color, @@ -148,8 +145,11 @@ fragment float4 monochrome_sprite_fragment( min_filter::linear); float4 sample = atlas_texture.sample(atlas_texture_sampler, input.tile_position); - float clip_distance = quad_sdf(input.position.xy, sprite.content_mask.bounds, - sprite.content_mask.corner_radii); + float clip_distance = quad_sdf( + input.position.xy, + sprite.content_mask.bounds, + Corners_ScaledPixels { 0., 0., 0., 0. } + ); float4 color = input.color; color.a *= sample.a * saturate(0.5 - clip_distance); return color; @@ -172,8 +172,9 @@ vertex PolychromeSpriteVertexOutput polychrome_sprite_vertex( float2 unit_vertex = unit_vertices[unit_vertex_id]; PolychromeSprite sprite = sprites[sprite_id]; + // Don't apply content mask at the vertex level because we don't have time to make sampling from the texture match the mask. float4 device_position = to_device_position( - unit_vertex, sprite.bounds, sprite.content_mask.bounds, viewport_size); + unit_vertex, sprite.bounds, sprite.bounds, viewport_size); float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size); return PolychromeSpriteVertexOutput{device_position, tile_position, sprite_id}; @@ -188,8 +189,10 @@ fragment float4 polychrome_sprite_fragment( min_filter::linear); float4 sample = atlas_texture.sample(atlas_texture_sampler, input.tile_position); - float clip_distance = quad_sdf(input.position.xy, sprite.content_mask.bounds, - sprite.content_mask.corner_radii); + float quad_distance = quad_sdf(input.position.xy, sprite.bounds, sprite.corner_radii); + float clip_distance = quad_sdf(input.position.xy, sprite.content_mask.bounds, Corners_ScaledPixels { 0., 0., 0., 0. }); + float distance = max(quad_distance, clip_distance); + float4 color = sample; if (sprite.grayscale) { float grayscale = 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b; @@ -197,7 +200,7 @@ fragment float4 polychrome_sprite_fragment( color.g = grayscale; color.b = grayscale; } - color.a *= saturate(0.5 - clip_distance); + color.a *= saturate(0.5 - distance); return color; } diff --git a/crates/gpui3/src/scene.rs b/crates/gpui3/src/scene.rs index fb6111b0391e85cc2e3077e4c7eec883445603ee..3426eefc9ea306ab530e7082b6ad16880d080c30 100644 --- a/crates/gpui3/src/scene.rs +++ b/crates/gpui3/src/scene.rs @@ -275,6 +275,7 @@ pub struct PolychromeSprite { pub order: u32, pub bounds: Bounds, pub content_mask: ScaledContentMask, + pub corner_radii: Corners, pub tile: AtlasTile, pub grayscale: bool, } diff --git a/crates/gpui3/src/style.rs b/crates/gpui3/src/style.rs index 33da5e3602a945f8873b973ae8e1ee4a57d0e603..1f874807488b01374f4517273518127f9460d787 100644 --- a/crates/gpui3/src/style.rs +++ b/crates/gpui3/src/style.rs @@ -202,7 +202,6 @@ impl Style { let min = current_mask.bounds.origin; let max = current_mask.bounds.lower_right(); - let mut mask_corner_radii = Corners::default(); let mask_bounds = match ( self.overflow.x == Overflow::Visible, self.overflow.y == Overflow::Visible, @@ -220,14 +219,10 @@ impl Style { point(bounds.lower_right().x, max.y), ), // both hidden - (false, false) => { - mask_corner_radii = self.corner_radii.to_pixels(bounds, cx.rem_size()); - bounds - } + (false, false) => bounds, }; let mask = ContentMask { bounds: mask_bounds, - corner_radii: mask_corner_radii, }; cx.with_content_mask(mask, f) diff --git a/crates/gpui3/src/window.rs b/crates/gpui3/src/window.rs index b92edb8ecefee09c1a89934b0ad45eef7b615dcb..e1a75791e4590733f721bb0b346f12d2baafacd4 100644 --- a/crates/gpui3/src/window.rs +++ b/crates/gpui3/src/window.rs @@ -76,24 +76,18 @@ impl Window { #[derive(Clone, Debug)] pub struct ContentMask { pub bounds: Bounds, - pub corner_radii: Corners, } impl ContentMask { pub fn scale(&self, factor: f32) -> ScaledContentMask { ScaledContentMask { bounds: self.bounds.scale(factor), - corner_radii: self.corner_radii.scale(factor), } } pub fn intersect(&self, other: &Self) -> Self { let bounds = self.bounds.intersect(&other.bounds); - // todo!("intersect corner radii") - ContentMask { - bounds, - corner_radii: self.corner_radii, - } + ContentMask { bounds } } } @@ -101,7 +95,6 @@ impl ContentMask { #[repr(C)] pub struct ScaledContentMask { bounds: Bounds, - corner_radii: Corners, } pub struct WindowContext<'a, 'w> { @@ -202,23 +195,6 @@ impl<'a, 'w> WindowContext<'a, 'w> { result } - pub fn clip( - &mut self, - bounds: Bounds, - corner_radii: Corners, - f: impl FnOnce(&mut Self) -> R, - ) -> R { - let clip_mask = ContentMask { - bounds, - corner_radii, - }; - - self.window.content_mask_stack.push(clip_mask); - let result = f(self); - self.window.content_mask_stack.pop(); - result - } - pub fn current_layer_id(&self) -> LayerId { self.window.current_layer_id.clone() } @@ -318,6 +294,7 @@ impl<'a, 'w> WindowContext<'a, 'w> { PolychromeSprite { order, bounds, + corner_radii: Default::default(), content_mask, tile, grayscale: false, @@ -371,6 +348,7 @@ impl<'a, 'w> WindowContext<'a, 'w> { pub fn paint_image( &mut self, bounds: Bounds, + corner_radii: Corners, order: u32, data: Arc, grayscale: bool, @@ -387,6 +365,7 @@ impl<'a, 'w> WindowContext<'a, 'w> { Ok((data.size(), Cow::Borrowed(data.as_bytes()))) })?; let content_mask = self.content_mask().scale(scale_factor); + let corner_radii = corner_radii.scale(scale_factor); self.window.scene.insert( layer_id, @@ -394,6 +373,7 @@ impl<'a, 'w> WindowContext<'a, 'w> { order, bounds, content_mask, + corner_radii, tile, grayscale, }, @@ -505,7 +485,6 @@ pub trait BorrowWindow: BorrowAppContext { origin: Point::default(), size: self.window().content_size, }, - corner_radii: Default::default(), }) } From dc9a26042578858108230567369798eb7c166529 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 4 Oct 2023 19:53:29 +0200 Subject: [PATCH 05/10] Checkpoint --- crates/gpui3/build.rs | 2 + crates/gpui3/src/app.rs | 40 +- crates/gpui3/src/executor.rs | 1120 ++----------------- crates/gpui3/src/gpui3.rs | 10 +- crates/gpui3/src/platform.rs | 9 +- crates/gpui3/src/platform/mac/dispatcher.rs | 41 +- crates/gpui3/src/platform/mac/platform.rs | 41 +- crates/gpui3/src/platform/mac/window.rs | 115 +- crates/gpui3/src/platform/test.rs | 4 +- crates/gpui3/src/util.rs | 24 +- crates/gpui3/src/window.rs | 7 +- 11 files changed, 235 insertions(+), 1178 deletions(-) diff --git a/crates/gpui3/build.rs b/crates/gpui3/build.rs index 60849484268b93204d95db695cb41a2eb3c32403..861534aa6eb75874fa25c1213e0c118bf3ee33aa 100644 --- a/crates/gpui3/build.rs +++ b/crates/gpui3/build.rs @@ -19,6 +19,8 @@ fn generate_dispatch_bindings() { let bindings = bindgen::Builder::default() .header("src/platform/mac/dispatch.h") .allowlist_var("_dispatch_main_q") + .allowlist_var("DISPATCH_QUEUE_PRIORITY_DEFAULT") + .allowlist_function("dispatch_get_global_queue") .allowlist_function("dispatch_async_f") .parse_callbacks(Box::new(bindgen::CargoCallbacks)) .layout_tests(false) diff --git a/crates/gpui3/src/app.rs b/crates/gpui3/src/app.rs index 94025ff9a844391b5153bf15ca1c20510001accb..a05f9bb792c1aed7813aff03f28d18c79d6e6b9f 100644 --- a/crates/gpui3/src/app.rs +++ b/crates/gpui3/src/app.rs @@ -8,9 +8,9 @@ pub use model_context::*; use refineable::Refineable; use crate::{ - current_platform, image_cache::ImageCache, run_on_main, spawn_on_main, AssetSource, Context, - LayoutId, MainThread, MainThreadOnly, Platform, PlatformDispatcher, RootView, SvgRenderer, - TextStyle, TextStyleRefinement, TextSystem, Window, WindowContext, WindowHandle, WindowId, + current_platform, image_cache::ImageCache, AssetSource, Context, Executor, LayoutId, + MainThread, MainThreadOnly, Platform, RootView, SvgRenderer, Task, TextStyle, + TextStyleRefinement, TextSystem, Window, WindowContext, WindowHandle, WindowId, }; use anyhow::{anyhow, Result}; use collections::{HashMap, VecDeque}; @@ -50,15 +50,15 @@ impl App { asset_source: Arc, http_client: Arc, ) -> Self { - let dispatcher = platform.dispatcher(); + let executor = platform.executor(); let text_system = Arc::new(TextSystem::new(platform.text_system())); let entities = EntityMap::new(); let unit_entity = entities.redeem(entities.reserve(), ()); Self(Arc::new_cyclic(|this| { Mutex::new(MainThread::new(AppContext { this: this.clone(), - platform: MainThreadOnly::new(platform, dispatcher.clone()), - dispatcher, + platform: MainThreadOnly::new(platform, executor.clone()), + executor, text_system, svg_renderer: SvgRenderer::new(asset_source), image_cache: ImageCache::new(http_client), @@ -95,7 +95,7 @@ pub struct AppContext { platform: MainThreadOnly, text_system: Arc, pending_updates: usize, - pub(crate) dispatcher: Arc, + pub(crate) executor: Executor, pub(crate) svg_renderer: SvgRenderer, pub(crate) image_cache: ImageCache, pub(crate) text_style_stack: Vec, @@ -184,6 +184,10 @@ impl AppContext { AsyncContext(unsafe { mem::transmute(self.this.clone()) }) } + pub fn executor(&self) -> &Executor { + &self.executor + } + pub fn run_on_main( &mut self, f: impl FnOnce(&mut MainThread) -> R + Send + 'static, @@ -192,20 +196,22 @@ impl AppContext { R: Send + 'static, { let (tx, rx) = oneshot::channel(); - if self.dispatcher.is_main_thread() { + if self.executor.is_main_thread() { let _ = tx.send(f(unsafe { mem::transmute::<&mut AppContext, &mut MainThread>(self) })); } else { let this = self.this.upgrade().unwrap(); - let _ = run_on_main(self.dispatcher.clone(), move || { - let cx = &mut *this.lock(); - cx.update(|cx| { - let _ = tx.send(f(unsafe { - mem::transmute::<&mut Self, &mut MainThread>(cx) - })); + self.executor + .run_on_main(move || { + let cx = &mut *this.lock(); + cx.update(|cx| { + let _ = tx.send(f(unsafe { + mem::transmute::<&mut Self, &mut MainThread>(cx) + })); + }) }) - }); + .detach(); } async move { rx.await.unwrap() } } @@ -213,13 +219,13 @@ impl AppContext { pub fn spawn_on_main( &self, f: impl FnOnce(&mut MainThread) -> F + Send + 'static, - ) -> impl Future + ) -> Task where F: Future + 'static, R: Send + 'static, { let this = self.this.upgrade().unwrap(); - spawn_on_main(self.dispatcher.clone(), move || { + self.executor.spawn_on_main(move || { let cx = &mut *this.lock(); cx.update(|cx| { f(unsafe { mem::transmute::<&mut AppContext, &mut MainThread>(cx) }) diff --git a/crates/gpui3/src/executor.rs b/crates/gpui3/src/executor.rs index 6a4f7a13e9bd278a7afe415673b61cd315b5253d..7c4ab3b8e50c647dcafbc162f6d80acd2122602e 100644 --- a/crates/gpui3/src/executor.rs +++ b/crates/gpui3/src/executor.rs @@ -1,1103 +1,113 @@ -use crate::util; use crate::PlatformDispatcher; -use anyhow::{anyhow, Result}; -use async_task::Runnable; -use futures::channel::{mpsc, oneshot}; -use smol::{channel, prelude::*, Executor}; +use smol::prelude::*; use std::{ - any::Any, - fmt::{self}, - marker::PhantomData, - mem, pin::Pin, - rc::Rc, sync::Arc, task::{Context, Poll}, - thread, - time::Duration, }; -/// Enqueues the given closure to run on the application's event loop. -/// Returns the result asynchronously. -pub(crate) fn run_on_main( +#[derive(Clone)] +pub struct Executor { dispatcher: Arc, - func: F, -) -> impl Future -where - F: FnOnce() -> R + Send + 'static, - R: Send + 'static, -{ - let (tx, rx) = oneshot::channel(); - if dispatcher.is_main_thread() { - let _ = tx.send(func()); - } else { - let _ = spawn_on_main(dispatcher, move || async move { - let _ = tx.send(func()); - }); - } - async move { rx.await.unwrap() } -} - -/// Enqueues the given closure to be run on the application's event loop. The -/// closure returns a future which will be run to completion on the main thread. -pub(crate) fn spawn_on_main( - dispatcher: Arc, - func: impl FnOnce() -> F + Send + 'static, -) -> impl Future -where - F: Future + 'static, - R: Send + 'static, -{ - let (tx, rx) = oneshot::channel(); - let (runnable, task) = async_task::spawn( - { - let dispatcher = dispatcher.clone(); - async move { - let future = func(); - let _ = spawn_on_main_local(dispatcher, async move { - let result = future.await; - let _ = tx.send(result); - }); - } - }, - move |runnable| dispatcher.run_on_main_thread(runnable), - ); - runnable.schedule(); - task.detach(); - async move { rx.await.unwrap() } -} - -/// Enqueues the given closure to be run on the application's event loop. Must -/// be called on the main thread. -pub(crate) fn spawn_on_main_local( - dispatcher: Arc, - future: impl Future + 'static, -) -> impl Future -where - R: 'static, -{ - assert!(dispatcher.is_main_thread(), "must be called on main thread"); - - let (tx, rx) = oneshot::channel(); - let (runnable, task) = async_task::spawn_local( - async move { - let result = future.await; - let _ = tx.send(result); - }, - move |runnable| dispatcher.run_on_main_thread(runnable), - ); - runnable.schedule(); - task.detach(); - async move { rx.await.unwrap() } } -pub enum ForegroundExecutor { - Platform { - dispatcher: Arc, - _not_send_or_sync: PhantomData>, - }, - #[cfg(any(test, feature = "test"))] - Deterministic { - cx_id: usize, - executor: Arc, - }, -} - -pub enum BackgroundExecutor { - #[cfg(any(test, feature = "test"))] - Deterministic { executor: Arc }, - Production { - executor: Arc>, - _stop: channel::Sender<()>, - }, -} - -type AnyLocalFuture = Pin>>>; -type AnyFuture = Pin>>>; -type AnyTask = async_task::Task>; -type AnyLocalTask = async_task::Task>; - -#[must_use] pub enum Task { Ready(Option), - Local { - any_task: AnyLocalTask, - result_type: PhantomData, - }, - Send { - any_task: AnyTask, - result_type: PhantomData, - }, -} - -unsafe impl Send for Task {} - -#[cfg(any(test, feature = "test"))] -struct DeterministicState { - rng: rand::prelude::StdRng, - seed: u64, - scheduled_from_foreground: collections::HashMap>, - scheduled_from_background: Vec, - forbid_parking: bool, - block_on_ticks: std::ops::RangeInclusive, - now: std::time::Instant, - next_timer_id: usize, - pending_timers: Vec<(usize, std::time::Instant, postage::barrier::Sender)>, - waiting_backtrace: Option, - next_runnable_id: usize, - poll_history: Vec, - previous_poll_history: Option>, - enable_runnable_backtraces: bool, - runnable_backtraces: collections::HashMap, -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum ExecutorEvent { - PollRunnable { id: usize }, - EnqueuRunnable { id: usize }, -} - -#[cfg(any(test, feature = "test"))] -struct ForegroundRunnable { - id: usize, - runnable: Runnable, - main: bool, -} - -#[cfg(any(test, feature = "test"))] -struct BackgroundRunnable { - id: usize, - runnable: Runnable, -} - -#[cfg(any(test, feature = "test"))] -pub struct Deterministic { - state: Arc>, - parker: parking_lot::Mutex, -} - -#[must_use] -pub enum Timer { - Production(smol::Timer), - #[cfg(any(test, feature = "test"))] - Deterministic(DeterministicTimer), -} - -#[cfg(any(test, feature = "test"))] -pub struct DeterministicTimer { - rx: postage::barrier::Receiver, - id: usize, - state: Arc>, -} - -#[cfg(any(test, feature = "test"))] -impl Deterministic { - pub fn new(seed: u64) -> Arc { - use rand::prelude::*; - - Arc::new(Self { - state: Arc::new(parking_lot::Mutex::new(DeterministicState { - rng: StdRng::seed_from_u64(seed), - seed, - scheduled_from_foreground: Default::default(), - scheduled_from_background: Default::default(), - forbid_parking: false, - block_on_ticks: 0..=1000, - now: std::time::Instant::now(), - next_timer_id: Default::default(), - pending_timers: Default::default(), - waiting_backtrace: None, - next_runnable_id: 0, - poll_history: Default::default(), - previous_poll_history: Default::default(), - enable_runnable_backtraces: false, - runnable_backtraces: Default::default(), - })), - parker: Default::default(), - }) - } - - pub fn execution_history(&self) -> Vec { - self.state.lock().poll_history.clone() - } - - pub fn set_previous_execution_history(&self, history: Option>) { - self.state.lock().previous_poll_history = history; - } - - pub fn enable_runnable_backtrace(&self) { - self.state.lock().enable_runnable_backtraces = true; - } - - pub fn runnable_backtrace(&self, runnable_id: usize) -> backtrace::Backtrace { - let mut backtrace = self.state.lock().runnable_backtraces[&runnable_id].clone(); - backtrace.resolve(); - backtrace - } - - pub fn build_background(self: &Arc) -> Arc { - Arc::new(BackgroundExecutor::Deterministic { - executor: self.clone(), - }) - } - - pub fn build_foreground(self: &Arc, id: usize) -> Rc { - Rc::new(ForegroundExecutor::Deterministic { - cx_id: id, - executor: self.clone(), - }) - } - - fn spawn_from_foreground( - &self, - cx_id: usize, - future: AnyLocalFuture, - main: bool, - ) -> AnyLocalTask { - let state = self.state.clone(); - let id; - { - let mut state = state.lock(); - id = util::post_inc(&mut state.next_runnable_id); - if state.enable_runnable_backtraces { - state - .runnable_backtraces - .insert(id, backtrace::Backtrace::new_unresolved()); - } - } - - let unparker = self.parker.lock().unparker(); - let (runnable, task) = async_task::spawn_local(future, move |runnable| { - let mut state = state.lock(); - state.push_to_history(ExecutorEvent::EnqueuRunnable { id }); - state - .scheduled_from_foreground - .entry(cx_id) - .or_default() - .push(ForegroundRunnable { id, runnable, main }); - unparker.unpark(); - }); - runnable.schedule(); - task - } - - fn spawn(&self, future: AnyFuture) -> AnyTask { - let state = self.state.clone(); - let id; - { - let mut state = state.lock(); - id = util::post_inc(&mut state.next_runnable_id); - if state.enable_runnable_backtraces { - state - .runnable_backtraces - .insert(id, backtrace::Backtrace::new_unresolved()); - } - } - - let unparker = self.parker.lock().unparker(); - let (runnable, task) = async_task::spawn(future, move |runnable| { - let mut state = state.lock(); - state - .poll_history - .push(ExecutorEvent::EnqueuRunnable { id }); - state - .scheduled_from_background - .push(BackgroundRunnable { id, runnable }); - unparker.unpark(); - }); - runnable.schedule(); - task - } - - fn run<'a>( - &self, - cx_id: usize, - main_future: Pin>>>, - ) -> Box { - use std::sync::atomic::{AtomicBool, Ordering::SeqCst}; - - let woken = Arc::new(AtomicBool::new(false)); - - let state = self.state.clone(); - let id; - { - let mut state = state.lock(); - id = util::post_inc(&mut state.next_runnable_id); - if state.enable_runnable_backtraces { - state - .runnable_backtraces - .insert(id, backtrace::Backtrace::new_unresolved()); - } - } - - let unparker = self.parker.lock().unparker(); - let (runnable, mut main_task) = unsafe { - async_task::spawn_unchecked(main_future, move |runnable| { - let state = &mut *state.lock(); - state - .scheduled_from_foreground - .entry(cx_id) - .or_default() - .push(ForegroundRunnable { - id: util::post_inc(&mut state.next_runnable_id), - runnable, - main: true, - }); - unparker.unpark(); - }) - }; - runnable.schedule(); - - loop { - if let Some(result) = self.run_internal(woken.clone(), Some(&mut main_task)) { - return result; - } - - if !woken.load(SeqCst) { - self.state.lock().will_park(); - } - - woken.store(false, SeqCst); - self.parker.lock().park(); - } - } - - pub fn run_until_parked(&self) { - use std::sync::atomic::AtomicBool; - let woken = Arc::new(AtomicBool::new(false)); - self.run_internal(woken, None); - } - - fn run_internal( - &self, - woken: Arc, - mut main_task: Option<&mut AnyLocalTask>, - ) -> Option> { - use rand::prelude::*; - use std::sync::atomic::Ordering::SeqCst; - - let unparker = self.parker.lock().unparker(); - let waker = waker_fn::waker_fn(move || { - woken.store(true, SeqCst); - unparker.unpark(); - }); - - let mut cx = Context::from_waker(&waker); - loop { - let mut state = self.state.lock(); - - if state.scheduled_from_foreground.is_empty() - && state.scheduled_from_background.is_empty() - { - if let Some(main_task) = main_task { - if let Poll::Ready(result) = main_task.poll(&mut cx) { - return Some(result); - } - } - - return None; - } - - if !state.scheduled_from_background.is_empty() && state.rng.gen() { - let background_len = state.scheduled_from_background.len(); - let ix = state.rng.gen_range(0..background_len); - let background_runnable = state.scheduled_from_background.remove(ix); - state.push_to_history(ExecutorEvent::PollRunnable { - id: background_runnable.id, - }); - drop(state); - background_runnable.runnable.run(); - } else if !state.scheduled_from_foreground.is_empty() { - let available_cx_ids = state - .scheduled_from_foreground - .keys() - .copied() - .collect::>(); - let cx_id_to_run = *available_cx_ids.iter().choose(&mut state.rng).unwrap(); - let scheduled_from_cx = state - .scheduled_from_foreground - .get_mut(&cx_id_to_run) - .unwrap(); - let foreground_runnable = scheduled_from_cx.remove(0); - if scheduled_from_cx.is_empty() { - state.scheduled_from_foreground.remove(&cx_id_to_run); - } - state.push_to_history(ExecutorEvent::PollRunnable { - id: foreground_runnable.id, - }); - - drop(state); - - foreground_runnable.runnable.run(); - if let Some(main_task) = main_task.as_mut() { - if foreground_runnable.main { - if let Poll::Ready(result) = main_task.poll(&mut cx) { - return Some(result); - } - } - } - } - } - } - - fn block(&self, future: &mut F, max_ticks: usize) -> Option - where - F: Unpin + Future, - { - use rand::prelude::*; - - let unparker = self.parker.lock().unparker(); - let waker = waker_fn::waker_fn(move || { - unparker.unpark(); - }); - - let mut cx = Context::from_waker(&waker); - for _ in 0..max_ticks { - let mut state = self.state.lock(); - let runnable_count = state.scheduled_from_background.len(); - let ix = state.rng.gen_range(0..=runnable_count); - if ix < state.scheduled_from_background.len() { - let background_runnable = state.scheduled_from_background.remove(ix); - state.push_to_history(ExecutorEvent::PollRunnable { - id: background_runnable.id, - }); - drop(state); - background_runnable.runnable.run(); - } else { - drop(state); - if let Poll::Ready(result) = future.poll(&mut cx) { - return Some(result); - } - let mut state = self.state.lock(); - if state.scheduled_from_background.is_empty() { - state.will_park(); - drop(state); - self.parker.lock().park(); - } - - continue; - } - } - - None - } - - pub fn timer(&self, duration: Duration) -> Timer { - let (tx, rx) = postage::barrier::channel(); - let mut state = self.state.lock(); - let wakeup_at = state.now + duration; - let id = util::post_inc(&mut state.next_timer_id); - match state - .pending_timers - .binary_search_by_key(&wakeup_at, |e| e.1) - { - Ok(ix) | Err(ix) => state.pending_timers.insert(ix, (id, wakeup_at, tx)), - } - let state = self.state.clone(); - Timer::Deterministic(DeterministicTimer { rx, id, state }) - } - - pub fn now(&self) -> std::time::Instant { - let state = self.state.lock(); - state.now - } - - pub fn advance_clock(&self, duration: Duration) { - let new_now = self.state.lock().now + duration; - loop { - self.run_until_parked(); - let mut state = self.state.lock(); - - if let Some((_, wakeup_time, _)) = state.pending_timers.first() { - let wakeup_time = *wakeup_time; - if wakeup_time <= new_now { - let timer_count = state - .pending_timers - .iter() - .take_while(|(_, t, _)| *t == wakeup_time) - .count(); - state.now = wakeup_time; - let timers_to_wake = state - .pending_timers - .drain(0..timer_count) - .collect::>(); - drop(state); - drop(timers_to_wake); - continue; - } - } - - break; - } - - self.state.lock().now = new_now; - } - - pub fn start_waiting(&self) { - self.state.lock().waiting_backtrace = Some(backtrace::Backtrace::new_unresolved()); - } - - pub fn finish_waiting(&self) { - self.state.lock().waiting_backtrace.take(); - } - - pub fn forbid_parking(&self) { - use rand::prelude::*; - - let mut state = self.state.lock(); - state.forbid_parking = true; - state.rng = StdRng::seed_from_u64(state.seed); - } - - pub fn allow_parking(&self) { - use rand::prelude::*; - - let mut state = self.state.lock(); - state.forbid_parking = false; - state.rng = StdRng::seed_from_u64(state.seed); - } - - pub async fn simulate_random_delay(&self) { - use rand::prelude::*; - use smol::future::yield_now; - if self.state.lock().rng.gen_bool(0.2) { - let yields = self.state.lock().rng.gen_range(1..=10); - for _ in 0..yields { - yield_now().await; - } - } - } - - pub fn record_backtrace(&self) { - let mut state = self.state.lock(); - if state.enable_runnable_backtraces { - let current_id = state - .poll_history - .iter() - .rev() - .find_map(|event| match event { - ExecutorEvent::PollRunnable { id } => Some(*id), - _ => None, - }); - if let Some(id) = current_id { - state - .runnable_backtraces - .insert(id, backtrace::Backtrace::new_unresolved()); - } - } - } -} - -impl Drop for Timer { - fn drop(&mut self) { - #[cfg(any(test, feature = "test"))] - if let Timer::Deterministic(DeterministicTimer { state, id, .. }) = self { - state - .lock() - .pending_timers - .retain(|(timer_id, _, _)| timer_id != id) - } - } -} - -impl Future for Timer { - type Output = (); - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - match &mut *self { - #[cfg(any(test, feature = "test"))] - Self::Deterministic(DeterministicTimer { rx, .. }) => { - use postage::stream::{PollRecv, Stream as _}; - smol::pin!(rx); - match rx.poll_recv(&mut postage::Context::from_waker(cx.waker())) { - PollRecv::Ready(()) | PollRecv::Closed => Poll::Ready(()), - PollRecv::Pending => Poll::Pending, - } - } - Self::Production(timer) => { - smol::pin!(timer); - match timer.poll(cx) { - Poll::Ready(_) => Poll::Ready(()), - Poll::Pending => Poll::Pending, - } - } - } - } + Spawned(async_task::Task), } -#[cfg(any(test, feature = "test"))] -impl DeterministicState { - fn push_to_history(&mut self, event: ExecutorEvent) { - use std::fmt::Write as _; - - self.poll_history.push(event); - if let Some(prev_history) = &self.previous_poll_history { - let ix = self.poll_history.len() - 1; - let prev_event = prev_history[ix]; - if event != prev_event { - let mut message = String::new(); - writeln!( - &mut message, - "current runnable backtrace:\n{:?}", - self.runnable_backtraces.get_mut(&event.id()).map(|trace| { - trace.resolve(); - crate::util::CwdBacktrace(trace) - }) - ) - .unwrap(); - writeln!( - &mut message, - "previous runnable backtrace:\n{:?}", - self.runnable_backtraces - .get_mut(&prev_event.id()) - .map(|trace| { - trace.resolve(); - util::CwdBacktrace(trace) - }) - ) - .unwrap(); - panic!("detected non-determinism after {ix}. {message}"); - } - } - } - - fn will_park(&mut self) { - if self.forbid_parking { - let mut backtrace_message = String::new(); - #[cfg(any(test, feature = "test"))] - if let Some(backtrace) = self.waiting_backtrace.as_mut() { - backtrace.resolve(); - backtrace_message = format!( - "\nbacktrace of waiting future:\n{:?}", - util::CwdBacktrace(backtrace) - ); - } - - panic!( - "deterministic executor parked after a call to forbid_parking{}", - backtrace_message - ); - } +impl Task { + pub fn ready(val: T) -> Self { + Task::Ready(Some(val)) } -} -#[cfg(any(test, feature = "test"))] -impl ExecutorEvent { - pub fn id(&self) -> usize { + pub fn detach(self) { match self { - ExecutorEvent::PollRunnable { id } => *id, - ExecutorEvent::EnqueuRunnable { id } => *id, + Task::Ready(_) => {} + Task::Spawned(task) => task.detach(), } } } -impl ForegroundExecutor { - pub fn new(dispatcher: Arc) -> Result { - if dispatcher.is_main_thread() { - Ok(Self::Platform { - dispatcher, - _not_send_or_sync: PhantomData, - }) - } else { - Err(anyhow!("must be constructed on main thread")) - } - } - - pub fn spawn(&self, future: impl Future + 'static) -> Task { - let future = any_local_future(future); - let any_task = match self { - #[cfg(any(test, feature = "test"))] - Self::Deterministic { cx_id, executor } => { - executor.spawn_from_foreground(*cx_id, future, false) - } - Self::Platform { dispatcher, .. } => { - fn spawn_inner( - future: AnyLocalFuture, - dispatcher: &Arc, - ) -> AnyLocalTask { - let dispatcher = dispatcher.clone(); - let schedule = - move |runnable: Runnable| dispatcher.run_on_main_thread(runnable); - let (runnable, task) = async_task::spawn_local(future, schedule); - runnable.schedule(); - task - } - spawn_inner(future, dispatcher) - } - }; - Task::local(any_task) - } - - #[cfg(any(test, feature = "test"))] - pub fn run(&self, future: impl Future) -> T { - let future = async move { Box::new(future.await) as Box }.boxed_local(); - let result = match self { - Self::Deterministic { cx_id, executor } => executor.run(*cx_id, future), - Self::Platform { .. } => panic!("you can't call run on a platform foreground executor"), - }; - *result.downcast().unwrap() - } - - #[cfg(any(test, feature = "test"))] - pub fn run_until_parked(&self) { - match self { - Self::Deterministic { executor, .. } => executor.run_until_parked(), - _ => panic!("this method can only be called on a deterministic executor"), - } - } - - #[cfg(any(test, feature = "test"))] - pub fn parking_forbidden(&self) -> bool { - match self { - Self::Deterministic { executor, .. } => executor.state.lock().forbid_parking, - _ => panic!("this method can only be called on a deterministic executor"), - } - } - - #[cfg(any(test, feature = "test"))] - pub fn start_waiting(&self) { - match self { - Self::Deterministic { executor, .. } => executor.start_waiting(), - _ => panic!("this method can only be called on a deterministic executor"), - } - } - - #[cfg(any(test, feature = "test"))] - pub fn finish_waiting(&self) { - match self { - Self::Deterministic { executor, .. } => executor.finish_waiting(), - _ => panic!("this method can only be called on a deterministic executor"), - } - } - - #[cfg(any(test, feature = "test"))] - pub fn forbid_parking(&self) { - match self { - Self::Deterministic { executor, .. } => executor.forbid_parking(), - _ => panic!("this method can only be called on a deterministic executor"), - } - } - - #[cfg(any(test, feature = "test"))] - pub fn allow_parking(&self) { - match self { - Self::Deterministic { executor, .. } => executor.allow_parking(), - _ => panic!("this method can only be called on a deterministic executor"), - } - } - - #[cfg(any(test, feature = "test"))] - pub fn advance_clock(&self, duration: Duration) { - match self { - Self::Deterministic { executor, .. } => executor.advance_clock(duration), - _ => panic!("this method can only be called on a deterministic executor"), - } - } +impl Future for Task { + type Output = T; - #[cfg(any(test, feature = "test"))] - pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive) { - match self { - Self::Deterministic { executor, .. } => executor.state.lock().block_on_ticks = range, - _ => panic!("this method can only be called on a deterministic executor"), + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + match unsafe { self.get_unchecked_mut() } { + Task::Ready(val) => Poll::Ready(val.take().unwrap()), + Task::Spawned(task) => task.poll(cx), } } } -impl BackgroundExecutor { - pub fn new() -> Self { - let executor = Arc::new(Executor::new()); - let stop = channel::unbounded::<()>(); - - for i in 0..2 * num_cpus::get() { - let executor = executor.clone(); - let stop = stop.1.clone(); - thread::Builder::new() - .name(format!("background-executor-{}", i)) - .spawn(move || smol::block_on(executor.run(stop.recv()))) - .unwrap(); - } - - Self::Production { - executor, - _stop: stop.0, - } - } - - pub fn num_cpus(&self) -> usize { - num_cpus::get() +impl Executor { + pub fn new(dispatcher: Arc) -> Self { + Self { dispatcher } } - pub fn spawn(&self, future: F) -> Task + /// Enqueues the given closure to be run on any thread. The closure returns + /// a future which will be run to completion on any available thread. + pub fn spawn(&self, future: impl Future + Send + 'static) -> Task where - T: 'static + Send, - F: Send + Future + 'static, + R: Send + 'static, { - let future = any_future(future); - let any_task = match self { - Self::Production { executor, .. } => executor.spawn(future), - #[cfg(any(test, feature = "test"))] - Self::Deterministic { executor } => executor.spawn(future), - }; - Task::send(any_task) - } - - pub fn block(&self, future: F) -> T - where - F: Future, - { - smol::pin!(future); - match self { - Self::Production { .. } => smol::block_on(&mut future), - #[cfg(any(test, feature = "test"))] - Self::Deterministic { executor, .. } => { - executor.block(&mut future, usize::MAX).unwrap() - } - } + let dispatcher = self.dispatcher.clone(); + let (runnable, task) = + async_task::spawn(future, move |runnable| dispatcher.dispatch(runnable)); + runnable.schedule(); + Task::Spawned(task) } - pub fn block_with_timeout( - &self, - timeout: Duration, - future: F, - ) -> Result> + /// Enqueues the given closure to run on the application's event loop. + /// Returns the result asynchronously. + pub fn run_on_main(&self, func: F) -> Task where - T: 'static, - F: 'static + Unpin + Future, + F: FnOnce() -> R + Send + 'static, + R: Send + 'static, { - let mut future = any_local_future(future); - if !timeout.is_zero() { - let output = match self { - Self::Production { .. } => smol::block_on(util::timeout(timeout, &mut future)).ok(), - #[cfg(any(test, feature = "test"))] - Self::Deterministic { executor, .. } => { - use rand::prelude::*; - let max_ticks = { - let mut state = executor.state.lock(); - let range = state.block_on_ticks.clone(); - state.rng.gen_range(range) - }; - executor.block(&mut future, max_ticks) - } - }; - if let Some(output) = output { - return Ok(*output.downcast().unwrap()); - } + if self.dispatcher.is_main_thread() { + Task::ready(func()) + } else { + self.spawn_on_main(move || async move { func() }) } - Err(async { *future.await.downcast().unwrap() }) } - pub async fn scoped<'scope, F>(self: &Arc, scheduler: F) + /// Enqueues the given closure to be run on the application's event loop. The + /// closure returns a future which will be run to completion on the main thread. + pub fn spawn_on_main(&self, func: impl FnOnce() -> F + Send + 'static) -> Task where - F: FnOnce(&mut Scope<'scope>), + F: Future + 'static, + R: Send + 'static, { - let mut scope = Scope::new(self.clone()); - (scheduler)(&mut scope); - let spawned = mem::take(&mut scope.futures) - .into_iter() - .map(|f| self.spawn(f)) - .collect::>(); - for task in spawned { - task.await; - } - } - - pub fn timer(&self, duration: Duration) -> Timer { - match self { - BackgroundExecutor::Production { .. } => { - Timer::Production(smol::Timer::after(duration)) - } - #[cfg(any(test, feature = "test"))] - BackgroundExecutor::Deterministic { executor } => executor.timer(duration), - } - } - - pub fn now(&self) -> std::time::Instant { - match self { - BackgroundExecutor::Production { .. } => std::time::Instant::now(), - #[cfg(any(test, feature = "test"))] - BackgroundExecutor::Deterministic { executor } => executor.now(), - } - } - - #[cfg(any(test, feature = "test"))] - pub fn rng<'a>(&'a self) -> impl 'a + std::ops::DerefMut { - match self { - Self::Deterministic { executor, .. } => { - parking_lot::lock_api::MutexGuard::map(executor.state.lock(), |s| &mut s.rng) - } - _ => panic!("this method can only be called on a deterministic executor"), - } - } - - #[cfg(any(test, feature = "test"))] - pub async fn simulate_random_delay(&self) { - match self { - Self::Deterministic { executor, .. } => { - executor.simulate_random_delay().await; - } - _ => { - panic!("this method can only be called on a deterministic executor") - } - } - } - - #[cfg(any(test, feature = "test"))] - pub fn record_backtrace(&self) { - match self { - Self::Deterministic { executor, .. } => executor.record_backtrace(), - _ => { - panic!("this method can only be called on a deterministic executor") - } - } - } - - #[cfg(any(test, feature = "test"))] - pub fn start_waiting(&self) { - match self { - Self::Deterministic { executor, .. } => executor.start_waiting(), - _ => panic!("this method can only be called on a deterministic executor"), - } - } -} - -impl Default for BackgroundExecutor { - fn default() -> Self { - Self::new() - } -} - -pub struct Scope<'a> { - executor: Arc, - futures: Vec + Send + 'static>>>, - tx: Option>, - rx: mpsc::Receiver<()>, - _phantom: PhantomData<&'a ()>, -} - -impl<'a> Scope<'a> { - fn new(executor: Arc) -> Self { - let (tx, rx) = mpsc::channel(1); - Self { - executor, - tx: Some(tx), - rx, - futures: Default::default(), - _phantom: PhantomData, - } + let dispatcher = self.dispatcher.clone(); + let (runnable, task) = + async_task::spawn_local(async move { func().await }, move |runnable| { + dispatcher.dispatch_on_main_thread(runnable) + }); + runnable.schedule(); + Task::Spawned(task) } - pub fn spawn(&mut self, f: F) + /// Enqueues the given closure to be run on the application's event loop. Must + /// be called on the main thread. + pub fn spawn_on_main_local(&self, future: impl Future + 'static) -> Task where - F: Future + Send + 'a, + R: 'static, { - let tx = self.tx.clone().unwrap(); - - // Safety: The 'a lifetime is guaranteed to outlive any of these futures because - // dropping this `Scope` blocks until all of the futures have resolved. - let f = unsafe { - mem::transmute::< - Pin + Send + 'a>>, - Pin + Send + 'static>>, - >(Box::pin(async move { - f.await; - drop(tx); - })) - }; - self.futures.push(f); - } -} - -impl<'a> Drop for Scope<'a> { - fn drop(&mut self) { - self.tx.take().unwrap(); - - // Wait until the channel is closed, which means that all of the spawned - // futures have resolved. - self.executor.block(self.rx.next()); - } -} + assert!( + self.dispatcher.is_main_thread(), + "must be called on main thread" + ); -impl Task { - pub fn ready(value: T) -> Self { - Self::Ready(Some(value)) - } - - fn local(any_task: AnyLocalTask) -> Self { - Self::Local { - any_task, - result_type: PhantomData, - } - } - - pub fn detach(self) { - match self { - Task::Ready(_) => {} - Task::Local { any_task, .. } => any_task.detach(), - Task::Send { any_task, .. } => any_task.detach(), - } - } -} - -// impl Task> { -// #[track_caller] -// pub fn detach_and_log_err(self, cx: &mut AppContext) { -// let caller = Location::caller(); -// cx.spawn(|_| async move { -// if let Err(err) = self.await { -// log::error!("{}:{}: {:#}", caller.file(), caller.line(), err); -// } -// }) -// .detach(); -// } -// } - -impl Task { - fn send(any_task: AnyTask) -> Self { - Self::Send { - any_task, - result_type: PhantomData, - } - } -} - -impl fmt::Debug for Task { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Task::Ready(value) => value.fmt(f), - Task::Local { any_task, .. } => any_task.fmt(f), - Task::Send { any_task, .. } => any_task.fmt(f), - } + let dispatcher = self.dispatcher.clone(); + let (runnable, task) = async_task::spawn_local(future, move |runnable| { + dispatcher.dispatch_on_main_thread(runnable) + }); + runnable.schedule(); + Task::Spawned(task) } -} -impl Future for Task { - type Output = T; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - match unsafe { self.get_unchecked_mut() } { - Task::Ready(value) => Poll::Ready(value.take().unwrap()), - Task::Local { any_task, .. } => { - any_task.poll(cx).map(|value| *value.downcast().unwrap()) - } - Task::Send { any_task, .. } => { - any_task.poll(cx).map(|value| *value.downcast().unwrap()) - } - } + pub fn is_main_thread(&self) -> bool { + self.dispatcher.is_main_thread() } } - -fn any_future(future: F) -> AnyFuture -where - T: 'static + Send, - F: Future + Send + 'static, -{ - async { Box::new(future.await) as Box }.boxed() -} - -fn any_local_future(future: F) -> AnyLocalFuture -where - T: 'static, - F: Future + 'static, -{ - async { Box::new(future.await) as Box }.boxed_local() -} diff --git a/crates/gpui3/src/gpui3.rs b/crates/gpui3/src/gpui3.rs index 7c8c7c498c3303c9cd3e5c226d1375151c7f03e8..cb14679948f3b1f9905dba885e352b0263945825 100644 --- a/crates/gpui3/src/gpui3.rs +++ b/crates/gpui3/src/gpui3.rs @@ -227,14 +227,14 @@ impl<'a, T> DerefMut for Reference<'a, T> { } pub(crate) struct MainThreadOnly { - dispatcher: Arc, + executor: Executor, value: Arc, } impl Clone for MainThreadOnly { fn clone(&self) -> Self { Self { - dispatcher: self.dispatcher.clone(), + executor: self.executor.clone(), value: self.value.clone(), } } @@ -243,12 +243,12 @@ impl Clone for MainThreadOnly { /// Allows a value to be accessed only on the main thread, allowing a non-`Send` type /// to become `Send`. impl MainThreadOnly { - pub(crate) fn new(value: Arc, dispatcher: Arc) -> Self { - Self { dispatcher, value } + pub(crate) fn new(value: Arc, executor: Executor) -> Self { + Self { executor, value } } pub(crate) fn borrow_on_main_thread(&self) -> &T { - assert!(self.dispatcher.is_main_thread()); + assert!(self.executor.is_main_thread()); &self.value } } diff --git a/crates/gpui3/src/platform.rs b/crates/gpui3/src/platform.rs index 91de2805606f8718966c6700a5064cbc06d9a844..9feec5a9c50dad1437377e31d128c5249a417787 100644 --- a/crates/gpui3/src/platform.rs +++ b/crates/gpui3/src/platform.rs @@ -7,8 +7,8 @@ mod test; use crate::image_cache::RenderImageParams; use crate::{ - AnyWindowHandle, Bounds, DevicePixels, Font, FontId, FontMetrics, GlyphId, Pixels, Point, - RenderGlyphParams, RenderSvgParams, Result, Scene, ShapedLine, SharedString, Size, + AnyWindowHandle, Bounds, DevicePixels, Executor, Font, FontId, FontMetrics, GlyphId, Pixels, + Point, RenderGlyphParams, RenderSvgParams, Result, Scene, ShapedLine, SharedString, Size, }; use anyhow::anyhow; use async_task::Runnable; @@ -43,7 +43,7 @@ pub(crate) fn current_platform() -> Arc { } pub trait Platform: 'static { - fn dispatcher(&self) -> Arc; + fn executor(&self) -> Executor; fn text_system(&self) -> Arc; fn run(&self, on_finish_launching: Box); @@ -154,7 +154,8 @@ pub trait PlatformWindow { pub trait PlatformDispatcher: Send + Sync { fn is_main_thread(&self) -> bool; - fn run_on_main_thread(&self, task: Runnable); + fn dispatch(&self, task: Runnable); + fn dispatch_on_main_thread(&self, task: Runnable); } pub trait PlatformTextSystem: Send + Sync { diff --git a/crates/gpui3/src/platform/mac/dispatcher.rs b/crates/gpui3/src/platform/mac/dispatcher.rs index f20ba602b0de3c4e510f1a069fdde557ad259b0e..df2f4785215228cc1820eeefcbc85e79a3858503 100644 --- a/crates/gpui3/src/platform/mac/dispatcher.rs +++ b/crates/gpui3/src/platform/mac/dispatcher.rs @@ -25,18 +25,49 @@ impl PlatformDispatcher for MacDispatcher { is_main_thread == YES } - fn run_on_main_thread(&self, runnable: Runnable) { + fn dispatch(&self, runnable: Runnable) { unsafe { dispatch_async_f( - dispatch_get_main_queue(), + dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.try_into().unwrap(), 0), runnable.into_raw() as *mut c_void, Some(trampoline), ); } + } - extern "C" fn trampoline(runnable: *mut c_void) { - let task = unsafe { Runnable::from_raw(runnable as *mut ()) }; - task.run(); + fn dispatch_on_main_thread(&self, runnable: Runnable) { + unsafe { + dispatch_async_f( + dispatch_get_main_queue(), + runnable.into_raw() as *mut c_void, + Some(trampoline), + ); } } } + +extern "C" fn trampoline(runnable: *mut c_void) { + let task = unsafe { Runnable::from_raw(runnable as *mut ()) }; + task.run(); +} + +// #include + +// int main(void) { + +// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ +// // Do some lengthy background work here... +// printf("Background Work\n"); + +// dispatch_async(dispatch_get_main_queue(), ^{ +// // Once done, update your UI on the main queue here. +// printf("UI Updated\n"); + +// }); +// }); + +// sleep(3); // prevent the program from terminating immediately + +// return 0; +// } +// ``` diff --git a/crates/gpui3/src/platform/mac/platform.rs b/crates/gpui3/src/platform/mac/platform.rs index cde57f8bcfdd262fbd14b1326c4c7637f853dfc4..d4110a114ed6f9dfacc4e77827b40faab6d3563d 100644 --- a/crates/gpui3/src/platform/mac/platform.rs +++ b/crates/gpui3/src/platform/mac/platform.rs @@ -1,8 +1,8 @@ use super::BoolExt; use crate::{ - AnyWindowHandle, ClipboardItem, CursorStyle, Event, MacDispatcher, MacScreen, MacTextSystem, - MacWindow, PathPromptOptions, Platform, PlatformScreen, PlatformTextSystem, PlatformWindow, - Result, ScreenId, SemanticVersion, WindowOptions, + AnyWindowHandle, ClipboardItem, CursorStyle, Event, Executor, MacDispatcher, MacScreen, + MacTextSystem, MacWindow, PathPromptOptions, Platform, PlatformScreen, PlatformTextSystem, + PlatformWindow, Result, ScreenId, SemanticVersion, WindowOptions, }; use anyhow::anyhow; use block::ConcreteBlock; @@ -142,7 +142,7 @@ unsafe fn build_classes() { pub struct MacPlatform(Mutex); pub struct MacPlatformState { - dispatcher: Arc, + executor: Executor, text_system: Arc, pasteboard: id, text_hash_pasteboard_type: id, @@ -163,7 +163,7 @@ pub struct MacPlatformState { impl MacPlatform { pub fn new() -> Self { Self(Mutex::new(MacPlatformState { - dispatcher: Arc::new(MacDispatcher), + executor: Executor::new(Arc::new(MacDispatcher)), text_system: Arc::new(MacTextSystem::new()), pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) }, text_hash_pasteboard_type: unsafe { ns_string("zed-text-hash") }, @@ -343,8 +343,8 @@ impl MacPlatform { } impl Platform for MacPlatform { - fn dispatcher(&self) -> Arc { - Arc::new(MacDispatcher) + fn executor(&self) -> Executor { + self.0.lock().executor.clone() } fn text_system(&self) -> Arc { @@ -479,7 +479,7 @@ impl Platform for MacPlatform { handle: AnyWindowHandle, options: WindowOptions, ) -> Box { - Box::new(MacWindow::open(handle, options, self)) + Box::new(MacWindow::open(handle, options, self.executor())) } fn open_url(&self, url: &str) { @@ -566,17 +566,20 @@ impl Platform for MacPlatform { fn reveal_path(&self, path: &Path) { unsafe { let path = path.to_path_buf(); - let dispatcher = self.0.lock().dispatcher.clone(); - let _ = crate::spawn_on_main_local(dispatcher, async move { - let full_path = ns_string(path.to_str().unwrap_or("")); - let root_full_path = ns_string(""); - let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace]; - let _: BOOL = msg_send![ - workspace, - selectFile: full_path - inFileViewerRootedAtPath: root_full_path - ]; - }); + self.0 + .lock() + .executor + .spawn_on_main_local(async move { + let full_path = ns_string(path.to_str().unwrap_or("")); + let root_full_path = ns_string(""); + let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace]; + let _: BOOL = msg_send![ + workspace, + selectFile: full_path + inFileViewerRootedAtPath: root_full_path + ]; + }) + .detach(); } } diff --git a/crates/gpui3/src/platform/mac/window.rs b/crates/gpui3/src/platform/mac/window.rs index 6d52e44316b49ed7833de4adbfb032d8464d49b9..080aa74350551728a2226bb32d8da16cb9db4811 100644 --- a/crates/gpui3/src/platform/mac/window.rs +++ b/crates/gpui3/src/platform/mac/window.rs @@ -1,10 +1,10 @@ use super::{ns_string, MetalRenderer, NSRange}; use crate::{ - point, px, size, AnyWindowHandle, Bounds, Event, KeyDownEvent, Keystroke, MacScreen, Modifiers, - ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMovedEvent, MouseUpEvent, NSRectExt, - Pixels, Platform, PlatformAtlas, PlatformDispatcher, PlatformInputHandler, PlatformScreen, - PlatformWindow, Point, Scene, Size, Timer, WindowAppearance, WindowBounds, WindowKind, - WindowOptions, WindowPromptLevel, + point, px, size, AnyWindowHandle, Bounds, Event, Executor, KeyDownEvent, Keystroke, MacScreen, + Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMovedEvent, MouseUpEvent, + NSRectExt, Pixels, PlatformAtlas, PlatformInputHandler, PlatformScreen, PlatformWindow, Point, + Scene, Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions, + WindowPromptLevel, }; use block::ConcreteBlock; use cocoa::{ @@ -279,7 +279,7 @@ struct InsertText { struct MacWindowState { handle: AnyWindowHandle, - dispatcher: Arc, + executor: Executor, native_window: id, renderer: MetalRenderer, scene_to_render: Option, @@ -415,7 +415,7 @@ unsafe impl Send for MacWindowState {} pub struct MacWindow(Arc>); impl MacWindow { - pub fn open(handle: AnyWindowHandle, options: WindowOptions, platform: &dyn Platform) -> Self { + pub fn open(handle: AnyWindowHandle, options: WindowOptions, executor: Executor) -> Self { unsafe { let pool = NSAutoreleasePool::new(nil); @@ -479,7 +479,7 @@ impl MacWindow { let window = Self(Arc::new(Mutex::new(MacWindowState { handle, - dispatcher: platform.dispatcher(), + executor, native_window, renderer: MetalRenderer::new(true), scene_to_render: None, @@ -616,12 +616,12 @@ impl MacWindow { impl Drop for MacWindow { fn drop(&mut self) { let this = self.0.clone(); - let dispatcher = self.0.lock().dispatcher.clone(); - let _ = crate::spawn_on_main(dispatcher, || async move { - unsafe { + let executor = self.0.lock().executor.clone(); + executor + .run_on_main(move || unsafe { this.lock().native_window.close(); - } - }); + }) + .detach(); } } @@ -739,14 +739,16 @@ impl PlatformWindow for MacWindow { }); let block = block.copy(); let native_window = self.0.lock().native_window; - let dispatcher = self.0.lock().dispatcher.clone(); - let _ = crate::spawn_on_main_local(dispatcher, async move { - let _: () = msg_send![ - alert, - beginSheetModalForWindow: native_window - completionHandler: block - ]; - }); + let executor = self.0.lock().executor.clone(); + executor + .spawn_on_main_local(async move { + let _: () = msg_send![ + alert, + beginSheetModalForWindow: native_window + completionHandler: block + ]; + }) + .detach(); done_rx } @@ -754,12 +756,14 @@ impl PlatformWindow for MacWindow { fn activate(&self) { let window = self.0.lock().native_window; - let dispatcher = self.0.lock().dispatcher.clone(); - let _ = crate::spawn_on_main_local(dispatcher.clone(), async move { - unsafe { - let _: () = msg_send![window, makeKeyAndOrderFront: nil]; - } - }); + let executor = self.0.lock().executor.clone(); + executor + .spawn_on_main_local(async move { + unsafe { + let _: () = msg_send![window, makeKeyAndOrderFront: nil]; + } + }) + .detach(); } fn set_title(&mut self, title: &str) { @@ -802,23 +806,25 @@ impl PlatformWindow for MacWindow { fn zoom(&self) { let this = self.0.lock(); let window = this.native_window; - let dispatcher = this.dispatcher.clone(); - let _ = crate::spawn_on_main_local(dispatcher, async move { - unsafe { - window.zoom_(nil); - } - }); + this.executor + .spawn_on_main_local(async move { + unsafe { + window.zoom_(nil); + } + }) + .detach(); } fn toggle_full_screen(&self) { let this = self.0.lock(); let window = this.native_window; - let dispatcher = this.dispatcher.clone(); - let _ = crate::spawn_on_main_local(dispatcher, async move { - unsafe { - window.toggleFullScreen_(nil); - } - }); + this.executor + .spawn_on_main_local(async move { + unsafe { + window.toggleFullScreen_(nil); + } + }) + .detach(); } fn on_event(&self, callback: Box bool>) { @@ -1114,15 +1120,14 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { }, ) => { lock.synthetic_drag_counter += 1; - let dispatcher = lock.dispatcher.clone(); - let _ = crate::spawn_on_main_local( - dispatcher, - synthetic_drag( + let executor = lock.executor.clone(); + executor + .spawn_on_main_local(synthetic_drag( weak_window_state, lock.synthetic_drag_counter, event.clone(), - ), - ); + )) + .detach(); } Event::MouseMoved(_) if !(is_active || lock.kind == WindowKind::PopUp) => return, @@ -1241,16 +1246,18 @@ extern "C" fn window_did_change_key_status(this: &Object, selector: Sel, _: id) } } - let dispatcher = lock.dispatcher.clone(); + let executor = lock.executor.clone(); drop(lock); - let _ = crate::spawn_on_main_local(dispatcher, async move { - let mut lock = window_state.as_ref().lock(); - if let Some(mut callback) = lock.activate_callback.take() { - drop(lock); - callback(is_active); - window_state.lock().activate_callback = Some(callback); - }; - }); + executor + .spawn_on_main_local(async move { + let mut lock = window_state.as_ref().lock(); + if let Some(mut callback) = lock.activate_callback.take() { + drop(lock); + callback(is_active); + window_state.lock().activate_callback = Some(callback); + }; + }) + .detach(); } extern "C" fn window_should_close(this: &Object, _: Sel, _: id) -> BOOL { diff --git a/crates/gpui3/src/platform/test.rs b/crates/gpui3/src/platform/test.rs index 3b709f503f5211445fd08b790357260cc2cd75fb..e170e21e9554ff3faed657936fca613e80ba2cd4 100644 --- a/crates/gpui3/src/platform/test.rs +++ b/crates/gpui3/src/platform/test.rs @@ -1,5 +1,5 @@ use super::Platform; -use crate::ScreenId; +use crate::{Executor, ScreenId}; pub struct TestPlatform; @@ -11,7 +11,7 @@ impl TestPlatform { // todo!("implement out what our tests needed in GPUI 1") impl Platform for TestPlatform { - fn dispatcher(&self) -> std::sync::Arc { + fn executor(&self) -> Executor { unimplemented!() } diff --git a/crates/gpui3/src/util.rs b/crates/gpui3/src/util.rs index c76408392402a7a151ec93f54ea0a6aaeca0b720..5e60ab091aaada1d43b5d8e4055b27a103ead1bc 100644 --- a/crates/gpui3/src/util.rs +++ b/crates/gpui3/src/util.rs @@ -1,18 +1,16 @@ -use smol::future::FutureExt; -use std::{future::Future, time::Duration}; pub use util::*; -pub async fn timeout(timeout: Duration, f: F) -> Result -where - F: Future, -{ - let timer = async { - smol::Timer::after(timeout).await; - Err(()) - }; - let future = async move { Ok(f.await) }; - timer.race(future).await -} +// pub async fn timeout(timeout: Duration, f: F) -> Result +// where +// F: Future, +// { +// let timer = async { +// smol::Timer::after(timeout).await; +// Err(()) +// }; +// let future = async move { Ok(f.await) }; +// timer.race(future).await +// } #[cfg(any(test, feature = "test"))] pub struct CwdBacktrace<'a>(pub &'a backtrace::Backtrace); diff --git a/crates/gpui3/src/window.rs b/crates/gpui3/src/window.rs index e1a75791e4590733f721bb0b346f12d2baafacd4..5cf40e0a6698480e0ffe17d2564cc03ec1f3d826 100644 --- a/crates/gpui3/src/window.rs +++ b/crates/gpui3/src/window.rs @@ -53,8 +53,7 @@ impl Window { } })); - let platform_window = - MainThreadOnly::new(Arc::new(platform_window), cx.platform().dispatcher()); + let platform_window = MainThreadOnly::new(Arc::new(platform_window), cx.executor.clone()); Window { handle, @@ -122,7 +121,7 @@ impl<'a, 'w> WindowContext<'a, 'w> { R: Send + 'static, { let (tx, rx) = oneshot::channel(); - if self.dispatcher.is_main_thread() { + if self.executor.is_main_thread() { let _ = tx.send(f(unsafe { mem::transmute::<&mut Self, &mut MainThread>(self) })); @@ -600,7 +599,7 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> { R: Send + 'static, { let (tx, rx) = oneshot::channel(); - if self.dispatcher.is_main_thread() { + if self.executor.is_main_thread() { let cx = unsafe { mem::transmute::<&mut Self, &mut MainThread>(self) }; let _ = tx.send(f(view, cx)); } else { From d28c81571c49782800fa4b2e25f172bf6402e487 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 4 Oct 2023 19:59:24 +0200 Subject: [PATCH 06/10] Checkpoint --- crates/gpui3/src/app.rs | 24 ++++++++---------------- crates/gpui3/src/window.rs | 36 ++++++++++++------------------------ 2 files changed, 20 insertions(+), 40 deletions(-) diff --git a/crates/gpui3/src/app.rs b/crates/gpui3/src/app.rs index a05f9bb792c1aed7813aff03f28d18c79d6e6b9f..592a6076defb6b43f879e08584d44ddc94ade01c 100644 --- a/crates/gpui3/src/app.rs +++ b/crates/gpui3/src/app.rs @@ -14,7 +14,7 @@ use crate::{ }; use anyhow::{anyhow, Result}; use collections::{HashMap, VecDeque}; -use futures::{channel::oneshot, Future}; +use futures::Future; use parking_lot::Mutex; use slotmap::SlotMap; use smallvec::SmallVec; @@ -191,29 +191,21 @@ impl AppContext { pub fn run_on_main( &mut self, f: impl FnOnce(&mut MainThread) -> R + Send + 'static, - ) -> impl Future + ) -> Task where R: Send + 'static, { - let (tx, rx) = oneshot::channel(); if self.executor.is_main_thread() { - let _ = tx.send(f(unsafe { + Task::ready(f(unsafe { mem::transmute::<&mut AppContext, &mut MainThread>(self) - })); + })) } else { let this = self.this.upgrade().unwrap(); - self.executor - .run_on_main(move || { - let cx = &mut *this.lock(); - cx.update(|cx| { - let _ = tx.send(f(unsafe { - mem::transmute::<&mut Self, &mut MainThread>(cx) - })); - }) - }) - .detach(); + self.executor.run_on_main(move || { + let cx = &mut *this.lock(); + cx.update(|cx| f(unsafe { mem::transmute::<&mut Self, &mut MainThread>(cx) })) + }) } - async move { rx.await.unwrap() } } pub fn spawn_on_main( diff --git a/crates/gpui3/src/window.rs b/crates/gpui3/src/window.rs index 5cf40e0a6698480e0ffe17d2564cc03ec1f3d826..3782dfe97a563b5e6ecc57ee13f834dc31ad66c4 100644 --- a/crates/gpui3/src/window.rs +++ b/crates/gpui3/src/window.rs @@ -3,11 +3,10 @@ use crate::{ Bounds, Context, Corners, DevicePixels, Effect, Element, EntityId, FontId, GlyphId, Handle, Hsla, ImageData, IsZero, LayerId, LayoutId, MainThread, MainThreadOnly, MonochromeSprite, Pixels, PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Reference, RenderGlyphParams, - RenderSvgParams, ScaledPixels, Scene, SharedString, Size, Style, TaffyLayoutEngine, WeakHandle, - WindowOptions, SUBPIXEL_VARIANTS, + RenderSvgParams, ScaledPixels, Scene, SharedString, Size, Style, TaffyLayoutEngine, Task, + WeakHandle, WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::Result; -use futures::{channel::oneshot, Future}; use smallvec::SmallVec; use std::{any::TypeId, borrow::Cow, marker::PhantomData, mem, sync::Arc}; use util::ResultExt; @@ -116,24 +115,18 @@ impl<'a, 'w> WindowContext<'a, 'w> { fn run_on_main( &mut self, f: impl FnOnce(&mut MainThread>) -> R + Send + 'static, - ) -> impl Future + ) -> Task> where R: Send + 'static, { - let (tx, rx) = oneshot::channel(); if self.executor.is_main_thread() { - let _ = tx.send(f(unsafe { + Task::ready(Ok(f(unsafe { mem::transmute::<&mut Self, &mut MainThread>(self) - })); + }))) } else { let id = self.window.handle.id; - let _ = self.app.run_on_main(move |cx| { - cx.update_window(id, |cx| { - let _ = tx.send(f(cx)); - }) - }); + self.app.run_on_main(move |cx| cx.update_window(id, f)) } - async move { rx.await.unwrap() } } pub fn request_layout( @@ -397,13 +390,14 @@ impl<'a, 'w> WindowContext<'a, 'w> { cx.window.root_view = Some(root_view); let scene = cx.window.scene.take(); - let _ = cx.run_on_main(view, |_, cx| { + cx.run_on_main(view, |_, cx| { cx.window .platform_window .borrow_on_main_thread() .draw(scene); cx.window.dirty = false; - }); + }) + .detach(); Ok(()) }) @@ -594,23 +588,17 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> { &mut self, view: &mut S, f: impl FnOnce(&mut S, &mut MainThread>) -> R + Send + 'static, - ) -> impl Future + ) -> Task> where R: Send + 'static, { - let (tx, rx) = oneshot::channel(); if self.executor.is_main_thread() { let cx = unsafe { mem::transmute::<&mut Self, &mut MainThread>(self) }; - let _ = tx.send(f(view, cx)); + Task::ready(Ok(f(view, cx))) } else { let handle = self.handle().upgrade(self).unwrap(); - let _ = self.window_cx.run_on_main(move |cx| { - handle.update(cx, |view, cx| { - let _ = tx.send(f(view, cx)); - }) - }); + self.window_cx.run_on_main(move |cx| handle.update(cx, f)) } - async move { rx.await.unwrap() } } pub(crate) fn erase_state(&mut self, f: impl FnOnce(&mut ViewContext<()>) -> R) -> R { From ebc80597d59b03542cd057b26a425713a3426087 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 4 Oct 2023 20:09:55 +0200 Subject: [PATCH 07/10] WIP --- crates/gpui3/src/app.rs | 12 ++++++++++++ crates/gpui3/src/window.rs | 28 +++++++++++++++++++++------- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/crates/gpui3/src/app.rs b/crates/gpui3/src/app.rs index 592a6076defb6b43f879e08584d44ddc94ade01c..3b1a66326c56b4f199e1d4aaa68f94725b63d760 100644 --- a/crates/gpui3/src/app.rs +++ b/crates/gpui3/src/app.rs @@ -225,6 +225,18 @@ impl AppContext { }) } + pub fn spawn(&self, f: impl FnOnce(&mut AppContext) -> Fut + Send + 'static) -> Task + where + Fut: Future + Send + 'static, + R: Send + 'static, + { + let this = self.this.upgrade().unwrap(); + self.executor.spawn(async move { + let future = f(&mut this.lock()); + future.await + }) + } + pub fn text_system(&self) -> &Arc { &self.text_system } diff --git a/crates/gpui3/src/window.rs b/crates/gpui3/src/window.rs index 3782dfe97a563b5e6ecc57ee13f834dc31ad66c4..3257f520cdabf7ad70f2927adb0709d6ca2d6ff0 100644 --- a/crates/gpui3/src/window.rs +++ b/crates/gpui3/src/window.rs @@ -1,14 +1,14 @@ use crate::{ - image_cache::RenderImageParams, px, AnyView, AppContext, AvailableSpace, BorrowAppContext, - Bounds, Context, Corners, DevicePixels, Effect, Element, EntityId, FontId, GlyphId, Handle, - Hsla, ImageData, IsZero, LayerId, LayoutId, MainThread, MainThreadOnly, MonochromeSprite, - Pixels, PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Reference, RenderGlyphParams, - RenderSvgParams, ScaledPixels, Scene, SharedString, Size, Style, TaffyLayoutEngine, Task, - WeakHandle, WindowOptions, SUBPIXEL_VARIANTS, + image_cache::RenderImageParams, px, AnyView, AppContext, AsyncContext, AvailableSpace, + BorrowAppContext, Bounds, Context, Corners, DevicePixels, Effect, Element, EntityId, FontId, + GlyphId, Handle, Hsla, ImageData, IsZero, LayerId, LayoutId, MainThread, MainThreadOnly, + MonochromeSprite, Pixels, PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Reference, + RenderGlyphParams, RenderSvgParams, ScaledPixels, Scene, SharedString, Size, Style, + TaffyLayoutEngine, Task, WeakHandle, WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::Result; use smallvec::SmallVec; -use std::{any::TypeId, borrow::Cow, marker::PhantomData, mem, sync::Arc}; +use std::{any::TypeId, borrow::Cow, future::Future, marker::PhantomData, mem, sync::Arc}; use util::ResultExt; pub struct AnyWindow {} @@ -601,6 +601,20 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> { } } + pub fn spawn( + &mut self, + f: impl FnOnce(&mut S, &mut ViewContext<'_, '_, S>) -> Fut + Send + 'static, + ) -> Task> + where + Fut: Future + Send + 'static, + { + let handle = self.handle(); + todo!() + // self.window_cx.spawn(|cx| { + // f + // }) + } + pub(crate) fn erase_state(&mut self, f: impl FnOnce(&mut ViewContext<()>) -> R) -> R { let entity_id = self.unit_entity.id; let mut cx = ViewContext::mutable( From e68b24f8390cc524400f03bb9f13a2a93f0184bc Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 4 Oct 2023 13:43:21 -0600 Subject: [PATCH 08/10] Checkpoint --- crates/gpui3/src/elements/img.rs | 9 ++++-- crates/gpui3/src/gpui3.rs | 3 +- crates/gpui3/src/window.rs | 53 +++++++++++++++++++++++++------- 3 files changed, 49 insertions(+), 16 deletions(-) diff --git a/crates/gpui3/src/elements/img.rs b/crates/gpui3/src/elements/img.rs index 1932045885ce0e30f3f4c6c18d652638c65d775f..e72bad5d9f59956a0d13cdbcee89aef66871d4d9 100644 --- a/crates/gpui3/src/elements/img.rs +++ b/crates/gpui3/src/elements/img.rs @@ -76,10 +76,13 @@ impl Element for Img { cx.paint_image(bounds, corner_radii, order, data, self.grayscale)?; } else { log::warn!("image not loaded yet"); + cx.spawn(|cx| async move { + if image_future.await.log_err().is_some() { + // this.update(&mut cx, |_, cx| cx.notify()).ok(); + } + }) + .detach() // cx.spawn(|this, mut cx| async move { - // if image_future.await.log_err().is_some() { - // this.update(&mut cx, |_, cx| cx.notify()).ok(); - // } // }) // .detach(); } diff --git a/crates/gpui3/src/gpui3.rs b/crates/gpui3/src/gpui3.rs index cb14679948f3b1f9905dba885e352b0263945825..a1b0a7d7bd554fdea244c87d198e811fe02f254f 100644 --- a/crates/gpui3/src/gpui3.rs +++ b/crates/gpui3/src/gpui3.rs @@ -27,8 +27,6 @@ pub use elements::*; pub use executor::*; pub use geometry::*; pub use gpui3_macros::*; -pub use svg_renderer::*; - pub use platform::*; pub use refineable::*; pub use scene::*; @@ -44,6 +42,7 @@ use std::{ pub use style::*; pub use style_helpers::*; pub use styled::*; +pub use svg_renderer::*; use taffy::TaffyLayoutEngine; pub use taffy::{AvailableSpace, LayoutId}; pub use text_system::*; diff --git a/crates/gpui3/src/window.rs b/crates/gpui3/src/window.rs index 3257f520cdabf7ad70f2927adb0709d6ca2d6ff0..fcfc9b9c8f178771cdd58f185d1ddedbae529b44 100644 --- a/crates/gpui3/src/window.rs +++ b/crates/gpui3/src/window.rs @@ -1,10 +1,10 @@ use crate::{ - image_cache::RenderImageParams, px, AnyView, AppContext, AsyncContext, AvailableSpace, - BorrowAppContext, Bounds, Context, Corners, DevicePixels, Effect, Element, EntityId, FontId, - GlyphId, Handle, Hsla, ImageData, IsZero, LayerId, LayoutId, MainThread, MainThreadOnly, - MonochromeSprite, Pixels, PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Reference, - RenderGlyphParams, RenderSvgParams, ScaledPixels, Scene, SharedString, Size, Style, - TaffyLayoutEngine, Task, WeakHandle, WindowOptions, SUBPIXEL_VARIANTS, + image_cache::RenderImageParams, px, AnyView, AppContext, AvailableSpace, BorrowAppContext, + Bounds, Context, Corners, DevicePixels, Effect, Element, EntityId, FontId, GlyphId, Handle, + Hsla, ImageData, IsZero, LayerId, LayoutId, MainThread, MainThreadOnly, MonochromeSprite, + Pixels, PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Reference, RenderGlyphParams, + RenderSvgParams, ScaledPixels, Scene, SharedString, Size, Style, TaffyLayoutEngine, Task, + WeakHandle, WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::Result; use smallvec::SmallVec; @@ -112,7 +112,7 @@ impl<'a, 'w> WindowContext<'a, 'w> { self.window.dirty = true; } - fn run_on_main( + pub fn run_on_main( &mut self, f: impl FnOnce(&mut MainThread>) -> R + Send + 'static, ) -> Task> @@ -129,6 +129,36 @@ impl<'a, 'w> WindowContext<'a, 'w> { } } + pub fn spawn( + &mut self, + f: impl FnOnce(&mut WindowContext<'_, '_>) -> Fut + Send + 'static, + ) -> Task> + where + R: Send + 'static, + Fut: Future + Send + 'static, + { + let id = self.window.handle.id; + self.app.spawn(move |cx| { + let future = cx.update_window(id, f); + async move { Ok(future?.await) } + }) + } + + pub fn try_spawn( + &mut self, + f: impl FnOnce(&mut WindowContext<'_, '_>) -> Fut + Send + 'static, + ) -> Task> + where + R: Send + 'static, + Fut: Future> + Send + 'static, + { + let id = self.window.handle.id; + self.app.spawn(move |cx| { + let future = cx.update_window(id, f); + async move { future?.await } + }) + } + pub fn request_layout( &mut self, style: Style, @@ -606,13 +636,14 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> { f: impl FnOnce(&mut S, &mut ViewContext<'_, '_, S>) -> Fut + Send + 'static, ) -> Task> where + R: Send + 'static, Fut: Future + Send + 'static, { let handle = self.handle(); - todo!() - // self.window_cx.spawn(|cx| { - // f - // }) + self.window_cx.try_spawn(move |cx| { + let result = handle.update(cx, f); + async move { Ok(result?.await) } + }) } pub(crate) fn erase_state(&mut self, f: impl FnOnce(&mut ViewContext<()>) -> R) -> R { From 02d6b91b73fb673f7b5338ceb59eb1accfdd0827 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 4 Oct 2023 15:05:04 -0600 Subject: [PATCH 09/10] Checkpoint --- crates/gpui3/src/app.rs | 46 +++++++++++-------- crates/gpui3/src/app/async_context.rs | 56 +++++++++++++++++++----- crates/gpui3/src/app/entity_map.rs | 63 +++++++++++++++++++-------- crates/gpui3/src/elements/img.rs | 16 ++++--- crates/gpui3/src/executor.rs | 9 ++-- crates/gpui3/src/gpui3.rs | 6 --- crates/gpui3/src/window.rs | 58 +++++++++++------------- 7 files changed, 156 insertions(+), 98 deletions(-) diff --git a/crates/gpui3/src/app.rs b/crates/gpui3/src/app.rs index 3b1a66326c56b4f199e1d4aaa68f94725b63d760..498c45c2ab153e9a0199fc62d3c2bb0b900a482d 100644 --- a/crates/gpui3/src/app.rs +++ b/crates/gpui3/src/app.rs @@ -29,7 +29,7 @@ use util::{ }; #[derive(Clone)] -pub struct App(Arc>>); +pub struct App(Arc>); impl App { pub fn production(asset_source: Arc) -> Self { @@ -53,9 +53,9 @@ impl App { let executor = platform.executor(); let text_system = Arc::new(TextSystem::new(platform.text_system())); let entities = EntityMap::new(); - let unit_entity = entities.redeem(entities.reserve(), ()); + let unit_entity = entities.insert(entities.reserve(), ()); Self(Arc::new_cyclic(|this| { - Mutex::new(MainThread::new(AppContext { + Mutex::new(AppContext { this: this.clone(), platform: MainThreadOnly::new(platform, executor.clone()), executor, @@ -71,7 +71,7 @@ impl App { pending_effects: Default::default(), observers: Default::default(), layout_id_buffer: Default::default(), - })) + }) })) } @@ -83,6 +83,7 @@ impl App { let platform = self.0.lock().platform.clone(); platform.borrow_on_main_thread().run(Box::new(move || { let cx = &mut *this.0.lock(); + let cx = unsafe { mem::transmute::<&mut AppContext, &mut MainThread>(cx) }; on_finish_launching(cx); })); } @@ -91,7 +92,7 @@ impl App { type Handlers = SmallVec<[Arc bool + Send + Sync + 'static>; 2]>; pub struct AppContext { - this: Weak>>, + this: Weak>, platform: MainThreadOnly, text_system: Arc, pending_updates: usize, @@ -109,7 +110,7 @@ pub struct AppContext { } impl AppContext { - fn update(&mut self, update: impl FnOnce(&mut Self) -> R) -> R { + pub(crate) fn update(&mut self, update: impl FnOnce(&mut Self) -> R) -> R { self.pending_updates += 1; let result = update(self); if self.pending_updates == 1 { @@ -144,6 +145,7 @@ impl AppContext { } fn flush_effects(&mut self) { + dbg!("flush effects"); while let Some(effect) = self.pending_effects.pop_front() { match effect { Effect::Notify(entity_id) => self.apply_notify_effect(entity_id), @@ -163,6 +165,8 @@ impl AppContext { }) .collect::>(); + dbg!(&dirty_window_ids); + for dirty_window_id in dirty_window_ids { self.update_window(dirty_window_id, |cx| cx.draw()) .unwrap() @@ -180,8 +184,8 @@ impl AppContext { } } - pub fn to_async(&self) -> AsyncContext { - AsyncContext(unsafe { mem::transmute(self.this.clone()) }) + pub fn to_async(&self) -> AsyncAppContext { + AsyncAppContext(unsafe { mem::transmute(self.this.clone()) }) } pub fn executor(&self) -> &Executor { @@ -213,7 +217,7 @@ impl AppContext { f: impl FnOnce(&mut MainThread) -> F + Send + 'static, ) -> Task where - F: Future + 'static, + F: Future + Send + 'static, R: Send + 'static, { let this = self.this.upgrade().unwrap(); @@ -225,14 +229,14 @@ impl AppContext { }) } - pub fn spawn(&self, f: impl FnOnce(&mut AppContext) -> Fut + Send + 'static) -> Task + pub fn spawn(&self, f: impl FnOnce(AsyncAppContext) -> Fut + Send + 'static) -> Task where Fut: Future + Send + 'static, R: Send + 'static, { - let this = self.this.upgrade().unwrap(); + let cx = self.to_async(); self.executor.spawn(async move { - let future = f(&mut this.lock()); + let future = f(cx); future.await }) } @@ -298,9 +302,11 @@ impl Context for AppContext { &mut self, build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T, ) -> Handle { - let slot = self.entities.reserve(); - let entity = build_entity(&mut ModelContext::mutable(self, slot.id)); - self.entities.redeem(slot, entity) + self.update(|cx| { + let slot = cx.entities.reserve(); + let entity = build_entity(&mut ModelContext::mutable(cx, slot.id)); + cx.entities.insert(slot, entity) + }) } fn update_entity( @@ -308,10 +314,12 @@ impl Context for AppContext { handle: &Handle, update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R, ) -> R { - let mut entity = self.entities.lease(handle); - let result = update(&mut *entity, &mut ModelContext::mutable(self, handle.id)); - self.entities.end_lease(entity); - result + self.update(|cx| { + let mut entity = cx.entities.lease(handle); + let result = update(&mut entity, &mut ModelContext::mutable(cx, handle.id)); + cx.entities.end_lease(entity); + result + }) } } diff --git a/crates/gpui3/src/app/async_context.rs b/crates/gpui3/src/app/async_context.rs index 1f4cf537776bc4a3b4d9e1658a327b72cbd1b348..92a26456e442d093a12f2d990d5eae6ffc07b817 100644 --- a/crates/gpui3/src/app/async_context.rs +++ b/crates/gpui3/src/app/async_context.rs @@ -1,13 +1,15 @@ -use crate::{AnyWindowHandle, AppContext, Context, Handle, ModelContext, Result, WindowContext}; +use crate::{ + AnyWindowHandle, AppContext, Context, Handle, ModelContext, Result, ViewContext, WindowContext, +}; use anyhow::anyhow; use parking_lot::Mutex; use std::sync::Weak; #[derive(Clone)] -pub struct AsyncContext(pub(crate) Weak>); +pub struct AsyncAppContext(pub(crate) Weak>); -impl Context for AsyncContext { - type EntityContext<'a, 'b, T: 'static + Send + Sync> = ModelContext<'a, T>; +impl Context for AsyncAppContext { + type EntityContext<'a, 'w, T: 'static + Send + Sync> = ModelContext<'a, T>; type Result = Result; fn entity( @@ -18,7 +20,7 @@ impl Context for AsyncContext { .0 .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - let mut lock = app.lock(); + let mut lock = app.lock(); // Does not compile without this variable. Ok(lock.entity(build_entity)) } @@ -31,17 +33,17 @@ impl Context for AsyncContext { .0 .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - let mut lock = app.lock(); + let mut lock = app.lock(); // Does not compile without this variable. Ok(lock.update_entity(handle, update)) } } -impl AsyncContext { - pub fn update_window( +impl AsyncAppContext { + pub fn update_window( &self, handle: AnyWindowHandle, - update: impl FnOnce(&mut WindowContext) -> T + Send + Sync, - ) -> Result { + update: impl FnOnce(&mut WindowContext) -> R, + ) -> Result { let app = self .0 .upgrade() @@ -50,3 +52,37 @@ impl AsyncContext { app_context.update_window(handle.id, update) } } + +#[derive(Clone)] +pub struct AsyncWindowContext { + app: AsyncAppContext, + window: AnyWindowHandle, +} + +impl AsyncWindowContext { + pub fn new(app: AsyncAppContext, window: AnyWindowHandle) -> Self { + Self { app, window } + } +} + +impl Context for AsyncWindowContext { + type EntityContext<'a, 'w, T: 'static + Send + Sync> = ViewContext<'a, 'w, T>; + type Result = Result; + + fn entity( + &mut self, + build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, R>) -> R, + ) -> Result> { + self.app + .update_window(self.window, |cx| cx.entity(build_entity)) + } + + fn update_entity( + &mut self, + handle: &Handle, + update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R, + ) -> Result { + self.app + .update_window(self.window, |cx| cx.update_entity(handle, update)) + } +} diff --git a/crates/gpui3/src/app/entity_map.rs b/crates/gpui3/src/app/entity_map.rs index 29db57bb39cc518b8c74bdcac13dd4c551d215f1..ac931ec66124952cff477389118b13581e4d7438 100644 --- a/crates/gpui3/src/app/entity_map.rs +++ b/crates/gpui3/src/app/entity_map.rs @@ -14,14 +14,6 @@ use std::{ 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>>>, @@ -35,31 +27,38 @@ impl EntityMap { } } + /// Reserve a slot for an entity, which you can subsequently use with `insert`. pub fn reserve(&self) -> Slot { let id = self.ref_counts.write().insert(1.into()); Slot(Handle::new(id, Arc::downgrade(&self.ref_counts))) } - pub fn redeem(&self, slot: Slot, entity: T) -> Handle { + /// Insert an entity into a slot obtained by calling `reserve`. + pub fn insert(&self, slot: Slot, entity: T) -> Handle { let handle = slot.0; self.entities.lock().insert(handle.id, Box::new(entity)); handle } + /// Move an entity to the stack. 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(); + let entity = Some( + 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); + /// Return an entity after moving it to the stack. + pub fn end_lease(&mut self, mut lease: Lease) { + self.entities + .lock() + .insert(lease.id, lease.entity.take().unwrap()); } pub fn weak_handle(&self, id: EntityId) -> WeakHandle { @@ -71,6 +70,34 @@ impl EntityMap { } } +pub struct Lease { + entity: Option>, + pub id: EntityId, +} + +impl core::ops::Deref for Lease { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.entity.as_ref().unwrap() + } +} + +impl core::ops::DerefMut for Lease { + fn deref_mut(&mut self) -> &mut Self::Target { + self.entity.as_mut().unwrap() + } +} + +impl Drop for Lease { + fn drop(&mut self) { + assert!( + self.entity.is_none(), + "Leases must be ended with EntityMap::end_lease" + ); + } +} + #[derive(Deref, DerefMut)] pub struct Slot(Handle); diff --git a/crates/gpui3/src/elements/img.rs b/crates/gpui3/src/elements/img.rs index e72bad5d9f59956a0d13cdbcee89aef66871d4d9..245b5d3918743404bc692385cff0861fb778a4af 100644 --- a/crates/gpui3/src/elements/img.rs +++ b/crates/gpui3/src/elements/img.rs @@ -35,7 +35,7 @@ impl Img { } } -impl Element for Img { +impl Element for Img { type State = S; type FrameState = (); @@ -75,16 +75,18 @@ impl Element for Img { let corner_radii = style.corner_radii.to_pixels(bounds, cx.rem_size()); cx.paint_image(bounds, corner_radii, order, data, self.grayscale)?; } else { - log::warn!("image not loaded yet"); - cx.spawn(|cx| async move { + dbg!("not loaded"); + cx.spawn(|view, mut cx| async move { + dbg!("awaiting image future"); if image_future.await.log_err().is_some() { - // this.update(&mut cx, |_, cx| cx.notify()).ok(); + view.update(&mut cx, |_, cx| { + dbg!("image future loaded"); + cx.notify(); + }) + .ok(); } }) .detach() - // cx.spawn(|this, mut cx| async move { - // }) - // .detach(); } } Ok(()) diff --git a/crates/gpui3/src/executor.rs b/crates/gpui3/src/executor.rs index 7c4ab3b8e50c647dcafbc162f6d80acd2122602e..166c265128e4372631b59243d950ee1298740639 100644 --- a/crates/gpui3/src/executor.rs +++ b/crates/gpui3/src/executor.rs @@ -76,14 +76,13 @@ impl Executor { /// closure returns a future which will be run to completion on the main thread. pub fn spawn_on_main(&self, func: impl FnOnce() -> F + Send + 'static) -> Task where - F: Future + 'static, + F: Future + Send + 'static, R: Send + 'static, { let dispatcher = self.dispatcher.clone(); - let (runnable, task) = - async_task::spawn_local(async move { func().await }, move |runnable| { - dispatcher.dispatch_on_main_thread(runnable) - }); + let (runnable, task) = async_task::spawn(async move { func().await }, move |runnable| { + dispatcher.dispatch_on_main_thread(runnable) + }); runnable.schedule(); Task::Spawned(task) } diff --git a/crates/gpui3/src/gpui3.rs b/crates/gpui3/src/gpui3.rs index a1b0a7d7bd554fdea244c87d198e811fe02f254f..53d2fd89f88712ae0a2f3171d11ca0194008fe80 100644 --- a/crates/gpui3/src/gpui3.rs +++ b/crates/gpui3/src/gpui3.rs @@ -69,12 +69,6 @@ pub trait Context { #[repr(transparent)] pub struct MainThread(T); -impl MainThread { - fn new(value: T) -> Self { - Self(value) - } -} - impl Deref for MainThread { type Target = T; diff --git a/crates/gpui3/src/window.rs b/crates/gpui3/src/window.rs index fcfc9b9c8f178771cdd58f185d1ddedbae529b44..36761f60d4ef10dc52bc0159b8c86f7575c350a2 100644 --- a/crates/gpui3/src/window.rs +++ b/crates/gpui3/src/window.rs @@ -1,10 +1,10 @@ use crate::{ - image_cache::RenderImageParams, px, AnyView, AppContext, AvailableSpace, BorrowAppContext, - Bounds, Context, Corners, DevicePixels, Effect, Element, EntityId, FontId, GlyphId, Handle, - Hsla, ImageData, IsZero, LayerId, LayoutId, MainThread, MainThreadOnly, MonochromeSprite, - Pixels, PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Reference, RenderGlyphParams, - RenderSvgParams, ScaledPixels, Scene, SharedString, Size, Style, TaffyLayoutEngine, Task, - WeakHandle, WindowOptions, SUBPIXEL_VARIANTS, + image_cache::RenderImageParams, px, AnyView, AppContext, AsyncWindowContext, AvailableSpace, + BorrowAppContext, Bounds, Context, Corners, DevicePixels, Effect, Element, EntityId, FontId, + GlyphId, Handle, Hsla, ImageData, IsZero, LayerId, LayoutId, MainThread, MainThreadOnly, + MonochromeSprite, Pixels, PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Reference, + RenderGlyphParams, RenderSvgParams, ScaledPixels, Scene, SharedString, Size, Style, + TaffyLayoutEngine, Task, WeakHandle, WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::Result; use smallvec::SmallVec; @@ -109,6 +109,7 @@ impl<'a, 'w> WindowContext<'a, 'w> { } pub fn notify(&mut self) { + dbg!("ViewContext::notify"); self.window.dirty = true; } @@ -129,33 +130,23 @@ impl<'a, 'w> WindowContext<'a, 'w> { } } - pub fn spawn( - &mut self, - f: impl FnOnce(&mut WindowContext<'_, '_>) -> Fut + Send + 'static, - ) -> Task> - where - R: Send + 'static, - Fut: Future + Send + 'static, - { - let id = self.window.handle.id; - self.app.spawn(move |cx| { - let future = cx.update_window(id, f); - async move { Ok(future?.await) } - }) + pub fn to_async(&self) -> AsyncWindowContext { + AsyncWindowContext::new(self.app.to_async(), self.window.handle) } - pub fn try_spawn( + pub fn spawn( &mut self, - f: impl FnOnce(&mut WindowContext<'_, '_>) -> Fut + Send + 'static, - ) -> Task> + f: impl FnOnce(AnyWindowHandle, AsyncWindowContext) -> Fut + Send + 'static, + ) -> Task where R: Send + 'static, - Fut: Future> + Send + 'static, + Fut: Future + Send + 'static, { - let id = self.window.handle.id; - self.app.spawn(move |cx| { - let future = cx.update_window(id, f); - async move { future?.await } + let window = self.window.handle; + self.app.spawn(move |app| { + let cx = AsyncWindowContext::new(app, window); + let future = f(window, cx); + async move { future.await } }) } @@ -448,7 +439,7 @@ impl Context for WindowContext<'_, '_> { &mut self.window, slot.id, )); - self.entities.redeem(slot, entity) + self.entities.insert(slot, entity) } fn update_entity( @@ -607,6 +598,7 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> { } pub fn notify(&mut self) { + dbg!("ViewContext::notify"); self.window_cx.notify(); self.window_cx .app @@ -633,16 +625,16 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> { pub fn spawn( &mut self, - f: impl FnOnce(&mut S, &mut ViewContext<'_, '_, S>) -> Fut + Send + 'static, - ) -> Task> + f: impl FnOnce(WeakHandle, AsyncWindowContext) -> Fut + Send + 'static, + ) -> Task where R: Send + 'static, Fut: Future + Send + 'static, { let handle = self.handle(); - self.window_cx.try_spawn(move |cx| { - let result = handle.update(cx, f); - async move { Ok(result?.await) } + self.window_cx.spawn(move |_, cx| { + let result = f(handle, cx); + async move { result.await } }) } From c8bc68c267afdd714080f84ea3f243f77a989dd6 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 4 Oct 2023 15:08:04 -0600 Subject: [PATCH 10/10] Checkpoint --- crates/gpui3/src/app.rs | 2 -- crates/gpui3/src/elements/img.rs | 3 --- crates/gpui3/src/platform/mac/metal_renderer.rs | 2 ++ crates/gpui3/src/window.rs | 2 -- 4 files changed, 2 insertions(+), 7 deletions(-) diff --git a/crates/gpui3/src/app.rs b/crates/gpui3/src/app.rs index 498c45c2ab153e9a0199fc62d3c2bb0b900a482d..36de3b932b6c9e86e77edc127b7447af95c4f0ff 100644 --- a/crates/gpui3/src/app.rs +++ b/crates/gpui3/src/app.rs @@ -165,8 +165,6 @@ impl AppContext { }) .collect::>(); - dbg!(&dirty_window_ids); - for dirty_window_id in dirty_window_ids { self.update_window(dirty_window_id, |cx| cx.draw()) .unwrap() diff --git a/crates/gpui3/src/elements/img.rs b/crates/gpui3/src/elements/img.rs index 245b5d3918743404bc692385cff0861fb778a4af..28bc51a04d5c12f702c5de0b4e2e0e524cf58f70 100644 --- a/crates/gpui3/src/elements/img.rs +++ b/crates/gpui3/src/elements/img.rs @@ -75,12 +75,9 @@ impl Element for Img { let corner_radii = style.corner_radii.to_pixels(bounds, cx.rem_size()); cx.paint_image(bounds, corner_radii, order, data, self.grayscale)?; } else { - dbg!("not loaded"); cx.spawn(|view, mut cx| async move { - dbg!("awaiting image future"); if image_future.await.log_err().is_some() { view.update(&mut cx, |_, cx| { - dbg!("image future loaded"); cx.notify(); }) .ok(); diff --git a/crates/gpui3/src/platform/mac/metal_renderer.rs b/crates/gpui3/src/platform/mac/metal_renderer.rs index a018aad7623cdec236d1c97f5176a6f4f5fe65aa..84a5fd51261aaa959b2b8f91ce3dc8d1e42a6f51 100644 --- a/crates/gpui3/src/platform/mac/metal_renderer.rs +++ b/crates/gpui3/src/platform/mac/metal_renderer.rs @@ -131,6 +131,8 @@ impl MetalRenderer { } pub fn draw(&mut self, scene: &mut Scene) { + dbg!("draw scene"); + let layer = self.layer.clone(); let viewport_size = layer.drawable_size(); let viewport_size: Size = size( diff --git a/crates/gpui3/src/window.rs b/crates/gpui3/src/window.rs index 36761f60d4ef10dc52bc0159b8c86f7575c350a2..f255172c776276f5e8297e3037e033f982d0bfd3 100644 --- a/crates/gpui3/src/window.rs +++ b/crates/gpui3/src/window.rs @@ -109,7 +109,6 @@ impl<'a, 'w> WindowContext<'a, 'w> { } pub fn notify(&mut self) { - dbg!("ViewContext::notify"); self.window.dirty = true; } @@ -598,7 +597,6 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> { } pub fn notify(&mut self) { - dbg!("ViewContext::notify"); self.window_cx.notify(); self.window_cx .app