use std::any::type_name;
use std::collections::{HashMap, VecDeque};
use std::fmt::{self, Debug};
use std::marker::PhantomData;
use std::ops::Receiver;
use std::rc::{Rc, Weak};
use std::time::Duration;
use inset_embedder::{DropData, PlatformRef, RequestId, ViewError, ViewFocusEvent, ViewId};
use slotmap::new_key_type;
use crate::app_cell::{AppCell, AsyncApp};
use crate::change_notifier::Listener;
use crate::entity::EntityMap;
use crate::executor::{ExecutorHandle, ForegroundExecutor, Task};
use crate::handle_map::HandleMap;
pub use crate::handle_map::{RetainedHandle, RetainedHandleId};
use crate::timers::{Timer, Timers};
const MICROTASK_BUDGET: usize = 100_000;
new_key_type! {
pub struct HandleId;
}
pub struct Handle<T> {
id: HandleId,
state: PhantomData<fn() -> T>,
}
impl<T> Handle<T> {
pub(crate) fn new(id: HandleId) -> Handle<T> {
Handle {
id,
state: PhantomData,
}
}
pub fn from_id(id: HandleId) -> Handle<T> {
Handle::new(id)
}
pub fn id(self) -> HandleId {
self.id
}
}
impl<T> Clone for Handle<T> {
fn clone(&self) -> Handle<T> {
*self
}
}
impl<T> Copy for Handle<T> {}
impl<T> Receiver for Handle<T> {
type Target = T;
}
impl<T> PartialEq for Handle<T> {
fn eq(&self, other: &Handle<T>) -> bool {
self.id == other.id
}
}
impl<T> Eq for Handle<T> {}
impl<T> std::hash::Hash for Handle<T> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state)
}
}
impl<T> Debug for Handle<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Handle<{}>({:?})", type_name::<T>(), self.id)
}
}
impl<T> From<Handle<T>> for HandleId {
fn from(handle: Handle<T>) -> HandleId {
handle.id
}
}
#[non_exhaustive]
pub struct App {
this: Weak<AppCell>,
handles: HandleMap,
pub(crate) entities: EntityMap,
singletons: HashMap<std::any::TypeId, HandleId>,
microtasks: VecDeque<Listener>,
timers: Timers,
executor: ForegroundExecutor,
platform: PlatformRef,
dispatcher: std::sync::Arc<dyn inset_embedder::Dispatcher>,
platform_callbacks: PlatformCallbacks,
}
#[derive(Default)]
pub struct PlatformCallbacks {
pub on_platform_brightness_changed: Option<Listener>,
pub on_locale_changed: Option<Listener>,
pub on_view_focus_change: Option<ViewFocusChangeCallback>,
pub on_drop: Option<DropCallback>,
pub on_pop_route: Option<PopRouteCallback>,
pub on_view_added: Option<ViewChangeCallback>,
pub on_view_removed: Option<ViewChangeCallback>,
pub on_view_close_requested: Option<ViewCloseRequestedCallback>,
pub on_view_request_failed: Option<ViewRequestFailedCallback>,
}
pub type ViewFocusChangeCallback = Rc<dyn Fn(&mut App, ViewFocusEvent)>;
pub type DropCallback = Rc<dyn Fn(&mut App, DropData)>;
pub type PopRouteCallback = Rc<dyn Fn(&mut App) -> bool>;
pub type ViewChangeCallback = Rc<dyn Fn(&mut App, ViewId)>;
pub type ViewCloseRequestedCallback = Rc<dyn Fn(&mut App, ViewId)>;
pub type ViewRequestFailedCallback = Rc<dyn Fn(&mut App, RequestId, ViewError)>;
impl App {
pub(crate) fn build(
this: Weak<AppCell>,
platform: PlatformRef,
executor: ForegroundExecutor,
) -> App {
App {
this,
handles: HandleMap::new(),
entities: EntityMap::new(),
singletons: HashMap::new(),
microtasks: VecDeque::new(),
timers: Timers::default(),
executor,
dispatcher: platform.dispatcher(),
platform,
platform_callbacks: PlatformCallbacks::default(),
}
}
pub(crate) fn executor_handle(&self) -> ExecutorHandle {
self.executor.handle()
}
pub fn to_async(&self) -> AsyncApp {
AsyncApp::new(self.this.clone(), self.executor.handle())
}
pub fn spawn<R: 'static>(&self, f: impl AsyncFnOnce(&mut AsyncApp) -> R + 'static) -> Task<R> {
let mut cx = self.to_async();
self.executor.spawn(async move { f(&mut cx).await })
}
pub fn run_in_background<T: Send + 'static>(
&self,
work: impl FnOnce() -> T + Send + 'static,
) -> Task<T> {
self.executor.run_in_background(work)
}
pub fn platform(&self) -> PlatformRef {
Rc::clone(&self.platform)
}
pub fn platform_callbacks(&self) -> &PlatformCallbacks {
&self.platform_callbacks
}
pub fn platform_callbacks_mut(&mut self) -> &mut PlatformCallbacks {
&mut self.platform_callbacks
}
pub fn singleton<T: 'static + Default>(&mut self) -> Handle<T> {
if let Some(&id) = self.singletons.get(&std::any::TypeId::of::<T>()) {
return Handle::new(id);
}
let handle = self.create(T::default());
self.singletons
.insert(std::any::TypeId::of::<T>(), handle.id());
handle
}
pub fn schedule_microtask(&mut self, callback: Listener) {
self.microtasks.push_back(callback);
}
pub fn drain_microtasks(&mut self) {
let mut drained = 0usize;
while let Some(callback) = self.microtasks.pop_front() {
drained += 1;
assert!(
drained <= MICROTASK_BUDGET,
"microtasks did not converge after {MICROTASK_BUDGET} callbacks; \
are two callbacks scheduling each other in a cycle?"
);
callback.call(self);
}
}
pub fn has_pending_microtasks(&self) -> bool {
!self.microtasks.is_empty()
}
pub fn schedule_timer(&mut self, duration: Duration, callback: Listener) -> Timer {
let timer = self.timers.schedule(duration, callback);
self.update_timer_wakeup();
timer
}
pub fn cancel_timer(&mut self, timer: Timer) {
self.timers.cancel(timer);
self.update_timer_wakeup();
}
pub fn timer_is_active(&self, timer: Timer) -> bool {
self.timers.is_active(timer)
}
pub(crate) fn clock(&self) -> Duration {
self.timers.now()
}
pub(crate) fn fire_next_due(&mut self, target: Duration) -> bool {
let Some(callback) = self.timers.pop_next_due(target) else {
return false;
};
callback.call(self);
true
}
pub(crate) fn advance_clock_to(&mut self, target: Duration) {
self.timers.advance_to(target);
}
pub(crate) fn update_timer_wakeup(&mut self) {
if let Some(delay) = self.timers.timer_wakeup_if_changed() {
let wakeup = delay.map(|delay| self.platform.now() + delay);
self.dispatcher.wake_at(wakeup);
}
}
pub fn create<T: 'static>(&mut self, state: T) -> Handle<T> {
self.handles.create(state)
}
pub fn destroy(&mut self, handle: impl Into<HandleId>) {
self.handles.destroy(handle.into());
}
pub fn contains(&self, handle: impl Into<HandleId>) -> bool {
self.handles.contains(handle.into())
}
pub fn retain<T: Copy + Into<HandleId>>(&mut self, handle: T) -> RetainedHandle<T> {
self.handles.retain(handle)
}
pub fn retain_id(&mut self, id: HandleId) -> RetainedHandleId {
self.handles.retain_id(id)
}
pub fn release<T>(&mut self, handle: RetainedHandle<T>) {
self.handles.release(handle);
}
pub fn release_id(&mut self, handle: RetainedHandleId) {
self.handles.release_id(handle);
}
pub(crate) fn release_dropped_retained_handles(&mut self) {
self.handles.release_dropped_retained_handles();
}
pub fn is_disposed(&self, handle: impl Into<HandleId>) -> bool {
self.handles.is_disposed(handle.into())
}
pub fn handle<T: 'static>(&self, handle: impl Into<HandleId>) -> Option<Handle<T>> {
self.handles.handle(handle.into())
}
pub fn get<T: 'static>(&self, handle: Handle<T>) -> &T {
self.handles.get(handle)
}
pub fn get_mut<T: 'static>(&mut self, handle: Handle<T>) -> &mut T {
self.handles.get_mut(handle)
}
pub fn get_disjoint_mut<A: 'static, B: 'static>(
&mut self,
a: Handle<A>,
b: Handle<B>,
) -> (&mut A, &mut B) {
self.handles.get_disjoint_mut(a, b)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Timer;
#[derive(Debug, Default, PartialEq)]
struct Counter(i32);
#[derive(Debug, PartialEq)]
struct Label(&'static str);
#[test]
fn state_survives_a_round_trip_through_a_handle() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let counter = app.create(Counter(1));
assert_eq!(app.get(counter), &Counter(1));
app.get_mut(counter).0 = 7;
assert_eq!(app.get(counter), &Counter(7));
}
#[test]
fn handles_of_different_types_share_one_arena() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let counter = app.create(Counter(1));
let label = app.create(Label("hello"));
assert_eq!(app.get(counter), &Counter(1));
assert_eq!(app.get(label), &Label("hello"));
}
#[test]
fn a_destroyed_entry_is_reused_but_its_handle_is_not() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let first = app.create(Counter(1));
let first_id = first.id();
app.destroy(first);
let second = app.create(Counter(2));
let entry_index = |id: HandleId| slotmap::Key::data(&id).as_ffi() & 0xffff_ffff;
assert_eq!(
entry_index(second.id()),
entry_index(first_id),
"the freed entry is reused, so the version is what tells them apart"
);
assert_ne!(second.id(), first_id);
assert!(!app.contains(first_id));
assert!(app.contains(second));
}
#[test]
#[should_panic(expected = "stale handle")]
fn reading_through_a_stale_handle_panics() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let counter = app.create(Counter(1));
app.destroy(counter);
app.get(counter);
}
#[test]
fn the_checked_path_from_an_untyped_id_answers_none_rather_than_panicking() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let counter = app.create(Counter(1));
let id = counter.id();
assert_eq!(app.handle::<Counter>(id), Some(counter));
assert_eq!(app.handle::<Label>(id), None, "wrong type");
app.destroy(counter);
assert_eq!(app.handle::<Counter>(id), None, "stale");
}
#[test]
fn a_singleton_is_one_handle_per_type_per_app() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let first: Handle<Counter> = app.singleton();
let second: Handle<Counter> = app.singleton();
assert_eq!(first, second);
app.get_mut(first).0 = 9;
assert_eq!(app.get(second).0, 9, "one entry behind both handles");
let other_cell = AppCell::new();
let mut other_app = other_cell.borrow_mut();
let elsewhere: Handle<Counter> = other_app.singleton();
assert_eq!(elsewhere.id(), first.id(), "ids may collide across Apps");
assert_eq!(other_app.get(elsewhere).0, 0, "but the state is per App");
}
#[test]
fn a_handle_is_copy_so_an_edge_can_be_a_plain_field() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let counter = app.create(Counter(1));
struct Edge {
target: Handle<Counter>,
}
let edge = Edge { target: counter };
let copied = edge.target;
assert_eq!(copied, counter);
assert_eq!(app.get(edge.target).0, 1);
}
#[test]
fn a_microtask_runs_on_drain_not_inline() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let counter = app.create(Counter(0));
app.schedule_microtask(crate::Listener::new(move |app| {
app.get_mut(counter).0 = 1;
}));
assert_eq!(app.get(counter).0, 0);
assert!(app.has_pending_microtasks());
app.drain_microtasks();
assert_eq!(app.get(counter).0, 1);
assert!(!app.has_pending_microtasks());
}
#[test]
fn a_microtask_scheduled_during_drain_runs_in_the_same_drain() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let counter = app.create(Counter(0));
app.schedule_microtask(crate::Listener::new(move |app| {
app.get_mut(counter).0 = 1;
app.schedule_microtask(crate::Listener::new(move |app| {
app.get_mut(counter).0 = 2;
}));
}));
app.drain_microtasks();
assert_eq!(app.get(counter).0, 2);
}
#[test]
fn timers_fire_in_due_order_after_microtasks() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let log = app.create(Vec::<&'static str>::new());
app.schedule_microtask(crate::Listener::new(move |app| {
app.get_mut(log).push("micro");
}));
Timer::new(
&mut app,
Duration::from_millis(200),
crate::Listener::new(move |app| {
app.get_mut(log).push("late");
}),
);
Timer::new(
&mut app,
Duration::from_millis(100),
crate::Listener::new(move |app| {
app.get_mut(log).push("early");
}),
);
Timer::new(
&mut app,
Duration::from_millis(100),
crate::Listener::new(move |app| {
app.get_mut(log).push("early-second");
}),
);
assert!(app.get(log).is_empty());
drop(app);
cell.elapse(Duration::from_millis(200));
let app = cell.borrow();
assert_eq!(
app.get(log).as_slice(),
["micro", "early", "early-second", "late"]
);
}
#[test]
fn cancel_prevents_a_timer_from_firing() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let counter = app.create(Counter(0));
let timer = Timer::new(
&mut app,
Duration::from_millis(10),
crate::Listener::new(move |app| {
app.get_mut(counter).0 = 1;
}),
);
assert!(timer.is_active(&app));
timer.cancel(&mut app);
assert!(!timer.is_active(&app));
drop(app);
cell.elapse(Duration::from_millis(10));
let app = cell.borrow();
assert_eq!(app.get(counter).0, 0);
}
#[test]
fn get_disjoint_mut_borrows_two_entries_at_once() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let a = app.create(1u32);
let b = app.create(String::from("x"));
let (a_value, b_value) = app.get_disjoint_mut(a, b);
*a_value += 1;
b_value.push('y');
assert_eq!(*app.get(a), 2);
assert_eq!(app.get(b), "xy");
}
#[test]
#[should_panic(expected = "not two live entries")]
fn get_disjoint_mut_rejects_the_same_entry_twice() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let a = app.create(1u32);
let _ = app.get_disjoint_mut(a, a);
}
#[test]
fn from_id_reads_like_the_minted_handle() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let minted = app.create(Counter(7));
let rebuilt = Handle::<Counter>::from_id(minted.id());
assert_eq!(rebuilt, minted);
assert_eq!(app.get(rebuilt).0, 7);
}
#[test]
#[should_panic(expected = "stale handle")]
fn from_id_defers_the_stale_check_to_get() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let minted = app.create(Counter(0));
let id = minted.id();
app.destroy(minted);
let rebuilt = Handle::<Counter>::from_id(id);
app.get(rebuilt);
}
}