-
-
Notifications
You must be signed in to change notification settings - Fork 119
/
mod.rs
215 lines (196 loc) · 6.33 KB
/
mod.rs
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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
//! 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 backtrace::Backtrace;
use std::{any::Any, fmt::Display, panic, sync::Mutex};
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";
// Contains details about a panic that we need to pass back to the application from the panic hook.
struct PanicInfo {
message: Option<String>,
location: Option<Location>,
backtrace: Backtrace,
}
impl Display for PanicInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let message = self
.message
.as_ref()
.map_or("No error given", |message| message.as_str());
write!(f, "\"{}\", ", message)?;
if let Some(location) = self.location.as_ref() {
write!(f, "{}", location)?;
} else {
write!(f, "no location given")?;
}
writeln!(f, "\nBacktrace:\n{:?}", self.backtrace)?;
Ok(())
}
}
struct Location {
file: String,
line: u32,
column: u32,
}
impl Display for Location {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}:{}", self.file, self.line, self.column)
}
}
static LAST_PANIC: Mutex<Option<PanicInfo>> = Mutex::new(None);
/// Capturing panics is something Rust really doesn't want you to do, and as such, they make it convoluted.
/// This sets up all the machinery in the background to pull it off.
///
/// It's okay to call this multiple times.
pub fn initialize_panic_handling() {
panic::set_hook(Box::new(|panic_info| {
let mut last_panic =
LAST_PANIC.lock().expect("Panic queue was poisoned."); // FIXME that can probably overflow the stack.
let message = if let Some(s) =
panic_info.payload().downcast_ref::<std::string::String>()
{
Some(s.as_str())
} else {
panic_info.payload().downcast_ref::<&str>().copied()
}
.map(|s| s.to_string());
let location = panic_info.location().map(|location| Location {
file: location.file().to_string(),
line: location.line(),
column: location.column(),
});
let backtrace = backtrace::Backtrace::new();
*last_panic = Some(PanicInfo {
message,
location,
backtrace,
});
}));
}
fn on_panic(_payload: Box<dyn Any + Send>) -> crate::abi::ffi_safe::String {
// The payload is technically no longer needed, but I left it there just in case a change to `catch_unwind` made
// it useful again.
if let Ok(mut panic_info) = LAST_PANIC.lock() {
if let Some(panic_info) = panic_info.take() {
crate::abi::ffi_safe::String::from(format!(
"Panic in model: {}",
panic_info
))
} else {
crate::abi::ffi_safe::String::from(
"Panic in model: No details were given.".to_string(),
)
}
} else {
crate::abi::ffi_safe::String::from(
"Panic in model, but due to a poisoned panic queue the information could not be collected."
.to_string())
}
}