tessera_ui/
plugin.rs

1//! Plugin lifecycle hooks for Tessera platform integrations.
2
3use 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
17/// The result type used by plugin lifecycle hooks.
18pub type PluginResult = Result<(), Box<dyn Error + Send + Sync>>;
19
20/// Lifecycle hooks for platform plugins.
21pub trait Plugin: Send + Sync + 'static {
22    /// Returns the plugin name for logging and diagnostics.
23    fn name(&self) -> &'static str {
24        std::any::type_name::<Self>()
25    }
26
27    /// Called when the renderer creates or resumes its platform resources.
28    fn on_resumed(&mut self, _context: &PluginContext) -> PluginResult {
29        Ok(())
30    }
31
32    /// Called when the renderer suspends and releases platform resources.
33    fn on_suspended(&mut self, _context: &PluginContext) -> PluginResult {
34        Ok(())
35    }
36
37    /// Called when the renderer is shutting down.
38    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/// Platform context shared with plugins during lifecycle events.
79#[derive(Clone)]
80pub struct PluginContext {
81    window: Arc<Window>,
82    #[cfg(target_os = "android")]
83    android_app: AndroidApp,
84}
85
86impl PluginContext {
87    /// Returns the active window associated with the renderer.
88    pub fn window(&self) -> &Window {
89        &self.window
90    }
91
92    /// Clones the underlying window handle for long-lived usage.
93    pub fn window_handle(&self) -> Arc<Window> {
94        self.window.clone()
95    }
96
97    /// Returns the Android application handle when running on Android.
98    #[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
117/// Registers a plugin instance for the current process.
118pub fn register_plugin<P: Plugin>(plugin: P) {
119    register_plugin_arc(Arc::new(RwLock::new(plugin)));
120}
121
122/// Registers a plugin instance wrapped in an `Arc<RwLock<_>>`.
123pub fn register_plugin_boxed<P: Plugin>(plugin: Arc<RwLock<P>>) {
124    register_plugin_arc(plugin);
125}
126
127/// Provides access to the registered plugin instance.
128///
129/// # Panics
130///
131/// Panics if the plugin type was not registered.
132pub 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
141/// Provides mutable access to the registered plugin instance.
142///
143/// # Panics
144///
145/// Panics if the plugin type was not registered.
146pub 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}