use std::fmt;
use std::sync::OnceLock;
pub mod bind;
pub mod bus;
pub mod imgui;
pub use bus::{Event, Service, Subscription, call, call_json, provide, provide_json, publish, publish_json, publish_retained, publish_retained_json, subscribe, subscribe_json};
pub use serde;
pub use serde_json;
pub const API_VERSION: u32 = 5;
pub type Handle = usize;
pub type HookFn = extern "C" fn(user: usize, instance: Handle, args: Handle) -> u8;
#[repr(C)]
pub struct Api {
pub log: extern "C" fn(*const u8, usize),
pub free: extern "C" fn(Handle),
pub get: extern "C" fn(Handle, *const u8, usize, *const u8, usize) -> Handle,
pub set: extern "C" fn(Handle, *const u8, usize, *const u8, usize, Handle) -> u8,
pub call: extern "C" fn(Handle, *const u8, usize, *const u8, usize, *const Handle, usize) -> Handle,
pub box_f64: extern "C" fn(f64) -> Handle,
pub box_str: extern "C" fn(*const u8, usize) -> Handle,
pub as_f64: extern "C" fn(Handle, *mut f64) -> u8,
pub as_str: extern "C" fn(Handle, *mut u8, usize) -> usize,
pub len: extern "C" fn(Handle) -> i64,
pub index: extern "C" fn(Handle, i64) -> Handle,
pub patch: extern "C" fn(*const u8, usize, *const u8, usize, u8, HookFn, usize) -> u8,
pub docs_path: extern "C" fn(*mut u8, usize) -> usize,
pub result_get: extern "C" fn() -> Handle,
pub result_set: extern "C" fn(Handle) -> u8,
pub key_pressed: extern "C" fn(*const u8, usize) -> u8,
pub key_down: extern "C" fn(*const u8, usize) -> u8,
pub invoke: extern "C" fn(Handle, i32, *const Handle, usize) -> Handle,
pub field_get: extern "C" fn(Handle, i32) -> Handle,
pub field_set: extern "C" fn(Handle, i32, Handle) -> u8,
pub ksa_version: extern "C" fn(*mut u8, usize) -> usize,
pub as_i64: extern "C" fn(Handle, *mut i64) -> u8,
pub box_i64: extern "C" fn(i64) -> Handle,
pub log_next: extern "C" fn() -> u64,
pub log_read: extern "C" fn(u64, *mut u8, usize) -> usize,
pub mod_status: extern "C" fn(*mut u8, usize) -> usize,
pub rhai_eval: extern "C" fn(*const u8, usize) -> u8,
pub rhai_result: extern "C" fn(*mut u8, usize) -> usize,
pub mod_id: *const u8,
pub mod_id_len: usize,
pub last_text: extern "C" fn(*mut u8, usize) -> usize,
pub bus_publish: extern "C" fn(*const u8, usize, *const u8, usize, *const u8, usize, u8) -> u8,
pub bus_subscribe: extern "C" fn(*const u8, usize, *const u8, usize, BusFn, usize) -> u64,
pub bus_unsubscribe: extern "C" fn(u64),
pub service_provide: extern "C" fn(*const u8, usize, *const u8, usize, ServiceFn, usize) -> u8,
pub service_reply: extern "C" fn(*const u8, usize),
pub service_call: extern "C" fn(*const u8, usize, *const u8, usize, *const u8, usize) -> u8,
}
unsafe impl Send for Api {}
unsafe impl Sync for Api {}
pub type BusFn = extern "C" fn(user: usize, json: *const u8, len: usize);
pub type ServiceFn = extern "C" fn(user: usize, args: *const u8, len: usize) -> u8;
static API: OnceLock<&'static Api> = OnceLock::new();
#[doc(hidden)]
pub fn set_api(api: &'static Api) {
let _ = API.set(api);
}
pub fn api() -> &'static Api {
API.get().expect("oxidizer_api used before oxidizer_init")
}
pub fn log(s: &str) {
(api().log)(s.as_ptr(), s.len())
}
pub fn read_string(f: impl Fn(*mut u8, usize) -> usize) -> String {
let mut buf = vec![0u8; 256];
let n = f(buf.as_mut_ptr(), buf.len());
if n > buf.len() {
buf.resize(n, 0);
f(buf.as_mut_ptr(), n);
}
buf.truncate(n);
String::from_utf8_lossy(&buf).into_owned()
}
pub struct Obj(Handle);
impl Obj {
pub const NULL: Obj = Obj(0);
pub unsafe fn from_handle(h: Handle) -> Obj {
Obj(h)
}
pub fn handle(&self) -> Handle {
self.0
}
pub fn is_null(&self) -> bool {
self.0 == 0
}
pub fn static_get(ty: &str, member: &str) -> Obj {
Obj((api().get)(0, ty.as_ptr(), ty.len(), member.as_ptr(), member.len()))
}
pub fn get(&self, member: &str) -> Obj {
Obj((api().get)(self.0, std::ptr::null(), 0, member.as_ptr(), member.len()))
}
pub fn set(&self, member: &str, value: &Obj) -> bool {
(api().set)(self.0, std::ptr::null(), 0, member.as_ptr(), member.len(), value.0) != 0
}
pub fn static_set(ty: &str, member: &str, value: &Obj) -> bool {
(api().set)(0, ty.as_ptr(), ty.len(), member.as_ptr(), member.len(), value.0) != 0
}
pub fn call(&self, method: &str, args: &[&Obj]) -> Obj {
Self::call_raw(self.0, "", method, args)
}
pub fn static_call(ty: &str, method: &str, args: &[&Obj]) -> Obj {
Self::call_raw(0, ty, method, args)
}
fn call_raw(h: Handle, ty: &str, method: &str, args: &[&Obj]) -> Obj {
let hs: Vec<Handle> = args.iter().map(|a| a.0).collect();
Obj((api().call)(h, ty.as_ptr(), ty.len(), method.as_ptr(), method.len(), hs.as_ptr(), hs.len()))
}
pub fn i64(&self) -> Option<i64> {
let mut v = 0;
((api().as_i64)(self.0, &mut v) != 0).then_some(v)
}
pub fn f64(&self) -> Option<f64> {
let mut v = 0.0;
((api().as_f64)(self.0, &mut v) != 0).then_some(v)
}
pub fn len(&self) -> Option<usize> {
usize::try_from((api().len)(self.0)).ok()
}
pub fn index(&self, i: usize) -> Obj {
Obj((api().index)(self.0, i as i64))
}
}
impl Drop for Obj {
fn drop(&mut self) {
if self.0 != 0 {
(api().free)(self.0)
}
}
}
impl fmt::Display for Obj {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&read_string(|b, c| (api().as_str)(self.0, b, c)))
}
}
impl From<f64> for Obj {
fn from(v: f64) -> Obj {
Obj((api().box_f64)(v))
}
}
impl From<i64> for Obj {
fn from(v: i64) -> Obj {
Obj((api().box_i64)(v))
}
}
impl From<bool> for Obj {
fn from(v: bool) -> Obj {
Obj((api().box_f64)(v as u8 as f64))
}
}
impl From<&str> for Obj {
fn from(s: &str) -> Obj {
Obj((api().box_str)(s.as_ptr(), s.len()))
}
}
pub fn patch(ty: &str, method: &str, postfix: bool, cb: HookFn, user: usize) -> bool {
(api().patch)(ty.as_ptr(), ty.len(), method.as_ptr(), method.len(), postfix as u8, cb, user) != 0
}
pub fn hook(ty: &str, method: &str, postfix: bool, f: fn(&Obj, &Obj) -> bool) -> bool {
extern "C" fn tramp(user: usize, inst: Handle, args: Handle) -> u8 {
let f: fn(&Obj, &Obj) -> bool = unsafe { std::mem::transmute(user) };
let (inst, args) = (Obj(inst), Obj(args));
std::panic::catch_unwind(|| f(&inst, &args)).unwrap_or(true) as u8
}
patch(ty, method, postfix, tramp, f as usize)
}
pub fn hook_result() -> Obj {
Obj((api().result_get)())
}
pub fn set_hook_result(value: &Obj) -> bool {
(api().result_set)(value.0) != 0
}
pub fn key_pressed(key: &str) -> bool {
(api().key_pressed)(key.as_ptr(), key.len()) != 0
}
pub fn key_down(key: &str) -> bool {
(api().key_down)(key.as_ptr(), key.len()) != 0
}
pub fn config_path(mod_id: &str) -> std::path::PathBuf {
let dir = std::path::PathBuf::from(read_string(|b, c| (api().docs_path)(b, c))).join("oxidizer");
let _ = std::fs::create_dir_all(&dir);
dir.join(format!("{mod_id}.toml"))
}
pub fn mods_dir() -> std::path::PathBuf {
std::path::PathBuf::from(read_string(|b, c| (api().docs_path)(b, c))).join("mods")
}
pub fn log_next() -> u64 {
(api().log_next)()
}
pub fn log_lines(from: u64) -> Vec<String> {
let text = read_string(|b, c| (api().log_read)(from, b, c));
if text.is_empty() { vec![] } else { text.split('\n').map(String::from).collect() }
}
#[derive(Clone, Debug)]
pub struct ModStatus {
pub id: String,
pub kind: String,
pub problem: Option<String>,
}
pub fn mod_statuses() -> Vec<ModStatus> {
read_string(|b, c| (api().mod_status)(b, c))
.lines()
.filter_map(|l| {
let mut f = l.splitn(3, '\t');
let (id, kind, problem) = (f.next()?, f.next()?, f.next().unwrap_or(""));
Some(ModStatus { id: id.into(), kind: kind.into(), problem: (!problem.is_empty()).then(|| problem.into()) })
})
.collect()
}
pub fn rhai_eval(code: &str) -> Result<String, String> {
let ok = (api().rhai_eval)(code.as_ptr(), code.len()) != 0;
let result = read_string(|b, c| (api().rhai_result)(b, c));
if ok { Ok(result) } else { Err(result) }
}
pub trait Mod: Send + 'static {
fn init() -> Self;
fn loaded(&mut self) {}
fn frame(&mut self, _player_time: f64, _dt: f64) {}
fn gui(&mut self, _dt: f64) {}
fn menu(&mut self) {}
fn unload(&mut self) {}
}
#[doc(hidden)]
pub fn guard(name: &str, f: impl FnOnce()) {
if std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)).is_err() {
log(&format!("mod panicked in {name}"));
}
}
#[macro_export]
macro_rules! oxidizer_mod {
($t:ty) => {
static __OXIDIZER_MOD: ::std::sync::Mutex<Option<$t>> = ::std::sync::Mutex::new(None);
#[unsafe(no_mangle)]
pub extern "C" fn oxidizer_api_version() -> u32 {
$crate::API_VERSION
}
fn __oxidizer_with(name: &str, f: impl FnOnce(&mut $t)) {
$crate::guard(name, || {
if let Some(m) = __OXIDIZER_MOD.lock().unwrap_or_else(|e| e.into_inner()).as_mut() {
f(m)
}
})
}
#[unsafe(no_mangle)]
pub extern "C" fn oxidizer_init(api: &'static $crate::Api) {
$crate::set_api(api);
$crate::guard("init", || {
let m = <$t as $crate::Mod>::init();
*__OXIDIZER_MOD.lock().unwrap_or_else(|e| e.into_inner()) = Some(m);
})
}
#[unsafe(no_mangle)]
pub extern "C" fn oxidizer_frame(t: f64, dt: f64) {
__oxidizer_with("frame", |m| $crate::Mod::frame(m, t, dt))
}
#[unsafe(no_mangle)]
pub extern "C" fn oxidizer_gui(dt: f64) {
__oxidizer_with("gui", |m| $crate::Mod::gui(m, dt))
}
#[unsafe(no_mangle)]
pub extern "C" fn oxidizer_menu() {
__oxidizer_with("menu", |m| $crate::Mod::menu(m))
}
#[unsafe(no_mangle)]
pub extern "C" fn oxidizer_loaded() {
__oxidizer_with("loaded", |m| $crate::Mod::loaded(m))
}
#[unsafe(no_mangle)]
pub extern "C" fn oxidizer_unload() {
__oxidizer_with("unload", |m| $crate::Mod::unload(m))
}
};
}