Present Servo BGRA hardware surfaces
This commit is contained in:
+488
@@ -0,0 +1,488 @@
|
||||
use crate::{
|
||||
AnyView, AnyWindowHandle, App, AppCell, AppContext, BackgroundExecutor, BorrowAppContext,
|
||||
Entity, EventEmitter, Focusable, ForegroundExecutor, Global, PromptButton, PromptLevel, Render,
|
||||
Reservation, Result, Subscription, Task, VisualContext, Window, WindowHandle,
|
||||
};
|
||||
use anyhow::{Context as _, anyhow};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use futures::channel::oneshot;
|
||||
use std::{future::Future, rc::Weak};
|
||||
|
||||
use super::{Context, WeakEntity};
|
||||
|
||||
/// An async-friendly version of [App] with a static lifetime so it can be held across `await` points in async code.
|
||||
/// You're provided with an instance when calling [App::spawn], and you can also create one with [App::to_async].
|
||||
/// Internally, this holds a weak reference to an `App`, so its methods are fallible to protect against cases where the [App] is dropped.
|
||||
#[derive(Clone)]
|
||||
pub struct AsyncApp {
|
||||
pub(crate) app: Weak<AppCell>,
|
||||
pub(crate) background_executor: BackgroundExecutor,
|
||||
pub(crate) foreground_executor: ForegroundExecutor,
|
||||
}
|
||||
|
||||
impl AppContext for AsyncApp {
|
||||
type Result<T> = Result<T>;
|
||||
|
||||
fn new<T: 'static>(
|
||||
&mut self,
|
||||
build_entity: impl FnOnce(&mut Context<T>) -> T,
|
||||
) -> Self::Result<Entity<T>> {
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
let mut app = app.borrow_mut();
|
||||
Ok(app.new(build_entity))
|
||||
}
|
||||
|
||||
fn reserve_entity<T: 'static>(&mut self) -> Result<Reservation<T>> {
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
let mut app = app.borrow_mut();
|
||||
Ok(app.reserve_entity())
|
||||
}
|
||||
|
||||
fn insert_entity<T: 'static>(
|
||||
&mut self,
|
||||
reservation: Reservation<T>,
|
||||
build_entity: impl FnOnce(&mut Context<T>) -> T,
|
||||
) -> Result<Entity<T>> {
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
let mut app = app.borrow_mut();
|
||||
Ok(app.insert_entity(reservation, build_entity))
|
||||
}
|
||||
|
||||
fn update_entity<T: 'static, R>(
|
||||
&mut self,
|
||||
handle: &Entity<T>,
|
||||
update: impl FnOnce(&mut T, &mut Context<T>) -> R,
|
||||
) -> Self::Result<R> {
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
let mut app = app.borrow_mut();
|
||||
Ok(app.update_entity(handle, update))
|
||||
}
|
||||
|
||||
fn as_mut<'a, T>(&'a mut self, _handle: &Entity<T>) -> Self::Result<super::GpuiBorrow<'a, T>>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
Err(anyhow!(
|
||||
"Cannot as_mut with an async context. Try calling update() first"
|
||||
))
|
||||
}
|
||||
|
||||
fn read_entity<T, R>(
|
||||
&self,
|
||||
handle: &Entity<T>,
|
||||
callback: impl FnOnce(&T, &App) -> R,
|
||||
) -> Self::Result<R>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
let lock = app.borrow();
|
||||
Ok(lock.read_entity(handle, callback))
|
||||
}
|
||||
|
||||
fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
|
||||
where
|
||||
F: FnOnce(AnyView, &mut Window, &mut App) -> T,
|
||||
{
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
let mut lock = app.try_borrow_mut()?;
|
||||
lock.update_window(window, f)
|
||||
}
|
||||
|
||||
fn read_window<T, R>(
|
||||
&self,
|
||||
window: &WindowHandle<T>,
|
||||
read: impl FnOnce(Entity<T>, &App) -> R,
|
||||
) -> Result<R>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
let lock = app.borrow();
|
||||
lock.read_window(window, read)
|
||||
}
|
||||
|
||||
fn background_spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
|
||||
where
|
||||
R: Send + 'static,
|
||||
{
|
||||
self.background_executor.spawn(future)
|
||||
}
|
||||
|
||||
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> Self::Result<R>
|
||||
where
|
||||
G: Global,
|
||||
{
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
let mut lock = app.borrow_mut();
|
||||
Ok(lock.update(|this| this.read_global(callback)))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncApp {
|
||||
/// Schedules all windows in the application to be redrawn.
|
||||
pub fn refresh(&self) -> Result<()> {
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
let mut lock = app.borrow_mut();
|
||||
lock.refresh_windows();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get an executor which can be used to spawn futures in the background.
|
||||
pub fn background_executor(&self) -> &BackgroundExecutor {
|
||||
&self.background_executor
|
||||
}
|
||||
|
||||
/// Get an executor which can be used to spawn futures in the foreground.
|
||||
pub fn foreground_executor(&self) -> &ForegroundExecutor {
|
||||
&self.foreground_executor
|
||||
}
|
||||
|
||||
/// Invoke the given function in the context of the app, then flush any effects produced during its invocation.
|
||||
pub fn update<R>(&self, f: impl FnOnce(&mut App) -> R) -> Result<R> {
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
let mut lock = app.borrow_mut();
|
||||
Ok(lock.update(f))
|
||||
}
|
||||
|
||||
/// Arrange for the given callback to be invoked whenever the given entity emits an event of a given type.
|
||||
/// The callback is provided a handle to the emitting entity and a reference to the emitted event.
|
||||
pub fn subscribe<T, Event>(
|
||||
&mut self,
|
||||
entity: &Entity<T>,
|
||||
mut on_event: impl FnMut(Entity<T>, &Event, &mut App) + 'static,
|
||||
) -> Result<Subscription>
|
||||
where
|
||||
T: 'static + EventEmitter<Event>,
|
||||
Event: 'static,
|
||||
{
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
let mut lock = app.borrow_mut();
|
||||
let subscription = lock.subscribe(entity, on_event);
|
||||
Ok(subscription)
|
||||
}
|
||||
|
||||
/// Open a window with the given options based on the root view returned by the given function.
|
||||
pub fn open_window<V>(
|
||||
&self,
|
||||
options: crate::WindowOptions,
|
||||
build_root_view: impl FnOnce(&mut Window, &mut App) -> Entity<V>,
|
||||
) -> Result<WindowHandle<V>>
|
||||
where
|
||||
V: 'static + Render,
|
||||
{
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
let mut lock = app.borrow_mut();
|
||||
lock.open_window(options, build_root_view)
|
||||
}
|
||||
|
||||
/// Schedule a future to be polled in the background.
|
||||
#[track_caller]
|
||||
pub fn spawn<AsyncFn, R>(&self, f: AsyncFn) -> Task<R>
|
||||
where
|
||||
AsyncFn: AsyncFnOnce(&mut AsyncApp) -> R + 'static,
|
||||
R: 'static,
|
||||
{
|
||||
let mut cx = self.clone();
|
||||
self.foreground_executor
|
||||
.spawn(async move { f(&mut cx).await })
|
||||
}
|
||||
|
||||
/// Determine whether global state of the specified type has been assigned.
|
||||
/// Returns an error if the `App` has been dropped.
|
||||
pub fn has_global<G: Global>(&self) -> Result<bool> {
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
let app = app.borrow_mut();
|
||||
Ok(app.has_global::<G>())
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// Returns an error if the `App` has been dropped.
|
||||
pub fn read_global<G: Global, R>(&self, read: impl FnOnce(&G, &App) -> R) -> Result<R> {
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
let app = app.borrow_mut();
|
||||
Ok(read(app.global(), &app))
|
||||
}
|
||||
|
||||
/// Reads the global state of the specified type, passing it to the given callback.
|
||||
///
|
||||
/// Similar to [`AsyncApp::read_global`], but returns an error instead of panicking
|
||||
/// if no state of the specified type has been assigned.
|
||||
///
|
||||
/// Returns an error if no state of the specified type has been assigned the `App` has been dropped.
|
||||
pub fn try_read_global<G: Global, R>(&self, read: impl FnOnce(&G, &App) -> R) -> Option<R> {
|
||||
let app = self.app.upgrade()?;
|
||||
let app = app.borrow_mut();
|
||||
Some(read(app.try_global()?, &app))
|
||||
}
|
||||
|
||||
/// Reads the global state of the specified type, passing it to the given callback.
|
||||
/// A default value is assigned if a global of this type has not yet been assigned.
|
||||
///
|
||||
/// # Errors
|
||||
/// If the app has ben dropped this returns an error.
|
||||
pub fn try_read_default_global<G: Global + Default, R>(
|
||||
&self,
|
||||
read: impl FnOnce(&G, &App) -> R,
|
||||
) -> Result<R> {
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
let mut app = app.borrow_mut();
|
||||
app.update(|cx| {
|
||||
cx.default_global::<G>();
|
||||
});
|
||||
Ok(read(app.try_global().context("app was released")?, &app))
|
||||
}
|
||||
|
||||
/// A convenience method for [`App::update_global`](BorrowAppContext::update_global)
|
||||
/// for updating the global state of the specified type.
|
||||
pub fn update_global<G: Global, R>(
|
||||
&self,
|
||||
update: impl FnOnce(&mut G, &mut App) -> R,
|
||||
) -> Result<R> {
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
let mut app = app.borrow_mut();
|
||||
Ok(app.update(|cx| cx.update_global(update)))
|
||||
}
|
||||
|
||||
/// Run something using this entity and cx, when the returned struct is dropped
|
||||
pub fn on_drop<T: 'static, Callback: FnOnce(&mut T, &mut Context<T>) + 'static>(
|
||||
&self,
|
||||
entity: &WeakEntity<T>,
|
||||
f: Callback,
|
||||
) -> util::Deferred<impl FnOnce() + use<T, Callback>> {
|
||||
let entity = entity.clone();
|
||||
let mut cx = self.clone();
|
||||
util::defer(move || {
|
||||
entity.update(&mut cx, f).ok();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A cloneable, owned handle to the application context,
|
||||
/// composed with the window associated with the current task.
|
||||
#[derive(Clone, Deref, DerefMut)]
|
||||
pub struct AsyncWindowContext {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
app: AsyncApp,
|
||||
window: AnyWindowHandle,
|
||||
}
|
||||
|
||||
impl AsyncWindowContext {
|
||||
pub(crate) fn new_context(app: AsyncApp, window: AnyWindowHandle) -> Self {
|
||||
Self { app, window }
|
||||
}
|
||||
|
||||
/// Get the handle of the window this context is associated with.
|
||||
pub fn window_handle(&self) -> AnyWindowHandle {
|
||||
self.window
|
||||
}
|
||||
|
||||
/// A convenience method for [`App::update_window`].
|
||||
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`].
|
||||
pub fn update_root<R>(
|
||||
&mut self,
|
||||
update: impl FnOnce(AnyView, &mut Window, &mut App) -> R,
|
||||
) -> Result<R> {
|
||||
self.app.update_window(self.window, update)
|
||||
}
|
||||
|
||||
/// A convenience method for [`Window::on_next_frame`].
|
||||
pub fn on_next_frame(&mut self, f: impl FnOnce(&mut Window, &mut App) + 'static) {
|
||||
self.window
|
||||
.update(self, |_, window, _| window.on_next_frame(f))
|
||||
.ok();
|
||||
}
|
||||
|
||||
/// A convenience method for [`App::global`].
|
||||
pub fn read_global<G: Global, R>(
|
||||
&mut self,
|
||||
read: impl FnOnce(&G, &Window, &App) -> R,
|
||||
) -> Result<R> {
|
||||
self.window
|
||||
.update(self, |_, window, cx| read(cx.global(), window, cx))
|
||||
}
|
||||
|
||||
/// A convenience method for [`App::update_global`](BorrowAppContext::update_global).
|
||||
/// for updating the global state of the specified type.
|
||||
pub fn update_global<G, R>(
|
||||
&mut self,
|
||||
update: impl FnOnce(&mut G, &mut Window, &mut App) -> R,
|
||||
) -> Result<R>
|
||||
where
|
||||
G: Global,
|
||||
{
|
||||
self.window.update(self, |_, window, cx| {
|
||||
cx.update_global(|global, cx| update(global, window, cx))
|
||||
})
|
||||
}
|
||||
|
||||
/// 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]
|
||||
pub fn spawn<AsyncFn, R>(&self, f: AsyncFn) -> Task<R>
|
||||
where
|
||||
AsyncFn: AsyncFnOnce(&mut AsyncWindowContext) -> R + 'static,
|
||||
R: 'static,
|
||||
{
|
||||
let mut cx = self.clone();
|
||||
self.foreground_executor
|
||||
.spawn(async move { f(&mut cx).await })
|
||||
}
|
||||
|
||||
/// Present a platform dialog.
|
||||
/// The provided message will be presented, along with buttons for each answer.
|
||||
/// When a button is clicked, the returned Receiver will receive the index of the clicked button.
|
||||
pub fn prompt<T>(
|
||||
&mut self,
|
||||
level: PromptLevel,
|
||||
message: &str,
|
||||
detail: Option<&str>,
|
||||
answers: &[T],
|
||||
) -> oneshot::Receiver<usize>
|
||||
where
|
||||
T: Clone + Into<PromptButton>,
|
||||
{
|
||||
self.window
|
||||
.update(self, |_, window, cx| {
|
||||
window.prompt(level, message, detail, answers, cx)
|
||||
})
|
||||
.unwrap_or_else(|_| oneshot::channel().1)
|
||||
}
|
||||
}
|
||||
|
||||
impl AppContext for AsyncWindowContext {
|
||||
type Result<T> = Result<T>;
|
||||
|
||||
fn new<T>(&mut self, build_entity: impl FnOnce(&mut Context<T>) -> T) -> Result<Entity<T>>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
self.window.update(self, |_, _, cx| cx.new(build_entity))
|
||||
}
|
||||
|
||||
fn reserve_entity<T: 'static>(&mut self) -> Result<Reservation<T>> {
|
||||
self.window.update(self, |_, _, cx| cx.reserve_entity())
|
||||
}
|
||||
|
||||
fn insert_entity<T: 'static>(
|
||||
&mut self,
|
||||
reservation: Reservation<T>,
|
||||
build_entity: impl FnOnce(&mut Context<T>) -> T,
|
||||
) -> Self::Result<Entity<T>> {
|
||||
self.window
|
||||
.update(self, |_, _, cx| cx.insert_entity(reservation, build_entity))
|
||||
}
|
||||
|
||||
fn update_entity<T: 'static, R>(
|
||||
&mut self,
|
||||
handle: &Entity<T>,
|
||||
update: impl FnOnce(&mut T, &mut Context<T>) -> R,
|
||||
) -> Result<R> {
|
||||
self.window
|
||||
.update(self, |_, _, cx| cx.update_entity(handle, update))
|
||||
}
|
||||
|
||||
fn as_mut<'a, T>(&'a mut self, _: &Entity<T>) -> Self::Result<super::GpuiBorrow<'a, T>>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
Err(anyhow!(
|
||||
"Cannot use as_mut() from an async context, call `update`"
|
||||
))
|
||||
}
|
||||
|
||||
fn read_entity<T, R>(
|
||||
&self,
|
||||
handle: &Entity<T>,
|
||||
read: impl FnOnce(&T, &App) -> R,
|
||||
) -> Self::Result<R>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
self.app.read_entity(handle, read)
|
||||
}
|
||||
|
||||
fn update_window<T, F>(&mut self, window: AnyWindowHandle, update: F) -> Result<T>
|
||||
where
|
||||
F: FnOnce(AnyView, &mut Window, &mut App) -> T,
|
||||
{
|
||||
self.app.update_window(window, update)
|
||||
}
|
||||
|
||||
fn read_window<T, R>(
|
||||
&self,
|
||||
window: &WindowHandle<T>,
|
||||
read: impl FnOnce(Entity<T>, &App) -> R,
|
||||
) -> Result<R>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
self.app.read_window(window, read)
|
||||
}
|
||||
|
||||
fn background_spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
|
||||
where
|
||||
R: Send + 'static,
|
||||
{
|
||||
self.app.background_executor.spawn(future)
|
||||
}
|
||||
|
||||
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> Result<R>
|
||||
where
|
||||
G: Global,
|
||||
{
|
||||
self.app.read_global(callback)
|
||||
}
|
||||
}
|
||||
|
||||
impl VisualContext for AsyncWindowContext {
|
||||
fn window_handle(&self) -> AnyWindowHandle {
|
||||
self.window
|
||||
}
|
||||
|
||||
fn new_window_entity<T: 'static>(
|
||||
&mut self,
|
||||
build_entity: impl FnOnce(&mut Window, &mut Context<T>) -> T,
|
||||
) -> Self::Result<Entity<T>> {
|
||||
self.window
|
||||
.update(self, |_, window, cx| cx.new(|cx| build_entity(window, cx)))
|
||||
}
|
||||
|
||||
fn update_window_entity<T: 'static, R>(
|
||||
&mut self,
|
||||
view: &Entity<T>,
|
||||
update: impl FnOnce(&mut T, &mut Window, &mut Context<T>) -> R,
|
||||
) -> Self::Result<R> {
|
||||
self.window.update(self, |_, window, cx| {
|
||||
view.update(cx, |entity, cx| update(entity, window, cx))
|
||||
})
|
||||
}
|
||||
|
||||
fn replace_root_view<V>(
|
||||
&mut self,
|
||||
build_view: impl FnOnce(&mut Window, &mut Context<V>) -> V,
|
||||
) -> Self::Result<Entity<V>>
|
||||
where
|
||||
V: 'static + Render,
|
||||
{
|
||||
self.window
|
||||
.update(self, |_, window, cx| window.replace_root(cx, build_view))
|
||||
}
|
||||
|
||||
fn focus<V>(&mut self, view: &Entity<V>) -> Self::Result<()>
|
||||
where
|
||||
V: Focusable,
|
||||
{
|
||||
self.window.update(self, |_, window, cx| {
|
||||
view.read(cx).focus_handle(cx).focus(window);
|
||||
})
|
||||
}
|
||||
}
|
||||
Vendored
+824
@@ -0,0 +1,824 @@
|
||||
use crate::{
|
||||
AnyView, AnyWindowHandle, AppContext, AsyncApp, DispatchPhase, Effect, EntityId, EventEmitter,
|
||||
FocusHandle, FocusOutEvent, Focusable, Global, KeystrokeObserver, Reservation, SubscriberSet,
|
||||
Subscription, Task, WeakEntity, WeakFocusHandle, Window, WindowHandle,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use futures::FutureExt;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
borrow::{Borrow, BorrowMut},
|
||||
future::Future,
|
||||
ops,
|
||||
sync::Arc,
|
||||
};
|
||||
use util::Deferred;
|
||||
|
||||
use super::{App, AsyncWindowContext, Entity, KeystrokeEvent};
|
||||
|
||||
/// The app context, with specialized behavior for the given entity.
|
||||
pub struct Context<'a, T> {
|
||||
app: &'a mut App,
|
||||
entity_state: WeakEntity<T>,
|
||||
}
|
||||
|
||||
impl<'a, T> ops::Deref for Context<'a, T> {
|
||||
type Target = App;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.app
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> ops::DerefMut for Context<'a, T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.app
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: 'static> Context<'a, T> {
|
||||
pub(crate) fn new_context(app: &'a mut App, entity_state: WeakEntity<T>) -> Self {
|
||||
Self { app, entity_state }
|
||||
}
|
||||
|
||||
/// The entity id of the entity backing this context.
|
||||
pub fn entity_id(&self) -> EntityId {
|
||||
self.entity_state.entity_id
|
||||
}
|
||||
|
||||
/// Returns a handle to the entity belonging to this context.
|
||||
pub fn entity(&self) -> Entity<T> {
|
||||
self.weak_entity()
|
||||
.upgrade()
|
||||
.expect("The entity must be alive if we have a entity context")
|
||||
}
|
||||
|
||||
/// Returns a weak handle to the entity belonging to this context.
|
||||
pub fn weak_entity(&self) -> WeakEntity<T> {
|
||||
self.entity_state.clone()
|
||||
}
|
||||
|
||||
/// Arranges for the given function to be called whenever [`Context::notify`] is
|
||||
/// called with the given entity.
|
||||
pub fn observe<W>(
|
||||
&mut self,
|
||||
entity: &Entity<W>,
|
||||
mut on_notify: impl FnMut(&mut T, Entity<W>, &mut Context<T>) + 'static,
|
||||
) -> Subscription
|
||||
where
|
||||
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
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Observe changes to ourselves
|
||||
pub fn observe_self(
|
||||
&mut self,
|
||||
mut on_event: impl FnMut(&mut T, &mut Context<T>) + 'static,
|
||||
) -> Subscription
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
let this = self.entity();
|
||||
self.app.observe(&this, move |this, cx| {
|
||||
this.update(cx, |this, cx| on_event(this, cx))
|
||||
})
|
||||
}
|
||||
|
||||
/// Subscribe to an event type from another entity
|
||||
pub fn subscribe<T2, Evt>(
|
||||
&mut self,
|
||||
entity: &Entity<T2>,
|
||||
mut on_event: impl FnMut(&mut T, Entity<T2>, &Evt, &mut Context<T>) + 'static,
|
||||
) -> Subscription
|
||||
where
|
||||
T: 'static,
|
||||
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
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Subscribe to an event type from ourself
|
||||
pub fn subscribe_self<Evt>(
|
||||
&mut self,
|
||||
mut on_event: impl FnMut(&mut T, &Evt, &mut Context<T>) + 'static,
|
||||
) -> Subscription
|
||||
where
|
||||
T: 'static + EventEmitter<Evt>,
|
||||
Evt: 'static,
|
||||
{
|
||||
let this = self.entity();
|
||||
self.app.subscribe(&this, move |this, evt, cx| {
|
||||
this.update(cx, |this, cx| on_event(this, evt, cx))
|
||||
})
|
||||
}
|
||||
|
||||
/// Register a callback to be invoked when GPUI releases this entity.
|
||||
pub fn on_release(&self, on_release: impl FnOnce(&mut T, &mut App) + 'static) -> Subscription
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
let (subscription, activate) = self.app.release_listeners.insert(
|
||||
self.entity_state.entity_id,
|
||||
Box::new(move |this, cx| {
|
||||
let this = this.downcast_mut().expect("invalid entity type");
|
||||
on_release(this, cx);
|
||||
}),
|
||||
);
|
||||
activate();
|
||||
subscription
|
||||
}
|
||||
|
||||
/// Register a callback to be run on the release of another entity
|
||||
pub fn observe_release<T2>(
|
||||
&self,
|
||||
entity: &Entity<T2>,
|
||||
on_release: impl FnOnce(&mut T, &mut T2, &mut Context<T>) + 'static,
|
||||
) -> Subscription
|
||||
where
|
||||
T: Any,
|
||||
T2: 'static,
|
||||
{
|
||||
let entity_id = entity.entity_id();
|
||||
let this = self.weak_entity();
|
||||
let (subscription, activate) = self.app.release_listeners.insert(
|
||||
entity_id,
|
||||
Box::new(move |entity, cx| {
|
||||
let entity = entity.downcast_mut().expect("invalid entity type");
|
||||
if let Some(this) = this.upgrade() {
|
||||
this.update(cx, |this, cx| on_release(this, entity, cx));
|
||||
}
|
||||
}),
|
||||
);
|
||||
activate();
|
||||
subscription
|
||||
}
|
||||
|
||||
/// Register a callback to for updates to the given global
|
||||
pub fn observe_global<G: 'static>(
|
||||
&mut self,
|
||||
mut f: impl FnMut(&mut T, &mut Context<T>) + 'static,
|
||||
) -> Subscription
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
let handle = self.weak_entity();
|
||||
let (subscription, activate) = self.global_observers.insert(
|
||||
TypeId::of::<G>(),
|
||||
Box::new(move |cx| handle.update(cx, |view, cx| f(view, cx)).is_ok()),
|
||||
);
|
||||
self.defer(move |_| activate());
|
||||
subscription
|
||||
}
|
||||
|
||||
/// Register a callback to be invoked when the application is about to restart.
|
||||
pub fn on_app_restart(
|
||||
&self,
|
||||
mut on_restart: impl FnMut(&mut T, &mut App) + 'static,
|
||||
) -> Subscription
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
let handle = self.weak_entity();
|
||||
self.app.on_app_restart(move |cx| {
|
||||
handle.update(cx, |entity, cx| on_restart(entity, cx)).ok();
|
||||
})
|
||||
}
|
||||
|
||||
/// Arrange for the given function to be invoked whenever the application is quit.
|
||||
/// The future returned from this callback will be polled for up to [crate::SHUTDOWN_TIMEOUT] until the app fully quits.
|
||||
pub fn on_app_quit<Fut>(
|
||||
&self,
|
||||
mut on_quit: impl FnMut(&mut T, &mut Context<T>) -> Fut + 'static,
|
||||
) -> Subscription
|
||||
where
|
||||
Fut: 'static + Future<Output = ()>,
|
||||
T: 'static,
|
||||
{
|
||||
let handle = self.weak_entity();
|
||||
self.app.on_app_quit(move |cx| {
|
||||
let future = handle.update(cx, |entity, cx| on_quit(entity, cx)).ok();
|
||||
async move {
|
||||
if let Some(future) = future {
|
||||
future.await;
|
||||
}
|
||||
}
|
||||
.boxed_local()
|
||||
})
|
||||
}
|
||||
|
||||
/// Tell GPUI that this entity has changed and observers of it should be notified.
|
||||
pub fn notify(&mut self) {
|
||||
self.app.notify(self.entity_state.entity_id);
|
||||
}
|
||||
|
||||
/// Spawn the future returned by the given function.
|
||||
/// 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]
|
||||
pub fn spawn<AsyncFn, R>(&self, f: AsyncFn) -> Task<R>
|
||||
where
|
||||
T: 'static,
|
||||
AsyncFn: AsyncFnOnce(WeakEntity<T>, &mut AsyncApp) -> R + 'static,
|
||||
R: 'static,
|
||||
{
|
||||
let this = self.weak_entity();
|
||||
self.app.spawn(async move |cx| f(this, cx).await)
|
||||
}
|
||||
|
||||
/// Convenience method for accessing view state in an event callback.
|
||||
///
|
||||
/// 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.
|
||||
pub fn listener<E: ?Sized>(
|
||||
&self,
|
||||
f: 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();
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience method for producing view state in a closure.
|
||||
/// See `listener` for more details.
|
||||
pub fn processor<E, R>(
|
||||
&self,
|
||||
f: impl Fn(&mut T, E, &mut Window, &mut Context<T>) -> R + 'static,
|
||||
) -> impl Fn(E, &mut Window, &mut App) -> R + 'static {
|
||||
let view = self.entity();
|
||||
move |e: E, window: &mut Window, cx: &mut App| {
|
||||
view.update(cx, |view, cx| f(view, e, window, cx))
|
||||
}
|
||||
}
|
||||
|
||||
/// Run something using this entity and cx, when the returned struct is dropped
|
||||
pub fn on_drop(
|
||||
&self,
|
||||
f: impl FnOnce(&mut T, &mut Context<T>) + 'static,
|
||||
) -> Deferred<impl FnOnce()> {
|
||||
let this = self.weak_entity();
|
||||
let mut cx = self.to_async();
|
||||
util::defer(move || {
|
||||
this.update(&mut cx, f).ok();
|
||||
})
|
||||
}
|
||||
|
||||
/// Focus the given view in the given window. View type is required to implement Focusable.
|
||||
pub fn focus_view<W: Focusable>(&mut self, view: &Entity<W>, window: &mut Window) {
|
||||
window.focus(&view.focus_handle(self));
|
||||
}
|
||||
|
||||
/// Sets a given callback to be run on the next frame.
|
||||
pub fn on_next_frame(
|
||||
&self,
|
||||
window: &mut Window,
|
||||
f: impl FnOnce(&mut T, &mut Window, &mut Context<T>) + 'static,
|
||||
) where
|
||||
T: 'static,
|
||||
{
|
||||
let view = self.entity();
|
||||
window.on_next_frame(move |window, cx| view.update(cx, |view, cx| f(view, window, cx)));
|
||||
}
|
||||
|
||||
/// Schedules the given function to be run at the end of the current effect cycle, allowing entities
|
||||
/// that are currently on the stack to be returned to the app.
|
||||
pub fn defer_in(
|
||||
&mut self,
|
||||
window: &Window,
|
||||
f: impl FnOnce(&mut T, &mut Window, &mut Context<T>) + 'static,
|
||||
) {
|
||||
let view = self.entity();
|
||||
window.defer(self, move |window, cx| {
|
||||
view.update(cx, |view, cx| f(view, window, cx))
|
||||
});
|
||||
}
|
||||
|
||||
/// Observe another entity for changes to its state, as tracked by [`Context::notify`].
|
||||
pub fn observe_in<V2>(
|
||||
&mut self,
|
||||
observed: &Entity<V2>,
|
||||
window: &mut Window,
|
||||
mut on_notify: impl FnMut(&mut T, Entity<V2>, &mut Window, &mut Context<T>) + 'static,
|
||||
) -> Subscription
|
||||
where
|
||||
V2: 'static,
|
||||
T: 'static,
|
||||
{
|
||||
let observed_id = observed.entity_id();
|
||||
let observed = observed.downgrade();
|
||||
let window_handle = window.handle;
|
||||
let observer = self.weak_entity();
|
||||
self.new_observer(
|
||||
observed_id,
|
||||
Box::new(move |cx| {
|
||||
window_handle
|
||||
.update(cx, |_, window, cx| {
|
||||
if let Some((observer, observed)) =
|
||||
observer.upgrade().zip(observed.upgrade())
|
||||
{
|
||||
observer.update(cx, |observer, cx| {
|
||||
on_notify(observer, observed, window, cx);
|
||||
});
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
/// Subscribe to events emitted by another entity.
|
||||
/// The entity to which you're subscribing must implement the [`EventEmitter`] trait.
|
||||
/// The callback will be invoked with a reference to the current view, a handle to the emitting `Entity`, the event, a mutable reference to the `Window`, and the context for the entity.
|
||||
pub fn subscribe_in<Emitter, Evt>(
|
||||
&mut self,
|
||||
emitter: &Entity<Emitter>,
|
||||
window: &Window,
|
||||
mut on_event: impl FnMut(&mut T, &Entity<Emitter>, &Evt, &mut Window, &mut Context<T>) + 'static,
|
||||
) -> Subscription
|
||||
where
|
||||
Emitter: EventEmitter<Evt>,
|
||||
Evt: 'static,
|
||||
{
|
||||
let emitter = emitter.downgrade();
|
||||
let window_handle = window.handle;
|
||||
let subscriber = self.weak_entity();
|
||||
self.new_subscription(
|
||||
emitter.entity_id(),
|
||||
(
|
||||
TypeId::of::<Evt>(),
|
||||
Box::new(move |event, cx| {
|
||||
window_handle
|
||||
.update(cx, |_, window, cx| {
|
||||
if let Some((subscriber, emitter)) =
|
||||
subscriber.upgrade().zip(emitter.upgrade())
|
||||
{
|
||||
let event = event.downcast_ref().expect("invalid event type");
|
||||
subscriber.update(cx, |subscriber, cx| {
|
||||
on_event(subscriber, &emitter, event, window, cx);
|
||||
});
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/// Register a callback to be invoked when the view is released.
|
||||
///
|
||||
/// The callback receives a handle to the view's window. This handle may be
|
||||
/// invalid, if the window was closed before the view was released.
|
||||
pub fn on_release_in(
|
||||
&mut self,
|
||||
window: &Window,
|
||||
on_release: impl FnOnce(&mut T, &mut Window, &mut App) + 'static,
|
||||
) -> Subscription {
|
||||
let entity = self.entity();
|
||||
self.app.observe_release_in(&entity, window, on_release)
|
||||
}
|
||||
|
||||
/// Register a callback to be invoked when the given Entity is released.
|
||||
pub fn observe_release_in<T2>(
|
||||
&self,
|
||||
observed: &Entity<T2>,
|
||||
window: &Window,
|
||||
mut on_release: impl FnMut(&mut T, &mut T2, &mut Window, &mut Context<T>) + 'static,
|
||||
) -> Subscription
|
||||
where
|
||||
T: 'static,
|
||||
T2: 'static,
|
||||
{
|
||||
let observer = self.weak_entity();
|
||||
self.app
|
||||
.observe_release_in(observed, window, move |observed, window, cx| {
|
||||
observer
|
||||
.update(cx, |observer, cx| {
|
||||
on_release(observer, observed, window, cx)
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
}
|
||||
|
||||
/// Register a callback to be invoked when the window is resized.
|
||||
pub fn observe_window_bounds(
|
||||
&self,
|
||||
window: &mut Window,
|
||||
mut callback: impl FnMut(&mut T, &mut Window, &mut Context<T>) + 'static,
|
||||
) -> Subscription {
|
||||
let view = self.weak_entity();
|
||||
let (subscription, activate) = window.bounds_observers.insert(
|
||||
(),
|
||||
Box::new(move |window, cx| {
|
||||
view.update(cx, |view, cx| callback(view, window, cx))
|
||||
.is_ok()
|
||||
}),
|
||||
);
|
||||
activate();
|
||||
subscription
|
||||
}
|
||||
|
||||
/// Register a callback to be invoked when the window is activated or deactivated.
|
||||
pub fn observe_window_activation(
|
||||
&self,
|
||||
window: &mut Window,
|
||||
mut callback: impl FnMut(&mut T, &mut Window, &mut Context<T>) + 'static,
|
||||
) -> Subscription {
|
||||
let view = self.weak_entity();
|
||||
let (subscription, activate) = window.activation_observers.insert(
|
||||
(),
|
||||
Box::new(move |window, cx| {
|
||||
view.update(cx, |view, cx| callback(view, window, cx))
|
||||
.is_ok()
|
||||
}),
|
||||
);
|
||||
activate();
|
||||
subscription
|
||||
}
|
||||
|
||||
/// Registers a callback to be invoked when the window appearance changes.
|
||||
pub fn observe_window_appearance(
|
||||
&self,
|
||||
window: &mut Window,
|
||||
mut callback: impl FnMut(&mut T, &mut Window, &mut Context<T>) + 'static,
|
||||
) -> Subscription {
|
||||
let view = self.weak_entity();
|
||||
let (subscription, activate) = window.appearance_observers.insert(
|
||||
(),
|
||||
Box::new(move |window, cx| {
|
||||
view.update(cx, |view, cx| callback(view, window, cx))
|
||||
.is_ok()
|
||||
}),
|
||||
);
|
||||
activate();
|
||||
subscription
|
||||
}
|
||||
|
||||
/// Register a callback to be invoked when a keystroke is received by the application
|
||||
/// in any window. Note that this fires after all other action and event mechanisms have resolved
|
||||
/// and that this API will not be invoked if the event's propagation is stopped.
|
||||
pub fn observe_keystrokes(
|
||||
&mut self,
|
||||
mut f: impl FnMut(&mut T, &KeystrokeEvent, &mut Window, &mut Context<T>) + 'static,
|
||||
) -> Subscription {
|
||||
fn inner(
|
||||
keystroke_observers: &SubscriberSet<(), KeystrokeObserver>,
|
||||
handler: KeystrokeObserver,
|
||||
) -> Subscription {
|
||||
let (subscription, activate) = keystroke_observers.insert((), handler);
|
||||
activate();
|
||||
subscription
|
||||
}
|
||||
|
||||
let view = self.weak_entity();
|
||||
inner(
|
||||
&self.keystroke_observers,
|
||||
Box::new(move |event, window, cx| {
|
||||
if let Some(view) = view.upgrade() {
|
||||
view.update(cx, |view, cx| f(view, event, window, cx));
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
/// Register a callback to be invoked when the window's pending input changes.
|
||||
pub fn observe_pending_input(
|
||||
&self,
|
||||
window: &mut Window,
|
||||
mut callback: impl FnMut(&mut T, &mut Window, &mut Context<T>) + 'static,
|
||||
) -> Subscription {
|
||||
let view = self.weak_entity();
|
||||
let (subscription, activate) = window.pending_input_observers.insert(
|
||||
(),
|
||||
Box::new(move |window, cx| {
|
||||
view.update(cx, |view, cx| callback(view, window, cx))
|
||||
.is_ok()
|
||||
}),
|
||||
);
|
||||
activate();
|
||||
subscription
|
||||
}
|
||||
|
||||
/// Register a listener to be called when the given focus handle receives focus.
|
||||
/// Returns a subscription and persists until the subscription is dropped.
|
||||
pub fn on_focus(
|
||||
&mut self,
|
||||
handle: &FocusHandle,
|
||||
window: &mut Window,
|
||||
mut listener: impl FnMut(&mut T, &mut Window, &mut Context<T>) + 'static,
|
||||
) -> Subscription {
|
||||
let view = self.weak_entity();
|
||||
let focus_id = handle.id;
|
||||
let (subscription, activate) =
|
||||
window.new_focus_listener(Box::new(move |event, window, cx| {
|
||||
view.update(cx, |view, cx| {
|
||||
if event.previous_focus_path.last() != Some(&focus_id)
|
||||
&& event.current_focus_path.last() == Some(&focus_id)
|
||||
{
|
||||
listener(view, window, cx)
|
||||
}
|
||||
})
|
||||
.is_ok()
|
||||
}));
|
||||
self.defer(|_| activate());
|
||||
subscription
|
||||
}
|
||||
|
||||
/// Register a listener to be called when the given focus handle or one of its descendants receives focus.
|
||||
/// This does not fire if the given focus handle - or one of its descendants - was previously focused.
|
||||
/// Returns a subscription and persists until the subscription is dropped.
|
||||
pub fn on_focus_in(
|
||||
&mut self,
|
||||
handle: &FocusHandle,
|
||||
window: &mut Window,
|
||||
mut listener: impl FnMut(&mut T, &mut Window, &mut Context<T>) + 'static,
|
||||
) -> Subscription {
|
||||
let view = self.weak_entity();
|
||||
let focus_id = handle.id;
|
||||
let (subscription, activate) =
|
||||
window.new_focus_listener(Box::new(move |event, window, cx| {
|
||||
view.update(cx, |view, cx| {
|
||||
if event.is_focus_in(focus_id) {
|
||||
listener(view, window, cx)
|
||||
}
|
||||
})
|
||||
.is_ok()
|
||||
}));
|
||||
self.defer(|_| activate());
|
||||
subscription
|
||||
}
|
||||
|
||||
/// Register a listener to be called when the given focus handle loses focus.
|
||||
/// Returns a subscription and persists until the subscription is dropped.
|
||||
pub fn on_blur(
|
||||
&mut self,
|
||||
handle: &FocusHandle,
|
||||
window: &mut Window,
|
||||
mut listener: impl FnMut(&mut T, &mut Window, &mut Context<T>) + 'static,
|
||||
) -> Subscription {
|
||||
let view = self.weak_entity();
|
||||
let focus_id = handle.id;
|
||||
let (subscription, activate) =
|
||||
window.new_focus_listener(Box::new(move |event, window, cx| {
|
||||
view.update(cx, |view, cx| {
|
||||
if event.previous_focus_path.last() == Some(&focus_id)
|
||||
&& event.current_focus_path.last() != Some(&focus_id)
|
||||
{
|
||||
listener(view, window, cx)
|
||||
}
|
||||
})
|
||||
.is_ok()
|
||||
}));
|
||||
self.defer(|_| activate());
|
||||
subscription
|
||||
}
|
||||
|
||||
/// Register a listener to be called when nothing in the window has focus.
|
||||
/// This typically happens when the node that was focused is removed from the tree,
|
||||
/// and this callback lets you chose a default place to restore the users focus.
|
||||
/// Returns a subscription and persists until the subscription is dropped.
|
||||
pub fn on_focus_lost(
|
||||
&mut self,
|
||||
window: &mut Window,
|
||||
mut listener: impl FnMut(&mut T, &mut Window, &mut Context<T>) + 'static,
|
||||
) -> Subscription {
|
||||
let view = self.weak_entity();
|
||||
let (subscription, activate) = window.focus_lost_listeners.insert(
|
||||
(),
|
||||
Box::new(move |window, cx| {
|
||||
view.update(cx, |view, cx| listener(view, window, cx))
|
||||
.is_ok()
|
||||
}),
|
||||
);
|
||||
self.defer(|_| activate());
|
||||
subscription
|
||||
}
|
||||
|
||||
/// Register a listener to be called when the given focus handle or one of its descendants loses focus.
|
||||
/// Returns a subscription and persists until the subscription is dropped.
|
||||
pub fn on_focus_out(
|
||||
&mut self,
|
||||
handle: &FocusHandle,
|
||||
window: &mut Window,
|
||||
mut listener: impl FnMut(&mut T, FocusOutEvent, &mut Window, &mut Context<T>) + 'static,
|
||||
) -> Subscription {
|
||||
let view = self.weak_entity();
|
||||
let focus_id = handle.id;
|
||||
let (subscription, activate) =
|
||||
window.new_focus_listener(Box::new(move |event, window, cx| {
|
||||
view.update(cx, |view, cx| {
|
||||
if let Some(blurred_id) = event.previous_focus_path.last().copied()
|
||||
&& event.is_focus_out(focus_id)
|
||||
{
|
||||
let event = FocusOutEvent {
|
||||
blurred: WeakFocusHandle {
|
||||
id: blurred_id,
|
||||
handles: Arc::downgrade(&cx.focus_handles),
|
||||
},
|
||||
};
|
||||
listener(view, event, window, cx)
|
||||
}
|
||||
})
|
||||
.is_ok()
|
||||
}));
|
||||
self.defer(|_| activate());
|
||||
subscription
|
||||
}
|
||||
|
||||
/// Schedule a future to be run asynchronously.
|
||||
/// The given callback is invoked with a [`WeakEntity<V>`] to avoid leaking the entity for a long-running process.
|
||||
/// 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]
|
||||
pub fn spawn_in<AsyncFn, R>(&self, window: &Window, f: AsyncFn) -> Task<R>
|
||||
where
|
||||
R: 'static,
|
||||
AsyncFn: AsyncFnOnce(WeakEntity<T>, &mut AsyncWindowContext) -> R + 'static,
|
||||
{
|
||||
let view = self.weak_entity();
|
||||
window.spawn(self, async move |cx| f(view, cx).await)
|
||||
}
|
||||
|
||||
/// Register a callback to be invoked when the given global state changes.
|
||||
pub fn observe_global_in<G: Global>(
|
||||
&mut self,
|
||||
window: &Window,
|
||||
mut f: impl FnMut(&mut T, &mut Window, &mut Context<T>) + 'static,
|
||||
) -> Subscription {
|
||||
let window_handle = window.handle;
|
||||
let view = self.weak_entity();
|
||||
let (subscription, activate) = self.global_observers.insert(
|
||||
TypeId::of::<G>(),
|
||||
Box::new(move |cx| {
|
||||
window_handle
|
||||
.update(cx, |_, window, cx| {
|
||||
view.update(cx, |view, cx| f(view, window, cx)).is_ok()
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}),
|
||||
);
|
||||
self.defer(move |_| activate());
|
||||
subscription
|
||||
}
|
||||
|
||||
/// Register a callback to be invoked when the given Action type is dispatched to the window.
|
||||
pub fn on_action(
|
||||
&mut self,
|
||||
action_type: TypeId,
|
||||
window: &mut Window,
|
||||
listener: impl Fn(&mut T, &dyn Any, DispatchPhase, &mut Window, &mut Context<T>) + 'static,
|
||||
) {
|
||||
let handle = self.weak_entity();
|
||||
window.on_action(action_type, move |action, phase, window, cx| {
|
||||
handle
|
||||
.update(cx, |view, cx| {
|
||||
listener(view, action, phase, window, cx);
|
||||
})
|
||||
.ok();
|
||||
});
|
||||
}
|
||||
|
||||
/// Move focus to the current view, assuming it implements [`Focusable`].
|
||||
pub fn focus_self(&mut self, window: &mut Window)
|
||||
where
|
||||
T: Focusable,
|
||||
{
|
||||
let view = self.entity();
|
||||
window.defer(self, move |window, cx| {
|
||||
view.read(cx).focus_handle(cx).focus(window)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Context<'_, T> {
|
||||
/// Emit an event of the specified type, which can be handled by other entities that have subscribed via `subscribe` methods on their respective contexts.
|
||||
pub fn emit<Evt>(&mut self, event: Evt)
|
||||
where
|
||||
T: EventEmitter<Evt>,
|
||||
Evt: 'static,
|
||||
{
|
||||
self.app.pending_effects.push_back(Effect::Emit {
|
||||
emitter: self.entity_state.entity_id,
|
||||
event_type: TypeId::of::<Evt>(),
|
||||
event: Box::new(event),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AppContext for Context<'_, T> {
|
||||
type Result<U> = U;
|
||||
|
||||
fn new<U: 'static>(&mut self, build_entity: impl FnOnce(&mut Context<U>) -> U) -> Entity<U> {
|
||||
self.app.new(build_entity)
|
||||
}
|
||||
|
||||
fn reserve_entity<U: 'static>(&mut self) -> Reservation<U> {
|
||||
self.app.reserve_entity()
|
||||
}
|
||||
|
||||
fn insert_entity<U: 'static>(
|
||||
&mut self,
|
||||
reservation: Reservation<U>,
|
||||
build_entity: impl FnOnce(&mut Context<U>) -> U,
|
||||
) -> Self::Result<Entity<U>> {
|
||||
self.app.insert_entity(reservation, build_entity)
|
||||
}
|
||||
|
||||
fn update_entity<U: 'static, R>(
|
||||
&mut self,
|
||||
handle: &Entity<U>,
|
||||
update: impl FnOnce(&mut U, &mut Context<U>) -> R,
|
||||
) -> R {
|
||||
self.app.update_entity(handle, update)
|
||||
}
|
||||
|
||||
fn as_mut<'a, E>(&'a mut self, handle: &Entity<E>) -> Self::Result<super::GpuiBorrow<'a, E>>
|
||||
where
|
||||
E: 'static,
|
||||
{
|
||||
self.app.as_mut(handle)
|
||||
}
|
||||
|
||||
fn read_entity<U, R>(
|
||||
&self,
|
||||
handle: &Entity<U>,
|
||||
read: impl FnOnce(&U, &App) -> R,
|
||||
) -> Self::Result<R>
|
||||
where
|
||||
U: 'static,
|
||||
{
|
||||
self.app.read_entity(handle, read)
|
||||
}
|
||||
|
||||
fn update_window<R, F>(&mut self, window: AnyWindowHandle, update: F) -> Result<R>
|
||||
where
|
||||
F: FnOnce(AnyView, &mut Window, &mut App) -> R,
|
||||
{
|
||||
self.app.update_window(window, update)
|
||||
}
|
||||
|
||||
fn read_window<U, R>(
|
||||
&self,
|
||||
window: &WindowHandle<U>,
|
||||
read: impl FnOnce(Entity<U>, &App) -> R,
|
||||
) -> Result<R>
|
||||
where
|
||||
U: 'static,
|
||||
{
|
||||
self.app.read_window(window, read)
|
||||
}
|
||||
|
||||
fn background_spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
|
||||
where
|
||||
R: Send + 'static,
|
||||
{
|
||||
self.app.background_executor.spawn(future)
|
||||
}
|
||||
|
||||
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> Self::Result<R>
|
||||
where
|
||||
G: Global,
|
||||
{
|
||||
self.app.read_global(callback)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Borrow<App> for Context<'_, T> {
|
||||
fn borrow(&self) -> &App {
|
||||
self.app
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> BorrowMut<App> for Context<'_, T> {
|
||||
fn borrow_mut(&mut self) -> &mut App {
|
||||
self.app
|
||||
}
|
||||
}
|
||||
+889
@@ -0,0 +1,889 @@
|
||||
use crate::{App, AppContext, GpuiBorrow, VisualContext, Window, seal::Sealed};
|
||||
use anyhow::{Context as _, Result};
|
||||
use collections::FxHashSet;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use parking_lot::{RwLock, RwLockUpgradableReadGuard};
|
||||
use slotmap::{KeyData, SecondaryMap, SlotMap};
|
||||
use std::{
|
||||
any::{Any, TypeId, type_name},
|
||||
cell::RefCell,
|
||||
cmp::Ordering,
|
||||
fmt::{self, Display},
|
||||
hash::{Hash, Hasher},
|
||||
marker::PhantomData,
|
||||
mem,
|
||||
num::NonZeroU64,
|
||||
sync::{
|
||||
Arc, Weak,
|
||||
atomic::{AtomicU64, AtomicUsize, Ordering::SeqCst},
|
||||
},
|
||||
thread::panicking,
|
||||
};
|
||||
|
||||
use super::Context;
|
||||
use crate::util::atomic_incr_if_not_zero;
|
||||
#[cfg(any(test, feature = "leak-detection"))]
|
||||
use collections::HashMap;
|
||||
|
||||
slotmap::new_key_type! {
|
||||
/// A unique identifier for a entity across the application.
|
||||
pub struct EntityId;
|
||||
}
|
||||
|
||||
impl From<u64> for EntityId {
|
||||
fn from(value: u64) -> Self {
|
||||
Self(KeyData::from_ffi(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl EntityId {
|
||||
/// Converts this entity id to a [NonZeroU64]
|
||||
pub fn as_non_zero_u64(self) -> NonZeroU64 {
|
||||
NonZeroU64::new(self.0.as_ffi()).unwrap()
|
||||
}
|
||||
|
||||
/// Converts this entity id to a [u64]
|
||||
pub fn as_u64(self) -> u64 {
|
||||
self.0.as_ffi()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for EntityId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.as_u64())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct EntityMap {
|
||||
entities: SecondaryMap<EntityId, Box<dyn Any>>,
|
||||
pub accessed_entities: RefCell<FxHashSet<EntityId>>,
|
||||
ref_counts: Arc<RwLock<EntityRefCounts>>,
|
||||
}
|
||||
|
||||
struct EntityRefCounts {
|
||||
counts: SlotMap<EntityId, AtomicUsize>,
|
||||
dropped_entity_ids: Vec<EntityId>,
|
||||
#[cfg(any(test, feature = "leak-detection"))]
|
||||
leak_detector: LeakDetector,
|
||||
}
|
||||
|
||||
impl EntityMap {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
entities: SecondaryMap::new(),
|
||||
accessed_entities: RefCell::new(FxHashSet::default()),
|
||||
ref_counts: Arc::new(RwLock::new(EntityRefCounts {
|
||||
counts: SlotMap::with_key(),
|
||||
dropped_entity_ids: Vec::new(),
|
||||
#[cfg(any(test, feature = "leak-detection"))]
|
||||
leak_detector: LeakDetector {
|
||||
next_handle_id: 0,
|
||||
entity_handles: HashMap::default(),
|
||||
},
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
/// Reserve a slot for an entity, which you can subsequently use with `insert`.
|
||||
pub fn reserve<T: 'static>(&self) -> Slot<T> {
|
||||
let id = self.ref_counts.write().counts.insert(1.into());
|
||||
Slot(Entity::new(id, Arc::downgrade(&self.ref_counts)))
|
||||
}
|
||||
|
||||
/// Insert an entity into a slot obtained by calling `reserve`.
|
||||
pub fn insert<T>(&mut self, slot: Slot<T>, entity: T) -> Entity<T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
let mut accessed_entities = self.accessed_entities.borrow_mut();
|
||||
accessed_entities.insert(slot.entity_id);
|
||||
|
||||
let handle = slot.0;
|
||||
self.entities.insert(handle.entity_id, Box::new(entity));
|
||||
handle
|
||||
}
|
||||
|
||||
/// 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.borrow_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,
|
||||
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 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)
|
||||
.and_then(|entity| entity.downcast_ref())
|
||||
.unwrap_or_else(|| double_lease_panic::<T>("read"))
|
||||
}
|
||||
|
||||
fn assert_valid_context(&self, entity: &AnyEntity) {
|
||||
debug_assert!(
|
||||
Weak::ptr_eq(&entity.entity_map, &Arc::downgrade(&self.ref_counts)),
|
||||
"used a entity with the wrong context"
|
||||
);
|
||||
}
|
||||
|
||||
pub fn extend_accessed(&mut self, entities: &FxHashSet<EntityId>) {
|
||||
self.accessed_entities
|
||||
.borrow_mut()
|
||||
.extend(entities.iter().copied());
|
||||
}
|
||||
|
||||
pub fn clear_accessed(&mut self) {
|
||||
self.accessed_entities.borrow_mut().clear();
|
||||
}
|
||||
|
||||
pub fn take_dropped(&mut self) -> Vec<(EntityId, Box<dyn Any>)> {
|
||||
let mut ref_counts = self.ref_counts.write();
|
||||
let dropped_entity_ids = mem::take(&mut ref_counts.dropped_entity_ids);
|
||||
let mut accessed_entities = self.accessed_entities.borrow_mut();
|
||||
|
||||
dropped_entity_ids
|
||||
.into_iter()
|
||||
.filter_map(|entity_id| {
|
||||
let count = ref_counts.counts.remove(entity_id).unwrap();
|
||||
debug_assert_eq!(
|
||||
count.load(SeqCst),
|
||||
0,
|
||||
"dropped an entity that was referenced"
|
||||
);
|
||||
accessed_entities.remove(&entity_id);
|
||||
// If the EntityId was allocated with `Context::reserve`,
|
||||
// the entity may not have been inserted.
|
||||
Some((entity_id, self.entities.remove(entity_id)?))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn double_lease_panic<T>(operation: &str) -> ! {
|
||||
panic!(
|
||||
"cannot {operation} {} while it is already being updated",
|
||||
std::any::type_name::<T>()
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) struct Lease<T> {
|
||||
entity: Option<Box<dyn Any>>,
|
||||
pub id: EntityId,
|
||||
entity_type: PhantomData<T>,
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for Lease<T> {
|
||||
fn drop(&mut self) {
|
||||
if self.entity.is_some() && !panicking() {
|
||||
panic!("Leases must be ended with EntityMap::end_lease")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deref, DerefMut)]
|
||||
pub(crate) struct Slot<T>(Entity<T>);
|
||||
|
||||
/// A dynamically typed reference to a entity, which can be downcast into a `Entity<T>`.
|
||||
pub struct AnyEntity {
|
||||
pub(crate) entity_id: EntityId,
|
||||
pub(crate) entity_type: TypeId,
|
||||
entity_map: Weak<RwLock<EntityRefCounts>>,
|
||||
#[cfg(any(test, feature = "leak-detection"))]
|
||||
handle_id: HandleId,
|
||||
}
|
||||
|
||||
impl AnyEntity {
|
||||
fn new(id: EntityId, entity_type: TypeId, entity_map: Weak<RwLock<EntityRefCounts>>) -> Self {
|
||||
Self {
|
||||
entity_id: id,
|
||||
entity_type,
|
||||
#[cfg(any(test, feature = "leak-detection"))]
|
||||
handle_id: entity_map
|
||||
.clone()
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
.write()
|
||||
.leak_detector
|
||||
.handle_created(id),
|
||||
entity_map,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the id associated with this entity.
|
||||
pub fn entity_id(&self) -> EntityId {
|
||||
self.entity_id
|
||||
}
|
||||
|
||||
/// Returns the [TypeId] associated with this entity.
|
||||
pub fn entity_type(&self) -> TypeId {
|
||||
self.entity_type
|
||||
}
|
||||
|
||||
/// Converts this entity handle into a weak variant, which does not prevent it from being released.
|
||||
pub fn downgrade(&self) -> AnyWeakEntity {
|
||||
AnyWeakEntity {
|
||||
entity_id: self.entity_id,
|
||||
entity_type: self.entity_type,
|
||||
entity_ref_counts: self.entity_map.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts this entity handle into a strongly-typed entity handle of the given type.
|
||||
/// If this entity handle is not of the specified type, returns itself as an error variant.
|
||||
pub fn downcast<T: 'static>(self) -> Result<Entity<T>, AnyEntity> {
|
||||
if TypeId::of::<T>() == self.entity_type {
|
||||
Ok(Entity {
|
||||
any_entity: self,
|
||||
entity_type: PhantomData,
|
||||
})
|
||||
} else {
|
||||
Err(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for AnyEntity {
|
||||
fn clone(&self) -> Self {
|
||||
if let Some(entity_map) = self.entity_map.upgrade() {
|
||||
let entity_map = entity_map.read();
|
||||
let count = entity_map
|
||||
.counts
|
||||
.get(self.entity_id)
|
||||
.expect("detected over-release of a entity");
|
||||
let prev_count = count.fetch_add(1, SeqCst);
|
||||
assert_ne!(prev_count, 0, "Detected over-release of a entity.");
|
||||
}
|
||||
|
||||
Self {
|
||||
entity_id: self.entity_id,
|
||||
entity_type: self.entity_type,
|
||||
entity_map: self.entity_map.clone(),
|
||||
#[cfg(any(test, feature = "leak-detection"))]
|
||||
handle_id: self
|
||||
.entity_map
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
.write()
|
||||
.leak_detector
|
||||
.handle_created(self.entity_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for AnyEntity {
|
||||
fn drop(&mut self) {
|
||||
if let Some(entity_map) = self.entity_map.upgrade() {
|
||||
let entity_map = entity_map.upgradable_read();
|
||||
let count = entity_map
|
||||
.counts
|
||||
.get(self.entity_id)
|
||||
.expect("detected over-release of a handle.");
|
||||
let prev_count = count.fetch_sub(1, SeqCst);
|
||||
assert_ne!(prev_count, 0, "Detected over-release of a entity.");
|
||||
if prev_count == 1 {
|
||||
// We were the last reference to this entity, so we can remove it.
|
||||
let mut entity_map = RwLockUpgradableReadGuard::upgrade(entity_map);
|
||||
entity_map.dropped_entity_ids.push(self.entity_id);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "leak-detection"))]
|
||||
if let Some(entity_map) = self.entity_map.upgrade() {
|
||||
entity_map
|
||||
.write()
|
||||
.leak_detector
|
||||
.handle_released(self.entity_id, self.handle_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Entity<T>> for AnyEntity {
|
||||
fn from(entity: Entity<T>) -> Self {
|
||||
entity.any_entity
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for AnyEntity {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.entity_id.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for AnyEntity {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.entity_id == other.entity_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for AnyEntity {}
|
||||
|
||||
impl Ord for AnyEntity {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.entity_id.cmp(&other.entity_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for AnyEntity {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for AnyEntity {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("AnyEntity")
|
||||
.field("entity_id", &self.entity_id.as_u64())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// A strong, well-typed reference to a struct which is managed
|
||||
/// by GPUI
|
||||
#[derive(Deref, DerefMut)]
|
||||
pub struct Entity<T> {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
pub(crate) any_entity: AnyEntity,
|
||||
pub(crate) entity_type: PhantomData<fn(T) -> T>,
|
||||
}
|
||||
|
||||
impl<T> Sealed for Entity<T> {}
|
||||
|
||||
impl<T: 'static> Entity<T> {
|
||||
fn new(id: EntityId, entity_map: Weak<RwLock<EntityRefCounts>>) -> Self
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
Self {
|
||||
any_entity: AnyEntity::new(id, TypeId::of::<T>(), entity_map),
|
||||
entity_type: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the entity ID associated with this entity
|
||||
pub fn entity_id(&self) -> EntityId {
|
||||
self.any_entity.entity_id
|
||||
}
|
||||
|
||||
/// Downgrade this entity pointer to a non-retaining weak pointer
|
||||
pub fn downgrade(&self) -> WeakEntity<T> {
|
||||
WeakEntity {
|
||||
any_entity: self.any_entity.downgrade(),
|
||||
entity_type: self.entity_type,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert this into a dynamically typed entity.
|
||||
pub fn into_any(self) -> AnyEntity {
|
||||
self.any_entity
|
||||
}
|
||||
|
||||
/// Grab a reference to this entity from the context.
|
||||
pub fn read<'a>(&self, cx: &'a App) -> &'a T {
|
||||
cx.entities.read(self)
|
||||
}
|
||||
|
||||
/// Read the entity referenced by this handle with the given function.
|
||||
pub fn read_with<R, C: AppContext>(
|
||||
&self,
|
||||
cx: &C,
|
||||
f: impl FnOnce(&T, &App) -> R,
|
||||
) -> C::Result<R> {
|
||||
cx.read_entity(self, f)
|
||||
}
|
||||
|
||||
/// Updates the entity referenced by this handle with the given function.
|
||||
pub fn update<R, C: AppContext>(
|
||||
&self,
|
||||
cx: &mut C,
|
||||
update: impl FnOnce(&mut T, &mut Context<T>) -> R,
|
||||
) -> C::Result<R> {
|
||||
cx.update_entity(self, update)
|
||||
}
|
||||
|
||||
/// Updates the entity referenced by this handle with the given function.
|
||||
pub fn as_mut<'a, C: AppContext>(&self, cx: &'a mut C) -> C::Result<GpuiBorrow<'a, T>> {
|
||||
cx.as_mut(self)
|
||||
}
|
||||
|
||||
/// Updates the entity referenced by this handle with the given function.
|
||||
pub fn write<C: AppContext>(&self, cx: &mut C, value: T) -> C::Result<()> {
|
||||
self.update(cx, |entity, cx| {
|
||||
*entity = value;
|
||||
cx.notify();
|
||||
})
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub fn update_in<R, C: VisualContext>(
|
||||
&self,
|
||||
cx: &mut C,
|
||||
update: impl FnOnce(&mut T, &mut Window, &mut Context<T>) -> R,
|
||||
) -> C::Result<R> {
|
||||
cx.update_window_entity(self, update)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for Entity<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
any_entity: self.any_entity.clone(),
|
||||
entity_type: self.entity_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::fmt::Debug for Entity<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Entity")
|
||||
.field("entity_id", &self.any_entity.entity_id)
|
||||
.field("entity_type", &type_name::<T>())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Hash for Entity<T> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.any_entity.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> PartialEq for Entity<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.any_entity == other.any_entity
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Eq for Entity<T> {}
|
||||
|
||||
impl<T> PartialEq<WeakEntity<T>> for Entity<T> {
|
||||
fn eq(&self, other: &WeakEntity<T>) -> bool {
|
||||
self.any_entity.entity_id() == other.entity_id()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> Ord for Entity<T> {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.entity_id().cmp(&other.entity_id())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> PartialOrd for Entity<T> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
/// A type erased, weak reference to a entity.
|
||||
#[derive(Clone)]
|
||||
pub struct AnyWeakEntity {
|
||||
pub(crate) entity_id: EntityId,
|
||||
entity_type: TypeId,
|
||||
entity_ref_counts: Weak<RwLock<EntityRefCounts>>,
|
||||
}
|
||||
|
||||
impl AnyWeakEntity {
|
||||
/// Get the entity ID associated with this weak reference.
|
||||
pub fn entity_id(&self) -> EntityId {
|
||||
self.entity_id
|
||||
}
|
||||
|
||||
/// Check if this weak handle can be upgraded, or if the entity has already been dropped
|
||||
pub fn is_upgradable(&self) -> bool {
|
||||
let ref_count = self
|
||||
.entity_ref_counts
|
||||
.upgrade()
|
||||
.and_then(|ref_counts| Some(ref_counts.read().counts.get(self.entity_id)?.load(SeqCst)))
|
||||
.unwrap_or(0);
|
||||
ref_count > 0
|
||||
}
|
||||
|
||||
/// Upgrade this weak entity reference to a strong reference.
|
||||
pub fn upgrade(&self) -> Option<AnyEntity> {
|
||||
let ref_counts = &self.entity_ref_counts.upgrade()?;
|
||||
let ref_counts = ref_counts.read();
|
||||
let ref_count = ref_counts.counts.get(self.entity_id)?;
|
||||
|
||||
if atomic_incr_if_not_zero(ref_count) == 0 {
|
||||
// entity_id is in dropped_entity_ids
|
||||
return None;
|
||||
}
|
||||
drop(ref_counts);
|
||||
|
||||
Some(AnyEntity {
|
||||
entity_id: self.entity_id,
|
||||
entity_type: self.entity_type,
|
||||
entity_map: self.entity_ref_counts.clone(),
|
||||
#[cfg(any(test, feature = "leak-detection"))]
|
||||
handle_id: self
|
||||
.entity_ref_counts
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
.write()
|
||||
.leak_detector
|
||||
.handle_created(self.entity_id),
|
||||
})
|
||||
}
|
||||
|
||||
/// Assert that entity referenced by this weak handle has been released.
|
||||
#[cfg(any(test, feature = "leak-detection"))]
|
||||
pub fn assert_released(&self) {
|
||||
self.entity_ref_counts
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
.write()
|
||||
.leak_detector
|
||||
.assert_released(self.entity_id);
|
||||
|
||||
if self
|
||||
.entity_ref_counts
|
||||
.upgrade()
|
||||
.and_then(|ref_counts| Some(ref_counts.read().counts.get(self.entity_id)?.load(SeqCst)))
|
||||
.is_some()
|
||||
{
|
||||
panic!(
|
||||
"entity was recently dropped but resources are retained until the end of the effect cycle."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a weak entity that can never be upgraded.
|
||||
pub fn new_invalid() -> Self {
|
||||
/// To hold the invariant that all ids are unique, and considering that slotmap
|
||||
/// increases their IDs from `0`, we can decrease ours from `u64::MAX` so these
|
||||
/// two will never conflict (u64 is way too large).
|
||||
static UNIQUE_NON_CONFLICTING_ID_GENERATOR: AtomicU64 = AtomicU64::new(u64::MAX);
|
||||
let entity_id = UNIQUE_NON_CONFLICTING_ID_GENERATOR.fetch_sub(1, SeqCst);
|
||||
|
||||
Self {
|
||||
// Safety:
|
||||
// Docs say this is safe but can be unspecified if slotmap changes the representation
|
||||
// after `1.0.7`, that said, providing a valid entity_id here is not necessary as long
|
||||
// as we guarantee that `entity_id` is never used if `entity_ref_counts` equals
|
||||
// to `Weak::new()` (that is, it's unable to upgrade), that is the invariant that
|
||||
// actually needs to be hold true.
|
||||
//
|
||||
// And there is no sane reason to read an entity slot if `entity_ref_counts` can't be
|
||||
// read in the first place, so we're good!
|
||||
entity_id: entity_id.into(),
|
||||
entity_type: TypeId::of::<()>(),
|
||||
entity_ref_counts: Weak::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for AnyWeakEntity {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct(type_name::<Self>())
|
||||
.field("entity_id", &self.entity_id)
|
||||
.field("entity_type", &self.entity_type)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<WeakEntity<T>> for AnyWeakEntity {
|
||||
fn from(entity: WeakEntity<T>) -> Self {
|
||||
entity.any_entity
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for AnyWeakEntity {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.entity_id.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for AnyWeakEntity {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.entity_id == other.entity_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for AnyWeakEntity {}
|
||||
|
||||
impl Ord for AnyWeakEntity {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.entity_id.cmp(&other.entity_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for AnyWeakEntity {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
/// A weak reference to a entity of the given type.
|
||||
#[derive(Deref, DerefMut)]
|
||||
pub struct WeakEntity<T> {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
any_entity: AnyWeakEntity,
|
||||
entity_type: PhantomData<fn(T) -> T>,
|
||||
}
|
||||
|
||||
impl<T> std::fmt::Debug for WeakEntity<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct(type_name::<Self>())
|
||||
.field("entity_id", &self.any_entity.entity_id)
|
||||
.field("entity_type", &type_name::<T>())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for WeakEntity<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
any_entity: self.any_entity.clone(),
|
||||
entity_type: self.entity_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> WeakEntity<T> {
|
||||
/// Upgrade this weak entity reference into a strong entity reference
|
||||
pub fn upgrade(&self) -> Option<Entity<T>> {
|
||||
Some(Entity {
|
||||
any_entity: self.any_entity.upgrade()?,
|
||||
entity_type: self.entity_type,
|
||||
})
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub fn update<C, R>(
|
||||
&self,
|
||||
cx: &mut C,
|
||||
update: impl FnOnce(&mut T, &mut Context<T>) -> R,
|
||||
) -> Result<R>
|
||||
where
|
||||
C: AppContext,
|
||||
Result<C::Result<R>>: crate::Flatten<R>,
|
||||
{
|
||||
crate::Flatten::flatten(
|
||||
self.upgrade()
|
||||
.context("entity released")
|
||||
.map(|this| cx.update_entity(&this, update)),
|
||||
)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub fn update_in<C, R>(
|
||||
&self,
|
||||
cx: &mut C,
|
||||
update: impl FnOnce(&mut T, &mut Window, &mut Context<T>) -> R,
|
||||
) -> Result<R>
|
||||
where
|
||||
C: VisualContext,
|
||||
Result<C::Result<R>>: crate::Flatten<R>,
|
||||
{
|
||||
let window = cx.window_handle();
|
||||
let this = self.upgrade().context("entity released")?;
|
||||
|
||||
crate::Flatten::flatten(window.update(cx, |_, window, cx| {
|
||||
this.update(cx, |entity, cx| update(entity, window, cx))
|
||||
}))
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub fn read_with<C, R>(&self, cx: &C, read: impl FnOnce(&T, &App) -> R) -> Result<R>
|
||||
where
|
||||
C: AppContext,
|
||||
Result<C::Result<R>>: crate::Flatten<R>,
|
||||
{
|
||||
crate::Flatten::flatten(
|
||||
self.upgrade()
|
||||
.context("entity released")
|
||||
.map(|this| cx.read_entity(&this, read)),
|
||||
)
|
||||
}
|
||||
|
||||
/// Create a new weak entity that can never be upgraded.
|
||||
pub fn new_invalid() -> Self {
|
||||
Self {
|
||||
any_entity: AnyWeakEntity::new_invalid(),
|
||||
entity_type: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Hash for WeakEntity<T> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.any_entity.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> PartialEq for WeakEntity<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.any_entity == other.any_entity
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Eq for WeakEntity<T> {}
|
||||
|
||||
impl<T> PartialEq<Entity<T>> for WeakEntity<T> {
|
||||
fn eq(&self, other: &Entity<T>) -> bool {
|
||||
self.entity_id() == other.any_entity.entity_id()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> Ord for WeakEntity<T> {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.entity_id().cmp(&other.entity_id())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> PartialOrd for WeakEntity<T> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "leak-detection"))]
|
||||
static LEAK_BACKTRACE: std::sync::LazyLock<bool> =
|
||||
std::sync::LazyLock::new(|| std::env::var("LEAK_BACKTRACE").is_ok_and(|b| !b.is_empty()));
|
||||
|
||||
#[cfg(any(test, feature = "leak-detection"))]
|
||||
#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq)]
|
||||
pub(crate) struct HandleId {
|
||||
id: u64, // id of the handle itself, not the pointed at object
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "leak-detection"))]
|
||||
pub(crate) struct LeakDetector {
|
||||
next_handle_id: u64,
|
||||
entity_handles: HashMap<EntityId, HashMap<HandleId, Option<backtrace::Backtrace>>>,
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "leak-detection"))]
|
||||
impl LeakDetector {
|
||||
#[track_caller]
|
||||
pub fn handle_created(&mut self, entity_id: EntityId) -> HandleId {
|
||||
let id = util::post_inc(&mut self.next_handle_id);
|
||||
let handle_id = HandleId { id };
|
||||
let handles = self.entity_handles.entry(entity_id).or_default();
|
||||
handles.insert(
|
||||
handle_id,
|
||||
LEAK_BACKTRACE.then(backtrace::Backtrace::new_unresolved),
|
||||
);
|
||||
handle_id
|
||||
}
|
||||
|
||||
pub fn handle_released(&mut self, entity_id: EntityId, handle_id: HandleId) {
|
||||
let handles = self.entity_handles.entry(entity_id).or_default();
|
||||
handles.remove(&handle_id);
|
||||
}
|
||||
|
||||
pub fn assert_released(&mut self, entity_id: EntityId) {
|
||||
let handles = self.entity_handles.entry(entity_id).or_default();
|
||||
if !handles.is_empty() {
|
||||
for backtrace in handles.values_mut() {
|
||||
if let Some(mut backtrace) = backtrace.take() {
|
||||
backtrace.resolve();
|
||||
eprintln!("Leaked handle: {:#?}", backtrace);
|
||||
} else {
|
||||
eprintln!("Leaked handle: export LEAK_BACKTRACE to find allocation site");
|
||||
}
|
||||
}
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::EntityMap;
|
||||
|
||||
struct TestEntity {
|
||||
pub i: i32,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_entity_map_slot_assignment_before_cleanup() {
|
||||
// Tests that slots are not re-used before take_dropped.
|
||||
let mut entity_map = EntityMap::new();
|
||||
|
||||
let slot = entity_map.reserve::<TestEntity>();
|
||||
entity_map.insert(slot, TestEntity { i: 1 });
|
||||
|
||||
let slot = entity_map.reserve::<TestEntity>();
|
||||
entity_map.insert(slot, TestEntity { i: 2 });
|
||||
|
||||
let dropped = entity_map.take_dropped();
|
||||
assert_eq!(dropped.len(), 2);
|
||||
|
||||
assert_eq!(
|
||||
dropped
|
||||
.into_iter()
|
||||
.map(|(_, entity)| entity.downcast::<TestEntity>().unwrap().i)
|
||||
.collect::<Vec<i32>>(),
|
||||
vec![1, 2],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_entity_map_weak_upgrade_before_cleanup() {
|
||||
// Tests that weak handles are not upgraded before take_dropped
|
||||
let mut entity_map = EntityMap::new();
|
||||
|
||||
let slot = entity_map.reserve::<TestEntity>();
|
||||
let handle = entity_map.insert(slot, TestEntity { i: 1 });
|
||||
let weak = handle.downgrade();
|
||||
drop(handle);
|
||||
|
||||
let strong = weak.upgrade();
|
||||
assert_eq!(strong, None);
|
||||
|
||||
let dropped = entity_map.take_dropped();
|
||||
assert_eq!(dropped.len(), 1);
|
||||
|
||||
assert_eq!(
|
||||
dropped
|
||||
.into_iter()
|
||||
.map(|(_, entity)| entity.downcast::<TestEntity>().unwrap().i)
|
||||
.collect::<Vec<i32>>(),
|
||||
vec![1],
|
||||
);
|
||||
}
|
||||
}
|
||||
+1047
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user