Skip to main content

lgui_core/application/
context.rs

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    /// Registers an application-owned cache adapter and retains it for this context's lifetime.
123    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    /// Runs a Future with this Application as the active context on every poll.
219    ///
220    /// LGUI task APIs apply this automatically. Use `scope` when an
221    /// application-owned Future is submitted to an external executor.
222    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}