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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
//! Internal implementation details for the host-guest interface.
//!
//! Note that the vast majority of this module is just providing FFI-safe
//! versions of common `std` types (e.g. `Vec`, `String`, and `Box<dyn Error>`),
//! or FFI-safe trait objects.
//!
/// If the macro generated the wrong code, this doctest would fail.
///
/// ```rust
/// use fj::{abi::INIT_FUNCTION_NAME, models::Metadata};
///
/// fj::register_model!(|_| {
///     Ok(Metadata::new("My Model", "1.2.3"))
/// });
///
/// mod x {
///     extern "C" {
///         pub fn fj_model_init(_: *mut fj::abi::Host<'_>) -> fj::abi::InitResult;
///     }
/// }
///
/// // make sure our function has the right signature
/// let func: fj::abi::InitFunction = fj_model_init;
///
/// // We can also make sure the unmangled name is correct by calling it.
///
/// let metadata: fj::models::Metadata = unsafe {
///     let mut host = Host;
///     let mut host = fj::abi::Host::from(&mut host);
///     x::fj_model_init(&mut host).unwrap().into()
/// };
///
/// assert_eq!(metadata.name, "My Model");
///
/// struct Host;
/// impl fj::models::Host for Host {
///     fn register_boxed_model(&mut self, model: Box<dyn fj::models::Model>) { todo!() }
/// }
/// ```
mod context;
pub mod ffi_safe;
mod host;
mod metadata;
mod model;

use std::any::Any;

pub use self::{
    context::Context,
    host::Host,
    metadata::{Metadata, ModelMetadata},
    model::Model,
};

/// Define the initialization routine used when registering models.
///
/// See the [`crate::model`] macro if your model can be implemented as a pure
/// function.
///
/// # Examples
///
/// ```rust
/// use fj::models::*;
///
/// fj::register_model!(|host: &mut dyn Host| {
///     host.register_model(MyModel::default());
///
///     Ok(Metadata::new(env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")))
/// });
///
/// #[derive(Default)]
/// struct MyModel { }
///
/// impl Model for MyModel {
///     fn metadata(&self) -> std::result::Result<fj::models::ModelMetadata, Box<(dyn std::error::Error + Send + Sync + 'static)>> { todo!() }
///
///     fn shape(&self, ctx: &dyn Context) -> Result<fj::Shape, Error> {
///         todo!()
///     }
/// }
/// ```
#[macro_export]
macro_rules! register_model {
    ($init:expr) => {
        #[no_mangle]
        unsafe extern "C" fn fj_model_init(
            mut host: *mut $crate::abi::Host<'_>,
        ) -> $crate::abi::InitResult {
            let init: fn(
                &mut dyn $crate::models::Host,
            ) -> Result<
                $crate::models::Metadata,
                $crate::models::Error,
            > = $init;

            match init(&mut *host) {
                Ok(meta) => $crate::abi::InitResult::Ok(meta.into()),
                Err(e) => $crate::abi::InitResult::Err(e.into()),
            }
        }
    };
}

/// The signature of the function generated by [`register_model`].
///
/// ```rust
/// fj::register_model!(|_| { todo!() });
///
/// const _: fj::abi::InitFunction = fj_model_init;
/// ```
pub type InitFunction = unsafe extern "C" fn(*mut Host<'_>) -> InitResult;
pub type InitResult = ffi_safe::Result<Metadata, ffi_safe::BoxedError>;
pub type ShapeResult = ffi_safe::Result<crate::Shape, ffi_safe::BoxedError>;
pub type ModelMetadataResult =
    ffi_safe::Result<ModelMetadata, ffi_safe::BoxedError>;

/// The name of the function generated by [`register_model`].
///
pub const INIT_FUNCTION_NAME: &str = "fj_model_init";

fn on_panic(payload: Box<dyn Any + Send>) -> crate::abi::ffi_safe::String {
    let msg: &str =
        if let Some(s) = payload.downcast_ref::<std::string::String>() {
            s.as_str()
        } else if let Some(s) = payload.downcast_ref::<&str>() {
            s
        } else {
            "A panic occurred"
        };

    crate::abi::ffi_safe::String::from(msg.to_string())
}