1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
#![allow(clippy::empty_docs)] // https://github.com/rust-lang/rust-clippy/issues/12377

mod proxy;

#[rustfmt::skip]
#[path = "gen/api.rs"]
mod api;

use std::{
    fmt,
    path::Path,
    sync::{Arc, LazyLock},
};

use abi_stable::library::lib_header_from_path;

pub use crate::api::*;
// This is not a public API. Use export_plugin! macro for plugin exporting.
#[doc(hidden)]
pub use crate::proxy::PluginMod_Ref;

/// Exports the plugin that will instantiated with the specified expression.
///
/// # Examples
///
/// ```
/// use openrr_plugin::Plugin;
///
/// openrr_plugin::export_plugin!(MyPlugin);
///
/// pub struct MyPlugin;
///
/// impl Plugin for MyPlugin {
/// }
/// ```
#[macro_export]
macro_rules! export_plugin {
    ($plugin_constructor:expr $(,)?) => {
        /// Exports the root module of this library.
        ///
        /// This code isn't run until the layout of the type it returns is checked.
        #[allow(clippy::drop_non_drop)] // this lint is triggered for code generated by #[export_root_module]
        #[::abi_stable::export_root_module]
        pub fn instantiate_root_module() -> $crate::PluginMod_Ref {
            $crate::PluginMod_Ref::new(plugin_constructor)
        }

        /// Instantiates the plugin.
        #[allow(clippy::drop_non_drop)] // this lint is triggered for code generated by #[export_root_module]
        #[::abi_stable::sabi_extern_fn]
        pub fn plugin_constructor() -> $crate::PluginProxy {
            $crate::PluginProxy::new($plugin_constructor)
        }
    };
}

impl PluginProxy {
    /// Loads a plugin from the specified path.
    pub fn from_path(path: impl AsRef<Path>) -> Result<Self, arci::Error> {
        let path = path.as_ref();

        let header = lib_header_from_path(path).map_err(anyhow::Error::from)?;
        let root_module = header
            .init_root_module::<PluginMod_Ref>()
            .map_err(anyhow::Error::from)?;

        let plugin_constructor = root_module.plugin_constructor();
        let plugin = plugin_constructor();

        Ok(plugin)
    }
}

impl fmt::Debug for PluginProxy {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("PluginProxy").finish()
    }
}

// Inspired by async-compat.
static TOKIO: LazyLock<tokio::runtime::Runtime> = LazyLock::new(|| {
    std::thread::Builder::new()
        .name("openrr-plugin/tokio".to_owned())
        .spawn(move || TOKIO.block_on(std::future::pending::<()>()))
        .unwrap();
    tokio::runtime::Builder::new_multi_thread()
        .enable_all()
        .build()
        .expect("cannot start tokio runtime")
});