1use std::{
2 future::Future,
3 sync::{Arc, Mutex, RwLock},
4};
5
6use crate::{
7 command::{Command, CommandHandle, CommandRegistry},
8 core::UiTaskSpawner,
9 events::{Event, EventBus, EventSubscription},
10 memory::{CacheRegistration, DomainRegistration, MemoryGovernor, MemoryOptions},
11 resources::Resources,
12};
13
14#[cfg(feature = "persistent-cache")]
15use crate::memory::PersistentCacheStore;
16
17use super::{ApplicationScopeFuture, RenderError, RenderErrorRegistration, WindowManager};
18
19#[derive(Clone)]
20pub struct ApplicationContext {
21 inner: Arc<ApplicationContextInner>,
22}
23
24struct ApplicationContextInner {
25 resources: Resources,
26 executor: RwLock<Option<UiTaskSpawner>>,
27 commands: CommandRegistry,
28 events: EventBus,
29 memory: MemoryGovernor,
30 memory_registrations: Mutex<Vec<CacheRegistration>>,
31 windows: WindowManager,
32}
33
34impl ApplicationContext {
35 pub fn empty(memory_options: MemoryOptions) -> Self {
36 Self::new(
37 Resources::new(),
38 None,
39 CommandRegistry::default(),
40 EventBus::default(),
41 memory_options,
42 )
43 }
44
45 pub(super) fn new(
46 resources: Resources,
47 executor: Option<UiTaskSpawner>,
48 commands: CommandRegistry,
49 events: EventBus,
50 memory_options: MemoryOptions,
51 ) -> Self {
52 Self::new_with_memory(
53 resources,
54 executor,
55 commands,
56 events,
57 memory_options,
58 #[cfg(feature = "persistent-cache")]
59 None,
60 )
61 }
62
63 pub(super) fn new_with_memory(
64 resources: Resources,
65 executor: Option<UiTaskSpawner>,
66 commands: CommandRegistry,
67 events: EventBus,
68 memory_options: MemoryOptions,
69 #[cfg(feature = "persistent-cache")] persistent_cache: Option<
70 Arc<dyn PersistentCacheStore>,
71 >,
72 ) -> Self {
73 let memory = MemoryGovernor::with_store(
74 memory_options,
75 #[cfg(feature = "persistent-cache")]
76 persistent_cache,
77 );
78 let context = Self {
79 inner: Arc::new(ApplicationContextInner {
80 resources,
81 executor: RwLock::new(executor),
82 commands,
83 events,
84 memory,
85 memory_registrations: Mutex::new(Vec::new()),
86 windows: WindowManager::new(),
87 }),
88 };
89 let registration = context
90 .memory()
91 .register(crate::memory::DomainRegistration::new(
92 crate::memory::CacheDomain::ScrollRaster,
93 context.memory().next_instance_id(),
94 "application:scroll-raster-commands",
95 crate::memory::CacheAdapter::managed(
96 crate::core::scroll_raster_command_cache_usage,
97 |request| {
98 let before =
99 crate::core::scroll_raster_command_cache_usage().resident_bytes();
100 crate::core::trim_scroll_raster_command_cache(request.target_bytes);
101 crate::memory::TrimResult {
102 before_bytes: before,
103 after_bytes: crate::core::scroll_raster_command_cache_usage()
104 .resident_bytes(),
105 }
106 },
107 crate::core::set_scroll_raster_command_cache_budget,
108 ),
109 ));
110 context.retain_memory_registration(registration);
111 context
112 }
113
114 pub fn resources(&self) -> &Resources {
115 &self.inner.resources
116 }
117
118 pub fn memory(&self) -> &MemoryGovernor {
119 &self.inner.memory
120 }
121
122 pub fn register_memory_domain(&self, registration: DomainRegistration) {
124 let registration = self.memory().register(registration);
125 self.retain_memory_registration(registration);
126 }
127
128 pub(crate) fn retain_memory_registration(&self, registration: CacheRegistration) {
129 self.inner
130 .memory_registrations
131 .lock()
132 .expect("memory registrations poisoned")
133 .push(registration);
134 }
135
136 pub fn resource<T>(&self) -> Arc<T>
137 where
138 T: Send + Sync + 'static,
139 {
140 self.inner.resources.require::<T>()
141 }
142
143 pub fn try_resource<T>(&self) -> Option<Arc<T>>
144 where
145 T: Send + Sync + 'static,
146 {
147 self.inner.resources.get::<T>()
148 }
149
150 pub fn command<C>(&self) -> CommandHandle<C>
151 where
152 C: Command,
153 {
154 CommandHandle::new(self.clone())
155 }
156
157 pub async fn invoke<C>(&self, args: C::Args) -> Result<C::Output, C::Error>
158 where
159 C: Command,
160 {
161 self.scope(self.command::<C>().invoke(args)).await
162 }
163
164 pub fn emit<E>(&self, event: E) -> usize
165 where
166 E: Event,
167 {
168 self.emit_keyed(&crate::events::EventKey::new(E::NAME), event)
169 }
170
171 pub fn subscribe<E>(&self, listener: impl Fn(E) + Send + Sync + 'static) -> EventSubscription
172 where
173 E: Event,
174 {
175 self.subscribe_keyed(crate::events::EventKey::new(E::NAME), listener)
176 }
177
178 pub fn subscribe_keyed<T>(
179 &self,
180 key: crate::events::EventKey<T>,
181 listener: impl Fn(T) + Send + Sync + 'static,
182 ) -> EventSubscription
183 where
184 T: Clone + Send + Sync + 'static,
185 {
186 self.inner.events.subscribe(key, listener)
187 }
188
189 pub(crate) fn emit_keyed<T>(&self, key: &crate::events::EventKey<T>, payload: T) -> usize
190 where
191 T: Clone + Send + Sync + 'static,
192 {
193 self.inner.events.emit(key, payload)
194 }
195
196 pub(crate) fn command_registry(&self) -> &CommandRegistry {
197 &self.inner.commands
198 }
199
200 pub fn set_executor(&self, executor: UiTaskSpawner) {
201 *self.inner.executor.write().expect("UI executor poisoned") = Some(executor);
202 }
203
204 pub fn spawn(&self, task: impl Future<Output = ()> + Send + 'static) -> bool {
205 let Some(executor) = self
206 .inner
207 .executor
208 .read()
209 .expect("UI executor poisoned")
210 .clone()
211 else {
212 return false;
213 };
214 executor.spawn(Box::pin(self.scope(task)));
215 true
216 }
217
218 pub fn scope<F>(&self, future: F) -> impl Future<Output = F::Output>
223 where
224 F: Future,
225 {
226 ApplicationScopeFuture::new(self.clone(), future)
227 }
228
229 pub fn windows(&self) -> WindowManager {
230 self.inner.windows.clone()
231 }
232
233 pub(crate) fn report_render_error(&self, error: RenderError) {
234 if let Some(registration) = self.try_resource::<RenderErrorRegistration>() {
235 registration.report(&error);
236 } else {
237 eprintln!("{error}");
238 }
239 }
240
241 pub(crate) fn task_spawner(&self) -> Option<UiTaskSpawner> {
242 let executor = self
243 .inner
244 .executor
245 .read()
246 .expect("UI executor poisoned")
247 .clone()?;
248 let application = Arc::downgrade(&self.inner);
249 Some(Arc::new(move |task: crate::core::UiTask| {
250 let Some(inner) = application.upgrade() else {
251 return;
252 };
253 let application = ApplicationContext { inner };
254 executor.spawn(Box::pin(application.scope(task)));
255 }))
256 }
257}
258
259impl PartialEq for ApplicationContext {
260 fn eq(&self, other: &Self) -> bool {
261 Arc::ptr_eq(&self.inner, &other.inner)
262 }
263}