Skip to main content

intuicio_plugins/
lib.rs

1//! Loading Intuicio plugins from dynamic libraries.
2//!
3//! A plugin is a `cdylib` that exports two C functions:
4//!
5//! ```ignore
6//! #[unsafe(no_mangle)]
7//! pub extern "C" fn version() -> IntuicioVersion { core_version() }
8//!
9//! #[unsafe(no_mangle)]
10//! pub extern "C" fn install(registry: &mut Registry) { /* register here */ }
11//! ```
12//!
13//! [`install_plugin`] loads the library, compares the two versions and calls
14//! `install`. A loaded library is kept alive for the rest of the thread, since
15//! the registry now holds pointers into it.
16use intuicio_core::{IntuicioVersion, crate_version, registry::Registry};
17use libloading::Library;
18use std::{cell::RefCell, collections::HashMap};
19
20thread_local! {
21    static LIBRARIES: RefCell<HashMap<String, Library>> = Default::default();
22}
23
24/// A plugin was built against an incompatible version of the platform.
25#[derive(Debug, Copy, Clone)]
26pub struct IncompatibleVersionsError {
27    /// Version the host reported.
28    pub host: IntuicioVersion,
29    /// Version the plugin reported.
30    pub plugin: IntuicioVersion,
31}
32
33impl std::fmt::Display for IncompatibleVersionsError {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        write!(
36            f,
37            "Incompatible host ({}) and plugin ({}) versions!",
38            self.host, self.plugin
39        )
40    }
41}
42
43impl std::error::Error for IncompatibleVersionsError {}
44
45/// Loads the plugin at `path` and lets it register its types and functions.
46///
47/// `host_version` defaults to [`plugins_version`]. The plugin is rejected when
48/// its major and minor numbers differ from the host ones.
49///
50/// The library stays loaded for the life of the calling thread, keyed by
51/// `path`. Loading the same path twice replaces the entry, so the second load
52/// drops the first library.
53///
54/// # Errors
55///
56/// Fails when the library cannot be loaded, when it exports no `version` or
57/// `install` symbol, or with [`IncompatibleVersionsError`] on a version
58/// mismatch.
59pub fn install_plugin(
60    path: &str,
61    registry: &mut Registry,
62    host_version: Option<IntuicioVersion>,
63) -> Result<(), Box<dyn std::error::Error>> {
64    unsafe {
65        let host_version = host_version.unwrap_or_else(plugins_version);
66        let library = Library::new(path)?;
67        let version = library.get::<unsafe extern "C" fn() -> IntuicioVersion>(b"version\0")?;
68        let plugin_version = version();
69        if !host_version.is_compatible(&plugin_version) {
70            return Err(Box::new(IncompatibleVersionsError {
71                host: host_version,
72                plugin: plugin_version,
73            }));
74        }
75        let install = library.get::<unsafe extern "C" fn(&mut Registry)>(b"install\0")?;
76        install(registry);
77        LIBRARIES.with(|map| map.borrow_mut().insert(path.to_owned(), library));
78        Ok(())
79    }
80}
81
82/// Returns the version of this crate, which plugins are checked against.
83pub fn plugins_version() -> IntuicioVersion {
84    crate_version!()
85}