1use std::{
4 any::{Any, TypeId},
5 collections::HashMap,
6 error::Error,
7 sync::Arc,
8};
9
10use parking_lot::RwLock;
11use tracing::{error, warn};
12use winit::window::Window;
13
14#[cfg(target_os = "android")]
15use winit::platform::android::activity::AndroidApp;
16
17pub type PluginResult = Result<(), Box<dyn Error + Send + Sync>>;
19
20type DesktopWakeHandler = Arc<dyn Fn() + Send + Sync>;
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub enum DesktopWindowAction {
25 Minimize,
27 Maximize,
29 ToggleMaximize,
31 Close,
33}
34
35impl DesktopWindowAction {
36 pub(crate) fn merge_pending(current: Option<Self>, new: Self) -> Self {
37 match (current, new) {
38 (Some(Self::Close), _) | (_, Self::Close) => Self::Close,
39 (_, new) => new,
40 }
41 }
42}
43
44#[derive(Clone)]
46pub struct DesktopPlatformContext {
47 window: Arc<Window>,
48 pending_action: Arc<RwLock<Option<DesktopWindowAction>>>,
49 wake_handler: DesktopWakeHandler,
50}
51
52impl DesktopPlatformContext {
53 pub fn window(&self) -> &Window {
55 &self.window
56 }
57
58 pub fn window_handle(&self) -> Arc<Window> {
60 self.window.clone()
61 }
62
63 pub fn minimize(&self) {
65 self.request_action(DesktopWindowAction::Minimize);
66 }
67
68 pub fn maximize(&self) {
70 self.request_action(DesktopWindowAction::Maximize);
71 }
72
73 pub fn toggle_maximize(&self) {
75 self.request_action(DesktopWindowAction::ToggleMaximize);
76 }
77
78 pub fn request_close(&self) {
80 self.request_action(DesktopWindowAction::Close);
81 }
82
83 fn request_action(&self, action: DesktopWindowAction) {
84 let mut pending_action = self.pending_action.write();
85 let next_action = DesktopWindowAction::merge_pending(*pending_action, action);
86 let changed = *pending_action != Some(next_action);
87 *pending_action = Some(next_action);
88 drop(pending_action);
89
90 if changed {
91 (self.wake_handler)();
92 }
93 }
94
95 pub(crate) fn new(
96 window: Arc<Window>,
97 pending_action: Arc<RwLock<Option<DesktopWindowAction>>>,
98 wake_handler: DesktopWakeHandler,
99 ) -> Self {
100 Self {
101 window,
102 pending_action,
103 wake_handler,
104 }
105 }
106}
107
108pub trait Plugin: Send + Sync + 'static {
110 fn name(&self) -> &'static str {
112 std::any::type_name::<Self>()
113 }
114
115 fn on_resumed(&mut self, _context: &PluginContext) -> PluginResult {
117 Ok(())
118 }
119
120 fn on_suspended(&mut self, _context: &PluginContext) -> PluginResult {
122 Ok(())
123 }
124
125 fn on_shutdown(&mut self, _context: &PluginContext) -> PluginResult {
127 Ok(())
128 }
129}
130
131trait PluginEntry: Send + Sync {
132 fn name(&self) -> &'static str;
133 fn resumed(&self, context: &PluginContext) -> PluginResult;
134 fn suspended(&self, context: &PluginContext) -> PluginResult;
135 fn shutdown(&self, context: &PluginContext) -> PluginResult;
136}
137
138struct PluginSlot<P: Plugin> {
139 inner: Arc<RwLock<P>>,
140}
141
142impl<P: Plugin> PluginSlot<P> {
143 fn new(inner: Arc<RwLock<P>>) -> Self {
144 Self { inner }
145 }
146}
147
148impl<P: Plugin> PluginEntry for PluginSlot<P> {
149 fn name(&self) -> &'static str {
150 self.inner.read().name()
151 }
152
153 fn resumed(&self, context: &PluginContext) -> PluginResult {
154 self.inner.write().on_resumed(context)
155 }
156
157 fn suspended(&self, context: &PluginContext) -> PluginResult {
158 self.inner.write().on_suspended(context)
159 }
160
161 fn shutdown(&self, context: &PluginContext) -> PluginResult {
162 self.inner.write().on_shutdown(context)
163 }
164}
165
166#[derive(Clone)]
168pub struct PluginContext {
169 desktop: DesktopPlatformContext,
170 #[cfg(target_os = "android")]
171 android_app: AndroidApp,
172}
173
174impl PluginContext {
175 pub fn desktop(&self) -> &DesktopPlatformContext {
177 &self.desktop
178 }
179
180 pub fn window(&self) -> &Window {
182 self.desktop.window()
183 }
184
185 pub fn window_handle(&self) -> Arc<Window> {
187 self.desktop.window_handle()
188 }
189
190 #[cfg(target_os = "ios")]
192 pub fn ui_view(&self) -> Option<objc2::rc::Retained<objc2_ui_kit::UIView>> {
193 use winit::raw_window_handle::{HasWindowHandle, RawWindowHandle};
194
195 objc2::MainThreadMarker::new()?;
196 let RawWindowHandle::UiKit(handle) = self.window().window_handle().ok()?.as_raw() else {
197 return None;
198 };
199 unsafe { objc2::rc::Retained::retain(handle.ui_view.as_ptr().cast()) }
201 }
202
203 #[cfg(target_os = "android")]
205 pub fn android_app(&self) -> &AndroidApp {
206 &self.android_app
207 }
208
209 #[cfg(target_os = "android")]
210 pub(crate) fn new(desktop: DesktopPlatformContext, android_app: AndroidApp) -> Self {
211 Self {
212 desktop,
213 android_app,
214 }
215 }
216
217 #[cfg(not(target_os = "android"))]
218 pub(crate) fn new(desktop: DesktopPlatformContext) -> Self {
219 Self { desktop }
220 }
221}
222
223pub fn register_plugin<P: Plugin>(plugin: P) {
225 register_plugin_arc(Arc::new(RwLock::new(plugin)));
226}
227
228pub fn register_plugin_boxed<P: Plugin>(plugin: Arc<RwLock<P>>) {
230 register_plugin_arc(plugin);
231}
232
233pub fn with_plugin<T, R>(f: impl FnOnce(&T) -> R) -> R
239where
240 T: Plugin + 'static,
241{
242 let plugin = plugin_instance::<T>();
243 let guard = plugin.read();
244 f(&*guard)
245}
246
247pub fn with_plugin_mut<T, R>(f: impl FnOnce(&mut T) -> R) -> R
253where
254 T: Plugin + 'static,
255{
256 let plugin = plugin_instance::<T>();
257 let mut guard = plugin.write();
258 f(&mut *guard)
259}
260
261pub(crate) struct PluginHost {
262 plugins: Vec<Arc<dyn PluginEntry>>,
263 shutdown_called: bool,
264}
265
266impl PluginHost {
267 pub(crate) fn new() -> Self {
268 Self {
269 plugins: registered_plugins(),
270 shutdown_called: false,
271 }
272 }
273
274 pub(crate) fn resumed(&self, context: &PluginContext) {
275 self.dispatch("resumed", context, |plugin, ctx| plugin.resumed(ctx));
276 }
277
278 pub(crate) fn suspended(&self, context: &PluginContext) {
279 self.dispatch("suspended", context, |plugin, ctx| plugin.suspended(ctx));
280 }
281
282 pub(crate) fn shutdown(&mut self, context: &PluginContext) {
283 if self.shutdown_called {
284 return;
285 }
286 self.shutdown_called = true;
287 self.dispatch("shutdown", context, |plugin, ctx| plugin.shutdown(ctx));
288 }
289
290 fn dispatch<F>(&self, stage: &'static str, context: &PluginContext, mut handler: F)
291 where
292 F: FnMut(&dyn PluginEntry, &PluginContext) -> PluginResult,
293 {
294 for plugin in &self.plugins {
295 if let Err(err) = handler(plugin.as_ref(), context) {
296 error!("Plugin '{}' {} hook failed: {}", plugin.name(), stage, err);
297 }
298 }
299 }
300}
301
302fn plugin_registry() -> Arc<RwLock<Vec<Arc<dyn PluginEntry>>>> {
303 crate::runtime_resource::<RwLock<Vec<Arc<dyn PluginEntry>>>>()
304}
305
306fn registered_plugins() -> Vec<Arc<dyn PluginEntry>> {
307 plugin_registry().read().clone()
308}
309
310fn register_plugin_arc<P: Plugin>(plugin: Arc<RwLock<P>>) {
311 assert!(
312 crate::RuntimeSession::current().is_some(),
313 "register plugins through an EntryPoint or RuntimeSession"
314 );
315 let plugin_entry = Arc::new(PluginSlot::new(plugin.clone())) as Arc<dyn PluginEntry>;
316 let instance_registry = plugin_instance_registry();
317 let mut instances = instance_registry.write();
318 let type_id = TypeId::of::<P>();
319 if instances.contains_key(&type_id) {
320 warn!(
321 "Plugin '{}' was registered more than once; keeping the first instance",
322 std::any::type_name::<P>()
323 );
324 return;
325 }
326 instances.insert(type_id, plugin as Arc<dyn Any + Send + Sync>);
327 drop(instances);
328
329 let plugins = plugin_registry();
330 let mut registry = plugins.write();
331 registry.push(plugin_entry);
332}
333
334fn plugin_instance_registry() -> Arc<RwLock<HashMap<TypeId, Arc<dyn Any + Send + Sync>>>> {
335 crate::runtime_resource::<RwLock<HashMap<TypeId, Arc<dyn Any + Send + Sync>>>>()
336}
337
338pub fn try_with_plugin<T: Plugin, R>(read: impl FnOnce(&T) -> R) -> Option<R> {
340 let _session = crate::RuntimeSession::current()?;
341 let registry = plugin_instance_registry();
342 let plugin = registry.read().get(&TypeId::of::<T>()).cloned()?;
343 let plugin = Arc::downcast::<RwLock<T>>(plugin).ok()?;
344 Some(read(&plugin.read()))
345}
346
347pub fn try_with_plugin_mut<T: Plugin, R>(update: impl FnOnce(&mut T) -> R) -> Option<R> {
349 let _session = crate::RuntimeSession::current()?;
350 let registry = plugin_instance_registry();
351 let plugin = registry.read().get(&TypeId::of::<T>()).cloned()?;
352 let plugin = Arc::downcast::<RwLock<T>>(plugin).ok()?;
353 Some(update(&mut plugin.write()))
354}
355
356fn plugin_instance<T: Plugin>() -> Arc<RwLock<T>> {
357 let instances = plugin_instance_registry();
358 let registry = instances.read();
359 let type_id = TypeId::of::<T>();
360 let Some(plugin) = registry.get(&type_id) else {
361 panic!("Plugin '{}' is not registered", std::any::type_name::<T>());
362 };
363 let plugin = plugin.clone();
364 drop(registry);
365 match Arc::downcast::<RwLock<T>>(plugin) {
366 Ok(plugin) => plugin,
367 Err(_) => panic!(
368 "Plugin '{}' has a mismatched type",
369 std::any::type_name::<T>()
370 ),
371 }
372}