1use std::{
4 any::{Any, TypeId},
5 collections::HashMap,
6 error::Error,
7 sync::{Arc, OnceLock},
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
20pub trait Plugin: Send + Sync + 'static {
22 fn name(&self) -> &'static str {
24 std::any::type_name::<Self>()
25 }
26
27 fn on_resumed(&mut self, _context: &PluginContext) -> PluginResult {
29 Ok(())
30 }
31
32 fn on_suspended(&mut self, _context: &PluginContext) -> PluginResult {
34 Ok(())
35 }
36
37 fn on_shutdown(&mut self, _context: &PluginContext) -> PluginResult {
39 Ok(())
40 }
41}
42
43trait PluginEntry: Send + Sync {
44 fn name(&self) -> &'static str;
45 fn resumed(&self, context: &PluginContext) -> PluginResult;
46 fn suspended(&self, context: &PluginContext) -> PluginResult;
47 fn shutdown(&self, context: &PluginContext) -> PluginResult;
48}
49
50struct PluginSlot<P: Plugin> {
51 inner: Arc<RwLock<P>>,
52}
53
54impl<P: Plugin> PluginSlot<P> {
55 fn new(inner: Arc<RwLock<P>>) -> Self {
56 Self { inner }
57 }
58}
59
60impl<P: Plugin> PluginEntry for PluginSlot<P> {
61 fn name(&self) -> &'static str {
62 self.inner.read().name()
63 }
64
65 fn resumed(&self, context: &PluginContext) -> PluginResult {
66 self.inner.write().on_resumed(context)
67 }
68
69 fn suspended(&self, context: &PluginContext) -> PluginResult {
70 self.inner.write().on_suspended(context)
71 }
72
73 fn shutdown(&self, context: &PluginContext) -> PluginResult {
74 self.inner.write().on_shutdown(context)
75 }
76}
77
78#[derive(Clone)]
80pub struct PluginContext {
81 window: Arc<Window>,
82 #[cfg(target_os = "android")]
83 android_app: AndroidApp,
84}
85
86impl PluginContext {
87 pub fn window(&self) -> &Window {
89 &self.window
90 }
91
92 pub fn window_handle(&self) -> Arc<Window> {
94 self.window.clone()
95 }
96
97 #[cfg(target_os = "android")]
99 pub fn android_app(&self) -> &AndroidApp {
100 &self.android_app
101 }
102
103 #[cfg(target_os = "android")]
104 pub(crate) fn new(window: Arc<Window>, android_app: AndroidApp) -> Self {
105 Self {
106 window,
107 android_app,
108 }
109 }
110
111 #[cfg(not(target_os = "android"))]
112 pub(crate) fn new(window: Arc<Window>) -> Self {
113 Self { window }
114 }
115}
116
117pub fn register_plugin<P: Plugin>(plugin: P) {
119 register_plugin_arc(Arc::new(RwLock::new(plugin)));
120}
121
122pub fn register_plugin_boxed<P: Plugin>(plugin: Arc<RwLock<P>>) {
124 register_plugin_arc(plugin);
125}
126
127pub fn with_plugin<T, R>(f: impl FnOnce(&T) -> R) -> R
133where
134 T: Plugin + 'static,
135{
136 let plugin = plugin_instance::<T>();
137 let guard = plugin.read();
138 f(&*guard)
139}
140
141pub fn with_plugin_mut<T, R>(f: impl FnOnce(&mut T) -> R) -> R
147where
148 T: Plugin + 'static,
149{
150 let plugin = plugin_instance::<T>();
151 let mut guard = plugin.write();
152 f(&mut *guard)
153}
154
155pub(crate) struct PluginHost {
156 plugins: Vec<Arc<dyn PluginEntry>>,
157 shutdown_called: bool,
158}
159
160impl PluginHost {
161 pub(crate) fn new() -> Self {
162 Self {
163 plugins: registered_plugins(),
164 shutdown_called: false,
165 }
166 }
167
168 pub(crate) fn resumed(&self, context: &PluginContext) {
169 self.dispatch("resumed", context, |plugin, ctx| plugin.resumed(ctx));
170 }
171
172 pub(crate) fn suspended(&self, context: &PluginContext) {
173 self.dispatch("suspended", context, |plugin, ctx| plugin.suspended(ctx));
174 }
175
176 pub(crate) fn shutdown(&mut self, context: &PluginContext) {
177 if self.shutdown_called {
178 return;
179 }
180 self.shutdown_called = true;
181 self.dispatch("shutdown", context, |plugin, ctx| plugin.shutdown(ctx));
182 }
183
184 fn dispatch<F>(&self, stage: &'static str, context: &PluginContext, mut handler: F)
185 where
186 F: FnMut(&dyn PluginEntry, &PluginContext) -> PluginResult,
187 {
188 for plugin in &self.plugins {
189 if let Err(err) = handler(plugin.as_ref(), context) {
190 error!("Plugin '{}' {} hook failed: {}", plugin.name(), stage, err);
191 }
192 }
193 }
194}
195
196fn plugin_registry() -> &'static RwLock<Vec<Arc<dyn PluginEntry>>> {
197 static REGISTRY: OnceLock<RwLock<Vec<Arc<dyn PluginEntry>>>> = OnceLock::new();
198 REGISTRY.get_or_init(|| RwLock::new(Vec::new()))
199}
200
201fn registered_plugins() -> Vec<Arc<dyn PluginEntry>> {
202 plugin_registry().read().clone()
203}
204
205fn register_plugin_arc<P: Plugin>(plugin: Arc<RwLock<P>>) {
206 let plugin_entry = Arc::new(PluginSlot::new(plugin.clone())) as Arc<dyn PluginEntry>;
207 let mut instances = plugin_instance_registry().write();
208 let type_id = TypeId::of::<P>();
209 if instances.contains_key(&type_id) {
210 warn!(
211 "Plugin '{}' was registered more than once; keeping the first instance",
212 std::any::type_name::<P>()
213 );
214 return;
215 }
216 instances.insert(type_id, plugin as Arc<dyn Any + Send + Sync>);
217 drop(instances);
218
219 let mut registry = plugin_registry().write();
220 registry.push(plugin_entry);
221}
222
223fn plugin_instance_registry() -> &'static RwLock<HashMap<TypeId, Arc<dyn Any + Send + Sync>>> {
224 static REGISTRY: OnceLock<RwLock<HashMap<TypeId, Arc<dyn Any + Send + Sync>>>> =
225 OnceLock::new();
226 REGISTRY.get_or_init(|| RwLock::new(HashMap::new()))
227}
228
229fn plugin_instance<T: Plugin>() -> Arc<RwLock<T>> {
230 let registry = plugin_instance_registry().read();
231 let type_id = TypeId::of::<T>();
232 let Some(plugin) = registry.get(&type_id) else {
233 panic!("Plugin '{}' is not registered", std::any::type_name::<T>());
234 };
235 let plugin = plugin.clone();
236 drop(registry);
237 match Arc::downcast::<RwLock<T>>(plugin) {
238 Ok(plugin) => plugin,
239 Err(_) => panic!(
240 "Plugin '{}' has a mismatched type",
241 std::any::type_name::<T>()
242 ),
243 }
244}