gc_plugin_abi 0.5.0

Gridcore Plugin API
Documentation
use gc_abi::{GCAbiConversion, raw};
use serde_json::Value;
use std::{
    ffi::{CString, c_char},
    sync::OnceLock,
};

use crate::{GCBorrowedDatapointValue, GCPluginInfo, GCPluginInterface, log};
static PLUGIN_INFO: OnceLock<GCPluginInfo> = OnceLock::new();

/// A trait that represents the internal state of a plugin.
/// Every plugin must implement this trait.
pub trait GCPluginInstance<'a>: Send + Sync + Sized {
    /// Entrypoint of the plugin and should return the instance of the plugin.
    fn init(plugin_interface: &'a GCPluginInterface) -> Box<Self>;

    /// Method invoked by the core gateway to get the plugin information.
    /// This can be called multiple times even if the instance is not running.
    fn get_plugin_info() -> GCPluginInfo;

    /// Method invoked by the core gateway whenever a new datapoint value is received.
    /// This method takes ownership of the datapoint value, and unlike the C ABI counterpart, it is not required to free the memory explicitly, which is done automatically when the object is dropped.
    fn receive_datapoint(&self, data_value: GCBorrowedDatapointValue) -> bool;

    /// Method invoked by a script to get the plugin configuration schema.
    fn get_config_schema() -> Option<Value> {
        None
    }

    /// Low level plugin initialization, should not be called directly.
    #[doc(hidden)]
    #[inline]
    unsafe fn gc_plugin_init(init_ctx: raw::GCPluginInterface) -> raw::GCInstanceHandle {
        unsafe {
            let release_callback = (*init_ctx).releaseDatapointValueCallback;
            let result = std::panic::catch_unwind(|| {
                crate::set_datapoint_release_fn(release_callback);
                Self::init(init_ctx.into())
            });
            match result {
                Ok(state) => std::boxed::Box::into_raw(state) as raw::GCInstanceHandle,
                Err(e) => {
                    let init_ctx: &GCPluginInterface = init_ctx.into();
                    let msg = e
                        .downcast_ref::<&str>()
                        .map(|s| s.to_string())
                        .or_else(|| e.downcast_ref::<String>().cloned())
                        .unwrap_or_else(|| "Unknown panic".to_string());
                    log!(init_ctx, crate::raw::eGCLogLevel_ERROR, "Plugin panicked during initialization: {}", msg);
                    std::ptr::null_mut()
                }
            }
        }
    }

    /// Low level plugin shutdown, should not be called directly.
    #[doc(hidden)]
    #[inline]
    unsafe fn gc_plugin_shutdown(instance: raw::GCInstanceHandle) {
        unsafe {
            drop(std::boxed::Box::from_raw(instance as *mut Self));
        }
    }

    /// Low level plugin get info, should not be called directly.
    #[doc(hidden)]
    #[inline]
    unsafe fn gc_plugin_get_info() -> raw::GCPluginInfo {
        unsafe { PLUGIN_INFO.get_or_init(|| Self::get_plugin_info()).as_ptr() }
    }

    /// Low level plugin receive datapoint, should not be called directly.
    #[doc(hidden)]
    #[inline]
    unsafe fn gc_plugin_receive_datapoint(instance: raw::GCInstanceHandle, data_value: raw::GCDatapointValue) -> bool {
        unsafe {
            let ctx = &*(instance as *const Self);

            ctx.receive_datapoint(data_value.into())
        }
    }

    /// Low level plugin receive datapoint, should not be called directly.
    #[doc(hidden)]
    #[inline]
    unsafe fn gc_plugin_config_schema() -> *mut c_char {
        let schema = match Self::get_config_schema() {
            Some(mut schema) => {
                let version = Self::get_plugin_info().version();
                let obj = schema.as_object_mut().unwrap();
                obj.insert("version".to_string(), Value::String(version));
                serde_json::to_string_pretty(&schema).unwrap()
            }
            None => "".to_string(),
        };
        let c_str_schema = CString::new(schema).unwrap();
        c_str_schema.into_raw()
    }
}