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}