phaneron_plugin/
lib.rs

1//! This module defines the interfaces to be implemented for a Phaneron plugin to be successfully loaded.
2//!
3//! To create a plugin you must have a function annotated with the `#[export_root_module]` from the `abi_stable` crate.
4//! This function should return a `PhaneronPluginRootModule` which acts as the handle which can be used to initialize
5//! your plugin. This first function should not perform any additional work such as loading assets etc. as your plugin will be given
6//! an opportunity to initialize itself later.
7//!
8//! You should then have a `load` function that is annotated using the `#[sabi_external_fn]` macro from the `abi_stable` crate.
9//! This function is the initializer for your plugin and is where you can load assets that are globally required and pre-allocate
10//! large amounts of memory if required. This function is allowed to fail and will only be called once. If it fails then the plugin
11//! will not be loaded and Phaneron will not attempt to load the plugin again. If failing, please return some useful error message.
12//!
13//! ```
14//! # use abi_stable::{
15//! #     export_root_module,
16//! #     prefix_type::PrefixTypeTrait,
17//! #     sabi_extern_fn,
18//! #     sabi_trait::TD_Opaque,
19//! #     std_types::{
20//! #         RResult::{self, RErr, ROk},
21//! #         RString, RVec,
22//! #     },
23//! # };
24//! # use log::LevelFilter;
25//! # use phaneron_plugin::{
26//! #     traits::NodeHandle_TO, traits::PhaneronPlugin_TO, types::NodeHandle, types::PhaneronPlugin,
27//! #     CreateNodeDescription, PhaneronPluginContext, PhaneronPluginRootModule,
28//! #     PhaneronPluginRootModuleRef, PluginNodeDescription,
29//! # };
30//! #[export_root_module]
31//! fn instantiate_root_module() -> PhaneronPluginRootModuleRef {
32//!     PhaneronPluginRootModule { load }.leak_into_prefix()
33//! }
34//!
35//! #[sabi_extern_fn]
36//! pub fn load(context: PhaneronPluginContext) -> RResult<PhaneronPlugin, RString> {
37//!     log::set_logger(phaneron_plugin::get_logger(&context)).unwrap();
38//!     log::set_max_level(LevelFilter::Trace);
39//!     let plugin = DemoPlugin {};
40//!
41//!     ROk(PhaneronPlugin_TO::from_value(plugin, TD_Opaque))
42//! }
43//!
44//! # struct DemoPlugin {}
45//! # impl phaneron_plugin::traits::PhaneronPlugin for DemoPlugin {
46//! #     fn get_available_node_types(&self) -> RVec<PluginNodeDescription> {
47//! #         todo!()
48//! #     }
49//! #
50//! #     fn create_node(&self, description: CreateNodeDescription) -> RResult<NodeHandle, RString> {
51//! #         todo!()
52//! #     }
53//! #
54//! #     fn destroy_node(&self, node_id: RString) -> RResult<(), RString> {
55//! #         todo!()
56//! #     }
57//! # }
58//! ```
59//!
60//! The returned `plugin` is of type `DemoPlugin` in this instance, which implements the [`PhaneronPlugin`](traits::PhaneronPlugin) trait. This object will be
61//! used to create nodes from your plugin and manage its lifecycle. Refer to the documentation of [`PhaneronPlugin`](traits::PhaneronPlugin).
62
63use std::fmt::Debug;
64
65use abi_stable::{
66    declare_root_module_statics,
67    library::RootModule,
68    package_version_strings, sabi_trait,
69    sabi_types::VersionStrings,
70    std_types::{RBox, RResult, RString, RVec},
71    StableAbi,
72};
73use log::{LevelFilter, SetLoggerError};
74use once_cell::sync::OnceCell;
75use types::PhaneronPlugin;
76
77pub use crate::{
78    audio::{AudioChannelLayout, AudioFormat},
79    colour::*,
80    graph::{AudioInputId, AudioOutputId, VideoInputId, VideoOutputId},
81    video::{InterlaceMode, VideoFormat},
82};
83
84mod audio;
85mod colour;
86mod graph;
87mod video;
88
89pub mod traits;
90pub mod types;
91
92/// A single logging instance is available to each individual plugin, so a plugin
93/// may request its context multiple times and get a reference to the same value.
94static LOGGER: OnceCell<PluginLogger> = OnceCell::new();
95
96/// Context passed to plugins, currently only serves as a way to provide a logging contet.
97#[repr(C)]
98#[derive(StableAbi)]
99pub struct PhaneronPluginContext {
100    logging_context: PhaneronLoggingContext_TO<'static, RBox<()>>,
101}
102
103impl PhaneronPluginContext {
104    pub fn new(logging_context: PhaneronLoggingContext_TO<'static, RBox<()>>) -> Self {
105        PhaneronPluginContext { logging_context }
106    }
107}
108
109impl Debug for PhaneronPluginContext {
110    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111        f.debug_struct("PhaneronPluginContext").finish()
112    }
113}
114
115/// This should not be used by plugins, it is consumed by Phaneron to carry log messages
116/// across the FFI boundary.
117#[repr(usize)]
118#[derive(StableAbi)]
119pub enum LogLevel {
120    Error = 1,
121    Warn,
122    Info,
123    Debug,
124    Trace,
125}
126
127impl From<log::Level> for LogLevel {
128    fn from(value: log::Level) -> Self {
129        match value {
130            log::Level::Error => LogLevel::Error,
131            log::Level::Warn => LogLevel::Warn,
132            log::Level::Info => LogLevel::Info,
133            log::Level::Debug => LogLevel::Debug,
134            log::Level::Trace => LogLevel::Trace,
135        }
136    }
137}
138
139/// This trait is used to allow the logger to be used across the FFI boundary. It should not be consumed by plugins.
140#[sabi_trait]
141pub trait PhaneronLoggingContext: Send + Sync + Clone {
142    fn log(&self, level: LogLevel, message: RString);
143}
144
145/// Describes the entrypoint for a plugin.
146#[repr(C)]
147#[derive(StableAbi)]
148#[sabi(kind(Prefix(prefix_ref = PhaneronPluginRootModuleRef)))]
149#[sabi(missing_field(panic))]
150pub struct PhaneronPluginRootModule {
151    #[sabi(last_prefix_field)]
152    pub load:
153        extern "C" fn(load_context: PhaneronPluginContext) -> RResult<PhaneronPlugin, RString>,
154}
155
156impl RootModule for PhaneronPluginRootModuleRef {
157    declare_root_module_statics! {PhaneronPluginRootModuleRef}
158    const BASE_NAME: &'static str = "phaneron-plugin";
159    const NAME: &'static str = "phaneron-plugin";
160    const VERSION_STRINGS: VersionStrings = package_version_strings!();
161}
162
163/// A video frame along with its associated output Id.
164#[repr(C)]
165#[derive(Clone, StableAbi)]
166pub struct VideoFrameWithId {
167    pub output_id: VideoOutputId,
168    pub frame: types::VideoFrame,
169}
170
171impl VideoFrameWithId {
172    pub fn new(output_id: VideoOutputId, frame: types::VideoFrame) -> Self {
173        Self { output_id, frame }
174    }
175}
176
177/// An audio frame along with its associated output Id.
178#[repr(C)]
179#[derive(Clone, StableAbi)]
180pub struct AudioFrameWithId {
181    pub output_id: AudioOutputId,
182    pub frame: types::AudioFrame,
183}
184
185impl AudioFrameWithId {
186    pub fn new(output_id: AudioOutputId, frame: types::AudioFrame) -> Self {
187        Self { output_id, frame }
188    }
189}
190
191/// Parameters to be passed to a process shader.
192/// Each call to a `set_param_` function will push a parameter of that
193/// type to the arguments list, so calls should be made in the order in which
194/// parameters are required to be sent to the shader.
195#[repr(C)]
196#[derive(Default, StableAbi)]
197pub struct ShaderParams {
198    params: RVec<ShaderParam>,
199}
200impl ShaderParams {
201    pub fn set_param_video_frame_input(&mut self, video_frame: types::VideoFrame) {
202        self.params.push(ShaderParam::VideoFrameInput(video_frame));
203    }
204
205    pub fn set_param_u32_input(&mut self, val: u32) {
206        self.params.push(ShaderParam::U32Input(val));
207    }
208
209    pub fn set_param_f32_input(&mut self, val: f32) {
210        self.params.push(ShaderParam::F32Input(val));
211    }
212
213    /// Sets a video frame as an output of a shader.
214    pub fn set_param_video_frame_output(&mut self, width: usize, height: usize) {
215        self.params
216            .push(ShaderParam::VideoFrameOutput { width, height });
217    }
218
219    pub fn get_params(&self) -> &RVec<ShaderParam> {
220        &self.params
221    }
222}
223
224/// Available shader parameter types, these do not need to be directly consumed by plugins.
225#[repr(C)]
226#[derive(StableAbi)]
227pub enum ShaderParam {
228    VideoFrameInput(types::VideoFrame),
229    U32Input(u32),
230    F32Input(f32),
231    VideoFrameOutput { width: usize, height: usize },
232}
233
234/// Provides logging to a plugin.
235/// It can be set as the default logger for the `log` trait by calling `init`.
236pub struct PluginLogger {
237    context: PhaneronLoggingContext_TO<'static, RBox<()>>,
238}
239
240impl PluginLogger {
241    /// Sets this logger as the global logger for `log`.
242    /// Returns an error if a logger has already been set as the
243    /// global logger.
244    pub fn init(&'static self) -> Result<(), SetLoggerError> {
245        log::set_logger(self)?;
246        log::set_max_level(LevelFilter::Trace);
247        Ok(())
248    }
249}
250
251impl Debug for PluginLogger {
252    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
253        f.debug_struct("PluginLogger").finish()
254    }
255}
256
257impl log::Log for PluginLogger {
258    fn enabled(&self, _metadata: &log::Metadata) -> bool {
259        true
260    }
261
262    fn log(&self, record: &log::Record) {
263        if self.enabled(record.metadata()) {
264            self.context
265                .log(record.level().into(), record.args().to_string().into())
266        }
267    }
268
269    fn flush(&self) {}
270}
271
272/// Returns the logger for a plugin which can then be used to pass log messages to Phaneron.
273pub fn get_logger(context: &PhaneronPluginContext) -> &'static PluginLogger {
274    let logger = PluginLogger {
275        context: context.logging_context.clone(),
276    };
277    LOGGER.set(logger).unwrap();
278    LOGGER.get().unwrap()
279}