shity basic one shot
This commit is contained in:
+167
@@ -0,0 +1,167 @@
|
||||
//! Evaluates a Rhai script that describes an Animator Controller, and hands the result to C# as JSON.
|
||||
//!
|
||||
//! The C# side never sees Rhai: it receives a `ControllerGraph` and drives the Animator As Code
|
||||
//! modification API with it.
|
||||
|
||||
pub mod builder;
|
||||
pub mod export;
|
||||
pub mod graph;
|
||||
pub mod motion_api;
|
||||
pub mod rhai_api;
|
||||
|
||||
use builder::Aac;
|
||||
use rhai::{Dynamic, Engine, Scope};
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::os::raw::c_char;
|
||||
use std::panic::{catch_unwind, AssertUnwindSafe};
|
||||
use std::ptr;
|
||||
|
||||
pub use graph::ControllerGraph;
|
||||
|
||||
/// Opaque to C#. Owns the graph, the Rhai engine, and the last error message.
|
||||
pub struct AacContext {
|
||||
aac: Aac,
|
||||
engine: Engine,
|
||||
last_error: Option<CString>,
|
||||
}
|
||||
|
||||
impl AacContext {
|
||||
fn set_error(&mut self, message: String) {
|
||||
// Interior NUL bytes would make CString::new fail; they can only come from a script string.
|
||||
self.last_error = Some(CString::new(message.replace('\0', "\\0")).unwrap_or_default());
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate a script and serialize the resulting graph. The testable core of the FFI.
|
||||
pub fn evaluate_to_json(script: &str) -> Result<String, String> {
|
||||
let aac = Aac::new();
|
||||
let engine = rhai_api::engine(aac.clone());
|
||||
let mut scope = Scope::new();
|
||||
let _ = engine
|
||||
.eval_with_scope::<Dynamic>(&mut scope, script)
|
||||
.map_err(|error| error.to_string())?;
|
||||
let graph = aac.write(|graph| graph.clone());
|
||||
export::to_json(&graph)
|
||||
}
|
||||
|
||||
/// Create a context. The returned pointer owns everything; release it with `aac_destroy`.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn aac_create() -> *mut AacContext {
|
||||
let aac = Aac::new();
|
||||
let engine = rhai_api::engine(aac.clone());
|
||||
Box::into_raw(Box::new(AacContext {
|
||||
aac,
|
||||
engine,
|
||||
last_error: None,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Evaluate a Rhai script. Returns 0 on success; on failure, call `aac_last_error`.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn aac_eval_rhai(handle: *mut AacContext, script: *const c_char) -> i32 {
|
||||
if handle.is_null() || script.is_null() {
|
||||
return 1;
|
||||
}
|
||||
// SAFETY: the caller guarantees `handle` came from `aac_create` and has not been destroyed.
|
||||
let context = unsafe { &mut *handle };
|
||||
let script = match unsafe { CStr::from_ptr(script) }.to_str() {
|
||||
Ok(script) => script,
|
||||
Err(error) => {
|
||||
context.set_error(format!("script is not valid UTF-8: {error}"));
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
let outcome = {
|
||||
let engine = &context.engine;
|
||||
catch_unwind(AssertUnwindSafe(|| {
|
||||
let mut scope = Scope::new();
|
||||
engine.eval_with_scope::<Dynamic>(&mut scope, script)
|
||||
}))
|
||||
};
|
||||
|
||||
match outcome {
|
||||
Ok(Ok(_)) => {
|
||||
context.last_error = None;
|
||||
0
|
||||
}
|
||||
Ok(Err(error)) => {
|
||||
context.set_error(error.to_string());
|
||||
1
|
||||
}
|
||||
Err(_) => {
|
||||
context.set_error("internal error: the evaluator panicked".to_string());
|
||||
2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialize the graph to JSON. Returns null on error; release the result with `aac_free_string`.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn aac_to_json(handle: *mut AacContext) -> *mut c_char {
|
||||
if handle.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
// SAFETY: the caller guarantees `handle` came from `aac_create` and has not been destroyed.
|
||||
let context = unsafe { &mut *handle };
|
||||
let outcome = catch_unwind(AssertUnwindSafe(|| {
|
||||
let graph = context.aac.write(|graph| graph.clone());
|
||||
export::to_json(&graph)
|
||||
}));
|
||||
|
||||
match outcome {
|
||||
Ok(Ok(json)) => match CString::new(json) {
|
||||
Ok(json) => {
|
||||
context.last_error = None;
|
||||
json.into_raw()
|
||||
}
|
||||
Err(_) => {
|
||||
context.set_error("internal error: serialized JSON contained a NUL byte".to_string());
|
||||
ptr::null_mut()
|
||||
}
|
||||
},
|
||||
Ok(Err(error)) => {
|
||||
context.set_error(error);
|
||||
ptr::null_mut()
|
||||
}
|
||||
Err(_) => {
|
||||
context.set_error("internal error: serialization panicked".to_string());
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The last error message, or null if the last call succeeded. Borrowed: valid until the next call
|
||||
/// on this context, and must not be freed.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn aac_last_error(handle: *mut AacContext) -> *const c_char {
|
||||
if handle.is_null() {
|
||||
return ptr::null();
|
||||
}
|
||||
// SAFETY: the caller guarantees `handle` came from `aac_create` and has not been destroyed.
|
||||
let context = unsafe { &*handle };
|
||||
match &context.last_error {
|
||||
Some(message) => message.as_ptr(),
|
||||
None => ptr::null(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Free a string returned by `aac_to_json`.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn aac_free_string(string: *mut c_char) {
|
||||
if string.is_null() {
|
||||
return;
|
||||
}
|
||||
// SAFETY: `string` must come from `aac_to_json`, which uses `CString::into_raw`.
|
||||
unsafe { drop(CString::from_raw(string)) };
|
||||
}
|
||||
|
||||
/// Destroy a context created by `aac_create`.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn aac_destroy(handle: *mut AacContext) {
|
||||
if handle.is_null() {
|
||||
return;
|
||||
}
|
||||
// SAFETY: `handle` must come from `aac_create`, which uses `Box::into_raw`.
|
||||
unsafe { drop(Box::from_raw(handle)) };
|
||||
}
|
||||
Reference in New Issue
Block a user