Share GPUI and scheduler implementations (#64261)

Move GPUI window/entity updates, view rendering, listener adapters,
arena allocation, and scheduler task setup into shared implementations.
Keep the generic entry points as thin adapters.

Uses pretty much the same technique prior PRs did, but this time on
`gpui` which I am not too confident about — can close it if seems
dangerous, but the binary strip win looks good to try.

`cargo llvm-lines --locked -p gpui`:
Before (bec63dee23): 934803 lines, 33225 copies
After: 926925 lines (-0.8%), 33454 copies (+0.7%)

With compiler wrappers disabled, `cargo clean --target-dir
target/binary-size-split-build` and `cargo build --locked -p zed`:
Before: 135.6s wall, 1569.8s CPU, zed binary 1175563800 bytes 
After: 142.5s wall (+5.1%), 1589.0s CPU (+1.2%), zed binary 1144068952
bytes (-2.7%, -31.5MB)

Release Notes:

- N/A
- [GPUI] Shared GPUI and scheduler implementations to reduce the final
binary size of the app
This commit is contained in:
Kirill Bulatov authored and GitHub committed 2026-09-18 10:50:41 +00:00
1 parent 87d3b5fdfd
commit 1753d7f52b
10 files changed
+590 -317

No files matched your search

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