dynamite/
lib.rs

1//! Dynamite is a language-agnostic scripting system for the Rust programming language. Dynamite
2//! makes it easy to integrate scripting languages into your Rust program and is special in the way
3//! that it orchestrates communication not only between the host program and the scripting
4//! languages, but also allows each scripting language to interact with data in the other scripting
5//! languages as well.
6//!
7//! Dynamite is not currently usable, but is being developed as a component for the [Arsenal] game
8//! engine.
9//!
10//! [Arsenal]: https://github.com/katharostech/arsenal 
11
12#[macro_use]
13extern crate dlopen_derive;
14
15use safer_ffi::prelude::*;
16use serde::{Deserialize, Serialize};
17
18use std::{borrow::Cow, collections::HashMap};
19
20mod allocation;
21pub use allocation::*;
22
23pub use ty::Erased;
24mod ty {
25    use safer_ffi::derive_ReprC;
26
27    #[derive_ReprC]
28    #[ReprC::opaque]
29    /// A type used to represent an untyped pointer
30    pub struct Erased {
31        _private: (),
32    }
33}
34
35/// The path to a scripted type, i.e. the "module" path such as "mygame::physics::RigidBody".
36pub type TypePath = String;
37
38/// A registry of scripted types mapping their unique module path to the type definition.
39pub type ScriptApi = HashMap<TypePath, ScriptType>;
40
41/// A script-loaded type
42#[derive(Serialize, Deserialize, Debug, Clone)]
43pub enum ScriptType {
44    /// A struct definition
45    Struct(StructDefinition),
46    /// A function definition
47    Function(FunctionDefinition),
48}
49
50/// The information necessary to define a component including the component ID and the memory
51/// layout.
52#[derive(Serialize, Deserialize, Debug, Clone)]
53pub struct StructDefinition {
54    /// The size and alignment of the component
55    pub layout: DataLayout,
56    /// The type of component this is
57    pub component_type: DataType,
58    /// The definitions of the methods associated to to the [`method_pointers`] with the same index.
59    pub method_definitions: Vec<FunctionDefinition>,
60}
61
62impl HasDataLayout for StructDefinition {
63    fn get_data_layout(&self) -> DataLayout {
64        self.layout
65    }
66}
67
68pub trait HasDataLayout {
69    fn get_data_layout(&self) -> DataLayout;
70}
71
72/// A type memory layout
73#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
74pub struct DataLayout {
75    /// The number of bytes the type takes up
76    size: usize,
77    /// The alignment of the struct
78    align: usize,
79}
80
81impl DataLayout {
82    pub fn from_size_align(size: usize, align: usize) -> Result<Self, std::alloc::LayoutError> {
83        // TODO: Better way to verify the layout parameters?
84        std::alloc::Layout::from_size_align(size, align)?;
85
86        Ok(Self { size, align })
87    }
88
89    pub fn size(&self) -> usize {
90        self.size
91    }
92
93    pub fn align(&self) -> usize {
94        self.align
95    }
96}
97
98/// The type of component
99#[derive(Serialize, Deserialize, Debug, Clone)]
100pub enum DataType {
101    /// A pointer to a different type
102    Pointer(Box<ScriptType>),
103    /// A struct with string field keys
104    Struct {
105        fields: HashMap<String, StructDefinition>,
106    },
107    /// A primitive type
108    Primitive(Primitive),
109}
110
111/// A primitive type
112#[derive(Serialize, Deserialize, Debug, Clone)]
113pub enum Primitive {
114    U8,
115    U16,
116    U32,
117    U64,
118    U128,
119    I8,
120    I16,
121    I32,
122    I64,
123    I128,
124    F32,
125    F64,
126    Char,
127    Bool,
128}
129
130impl HasDataLayout for Primitive {
131    #[rustfmt::skip]
132    fn get_data_layout(&self) -> DataLayout {
133        match self {
134            Primitive::Char => DataLayout::from_size_align(1, 1).unwrap(),
135            Primitive::Bool => DataLayout::from_size_align(1, 1).unwrap(),
136            Primitive::U8   => DataLayout::from_size_align(1, 1).unwrap(),
137            Primitive::U16  => DataLayout::from_size_align(2, 2).unwrap(),
138            Primitive::U32  => DataLayout::from_size_align(4, 4).unwrap(),
139            Primitive::U64  => DataLayout::from_size_align(8, 8).unwrap(),
140            Primitive::U128 => DataLayout::from_size_align(16, 16).unwrap(),
141            Primitive::I8   => DataLayout::from_size_align(1, 1).unwrap(),
142            Primitive::I16  => DataLayout::from_size_align(2, 2).unwrap(),
143            Primitive::I32  => DataLayout::from_size_align(4, 4).unwrap(),
144            Primitive::I64  => DataLayout::from_size_align(8, 8).unwrap(),
145            Primitive::I128 => DataLayout::from_size_align(16, 16).unwrap(),
146            Primitive::F32  => DataLayout::from_size_align(4, 4).unwrap(),
147            Primitive::F64  => DataLayout::from_size_align(8, 8).unwrap(),
148        }
149    }
150}
151
152/// the definition for a script type's method
153#[derive(Serialize, Deserialize, Clone, Debug)]
154pub struct FunctionDefinition {
155    /// The arguments of the functions, mapping the arg name to the type path
156    pub arguments: HashMap<Cow<'static, str>, TypePath>,
157    /// The return value of the function
158    pub return_type: Option<TypePath>,
159}
160
161pub use language_adapter::*;
162#[allow(missing_docs)]
163mod language_adapter {
164    use super::{Erased, ScriptApi};
165    use dlopen::wrapper::{Container, WrapperApi};
166    use safer_ffi::{prelude::*, string::str_ref};
167
168    #[derive_ReprC]
169    #[repr(C)]
170    pub struct LanguageAdapterInitArgs {
171        pub log_info: extern "C" fn(safer_ffi::string::String),
172    }
173
174    /// Wrapper for [`LanguageAdapterCApi`] that makes the use of the functions more convenient.
175    pub struct LanguageAdapter(Container<LanguageAdapterCApi>);
176
177    impl LanguageAdapter {
178        pub fn new(api: Container<LanguageAdapterCApi>) -> Self {
179            Self(api)
180        }
181
182        /// Initialize the adapter
183        pub fn init_adapter(&self, args: &LanguageAdapterInitArgs) {
184            self.0.init_adapter(args)
185        }
186
187        /// Get the components from the language adapter
188        pub fn get_api(&self) -> Result<ScriptApi, serde_cbor::Error> {
189            serde_cbor::from_slice(&self.0.get_api())
190        }
191
192        pub fn run_function(&self, path: &str, args: &[*const Erased]) -> *const Erased {
193            (self.0.run_function)(path.into(), args.into())
194        }
195    }
196
197    /// The C API implemented by language adapters
198    #[derive(WrapperApi)]
199    pub struct LanguageAdapterCApi {
200        /// Initialize the language adapter. The implementation is required to be idempotent and should
201        /// be allowed to be called multiple times without negative side-effects.
202        init_adapter: fn(args: &LanguageAdapterInitArgs),
203
204        /// Get a catalog of all of the components discovered by the adapter. The return value of the
205        /// function must be a vector of bytes in the CBOR format corresponding to a serialized
206        /// [`ScriptApi`].
207        get_api: fn() -> safer_ffi::Vec<u8>,
208
209        /// Execute a function that is hosted by the language adapter.
210        run_function: fn(path: str_ref, args: c_slice::Ref<*const Erased>) -> *const Erased,
211    }
212}