use crate::{ current_platform, AnyWindowHandle, Context, LayoutId, MainThreadOnly, Platform, Reference, RootView, TextSystem, Window, WindowContext, WindowHandle, WindowId, }; use anyhow::{anyhow, Result}; use collections::{HashMap, VecDeque}; use futures::{future, Future}; use parking_lot::Mutex; use slotmap::SlotMap; use smallvec::SmallVec; use std::{ any::Any, marker::PhantomData, sync::{Arc, Weak}, }; use util::ResultExt; #[derive(Clone)] pub struct App(Arc>); impl App { pub fn production() -> Self { Self::new(current_platform()) } #[cfg(any(test, feature = "test"))] pub fn test() -> Self { Self::new(Arc::new(super::TestPlatform::new())) } fn new(platform: Arc) -> Self { let dispatcher = platform.dispatcher(); let text_system = Arc::new(TextSystem::new(platform.text_system())); let mut entities = SlotMap::with_key(); let unit_entity = Handle::new(entities.insert(Some(Box::new(()) as Box))); Self(Arc::new_cyclic(|this| { Mutex::new(AppContext { this: this.clone(), platform: MainThreadOnly::new(platform, dispatcher), text_system, unit_entity, entities, windows: SlotMap::with_key(), pending_updates: 0, pending_effects: Default::default(), observers: Default::default(), layout_id_buffer: Default::default(), }) })) } pub fn run(self, on_finish_launching: F) where F: 'static + FnOnce(&mut AppContext), { let this = self.clone(); let platform = self.0.lock().platform.clone(); platform.borrow_on_main_thread().run(Box::new(move || { let cx = &mut *this.0.lock(); on_finish_launching(cx); })); } } type Handlers = SmallVec<[Arc bool + Send + Sync + 'static>; 2]>; pub struct AppContext { this: Weak>, platform: MainThreadOnly, text_system: Arc, pub(crate) unit_entity: Handle<()>, pub(crate) entities: SlotMap>>, pub(crate) windows: SlotMap>, pending_updates: usize, pub(crate) pending_effects: VecDeque, pub(crate) observers: HashMap, // We recycle this memory across layout requests. pub(crate) layout_id_buffer: Vec, } impl AppContext { pub fn text_system(&self) -> &Arc { &self.text_system } pub fn to_async(&self) -> AsyncContext { AsyncContext(self.this.clone()) } pub fn spawn_on_main( &self, f: impl FnOnce(&dyn Platform, &mut Self) -> F + Send + 'static, ) -> impl Future where F: Future + 'static, R: Send + 'static, { let this = self.this.upgrade().unwrap(); self.platform.read(move |platform| { let cx = &mut *this.lock(); cx.update(|cx| f(platform, cx)) }) } pub fn open_window( &mut self, options: crate::WindowOptions, build_root_view: impl FnOnce(&mut WindowContext) -> RootView + Send + 'static, ) -> impl Future> { let id = self.windows.insert(None); let handle = WindowHandle::new(id); self.spawn_on_main(move |platform, cx| { let mut window = Window::new(handle.into(), options, platform, cx); let root_view = build_root_view(&mut WindowContext::mutable(cx, &mut window)); window.root_view.replace(root_view.into_any()); cx.windows.get_mut(id).unwrap().replace(window); future::ready(handle) }) } pub(crate) fn update_window( &mut self, id: WindowId, update: impl FnOnce(&mut WindowContext) -> R, ) -> Result { self.update(|cx| { let mut window = cx .windows .get_mut(id) .ok_or_else(|| anyhow!("window not found"))? .take() .unwrap(); let result = update(&mut WindowContext::mutable(cx, &mut window)); window.dirty = true; cx.windows .get_mut(id) .ok_or_else(|| anyhow!("window not found"))? .replace(window); Ok(result) }) } fn update(&mut self, update: impl FnOnce(&mut Self) -> R) -> R { self.pending_updates += 1; let result = update(self); self.pending_updates -= 1; if self.pending_updates == 0 { self.flush_effects(); } result } fn flush_effects(&mut self) { while let Some(effect) = self.pending_effects.pop_front() { match effect { Effect::Notify(entity_id) => self.apply_notify_effect(entity_id), } } let dirty_window_ids = self .windows .iter() .filter_map(|(window_id, window)| { let window = window.as_ref().unwrap(); if window.dirty { Some(window_id) } else { None } }) .collect::>(); for dirty_window_id in dirty_window_ids { self.update_window(dirty_window_id, |cx| cx.draw()) .unwrap() // We know we have the window. .log_err(); } } fn apply_notify_effect(&mut self, updated_entity: EntityId) { if let Some(mut handlers) = self.observers.remove(&updated_entity) { handlers.retain(|handler| handler(self)); if let Some(new_handlers) = self.observers.remove(&updated_entity) { handlers.extend(new_handlers); } self.observers.insert(updated_entity, handlers); } } } impl Context for AppContext { type EntityContext<'a, 'w, T: Send + Sync + 'static> = ModelContext<'a, T>; type Result = T; fn entity( &mut self, build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T, ) -> Handle { let id = self.entities.insert(None); let entity = Box::new(build_entity(&mut ModelContext::mutable(self, id))); self.entities.get_mut(id).unwrap().replace(entity); Handle::new(id) } fn update_entity( &mut self, handle: &Handle, update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R, ) -> R { let mut entity = self .entities .get_mut(handle.id) .unwrap() .take() .unwrap() .downcast::() .unwrap(); let result = update(&mut *entity, &mut ModelContext::mutable(self, handle.id)); self.entities.get_mut(handle.id).unwrap().replace(entity); result } } #[derive(Clone)] pub struct AsyncContext(Weak>); impl Context for AsyncContext { type EntityContext<'a, 'b, T: Send + Sync + 'static> = ModelContext<'a, T>; type Result = Result; fn entity( &mut self, build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T, ) -> Result> { let app = self .0 .upgrade() .ok_or_else(|| anyhow!("app was released"))?; let mut lock = app.lock(); Ok(lock.entity(build_entity)) } fn update_entity( &mut self, handle: &Handle, update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R, ) -> Result { let app = self .0 .upgrade() .ok_or_else(|| anyhow!("app was released"))?; let mut lock = app.lock(); Ok(lock.update_entity(handle, update)) } } impl AsyncContext { pub fn update_window( &self, handle: AnyWindowHandle, update: impl FnOnce(&mut WindowContext) -> T + Send + Sync, ) -> Result { let app = self .0 .upgrade() .ok_or_else(|| anyhow!("app was released"))?; let mut app_context = app.lock(); app_context.update_window(handle.id, update) } } pub struct ModelContext<'a, T> { app: Reference<'a, AppContext>, entity_type: PhantomData, entity_id: EntityId, } impl<'a, T: Send + Sync + 'static> ModelContext<'a, T> { pub(crate) fn mutable(app: &'a mut AppContext, entity_id: EntityId) -> Self { Self { app: Reference::Mutable(app), entity_type: PhantomData, entity_id, } } // todo! // fn update(&mut self, update: impl FnOnce(&mut T, &mut Self) -> R) -> R { // let mut entity = self // .app // .entities // .get_mut(self.entity_id) // .unwrap() // .take() // .unwrap(); // let result = update(entity.downcast_mut::().unwrap(), self); // self.app // .entities // .get_mut(self.entity_id) // .unwrap() // .replace(entity); // result // } pub fn handle(&self) -> WeakHandle { WeakHandle { id: self.entity_id, entity_type: PhantomData, } } pub fn observe( &mut self, handle: &Handle, on_notify: impl Fn(&mut T, Handle, &mut ModelContext<'_, T>) + Send + Sync + 'static, ) { let this = self.handle(); let handle = handle.downgrade(); self.app .observers .entry(handle.id) .or_default() .push(Arc::new(move |cx| { if let Some((this, handle)) = this.upgrade(cx).zip(handle.upgrade(cx)) { this.update(cx, |this, cx| on_notify(this, handle, cx)); true } else { false } })); } pub fn notify(&mut self) { self.app .pending_effects .push_back(Effect::Notify(self.entity_id)); } } impl<'a, T: 'static> Context for ModelContext<'a, T> { type EntityContext<'b, 'c, U: Send + Sync + 'static> = ModelContext<'b, U>; type Result = U; fn entity( &mut self, build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, U>) -> U, ) -> Handle { self.app.entity(build_entity) } fn update_entity( &mut self, handle: &Handle, update: impl FnOnce(&mut U, &mut Self::EntityContext<'_, '_, U>) -> R, ) -> R { self.app.update_entity(handle, update) } } slotmap::new_key_type! { pub struct EntityId; } pub struct Handle { pub(crate) id: EntityId, pub(crate) entity_type: PhantomData, } impl Handle { fn new(id: EntityId) -> Self { Self { id, entity_type: PhantomData, } } pub fn downgrade(&self) -> WeakHandle { WeakHandle { id: self.id, entity_type: self.entity_type, } } /// Update the entity referenced by this handle with the given function. /// /// The update function receives a context appropriate for its environment. /// When updating in an `AppContext`, it receives a `ModelContext`. /// When updating an a `WindowContext`, it receives a `ViewContext`. pub fn update( &self, cx: &mut C, update: impl FnOnce(&mut T, &mut C::EntityContext<'_, '_, T>) -> R, ) -> C::Result { cx.update_entity(self, update) } } impl Clone for Handle { fn clone(&self) -> Self { Self { id: self.id, entity_type: PhantomData, } } } pub struct WeakHandle { pub(crate) id: EntityId, pub(crate) entity_type: PhantomData, } impl WeakHandle { pub fn upgrade(&self, _: &impl Context) -> Option> { // todo!("Actually upgrade") Some(Handle { id: self.id, entity_type: self.entity_type, }) } /// Update the entity referenced by this handle with the given function if /// the referenced entity still exists. Returns an error if the entity has /// been released. /// /// The update function receives a context appropriate for its environment. /// When updating in an `AppContext`, it receives a `ModelContext`. /// When updating an a `WindowContext`, it receives a `ViewContext`. pub fn update( &self, cx: &mut C, update: impl FnOnce(&mut T, &mut C::EntityContext<'_, '_, T>) -> R, ) -> Result where Result>: crate::Flatten, { crate::Flatten::flatten( self.upgrade(cx) .ok_or_else(|| anyhow!("entity release")) .map(|this| cx.update_entity(&this, update)), ) } } pub(crate) enum Effect { Notify(EntityId), } #[cfg(test)] mod tests { use super::AppContext; #[test] fn test_app_context_send_sync() { // This will not compile if `AppContext` does not implement `Send` fn assert_send() {} assert_send::(); } }