turing-api 0.0.4

The Turing mod scripting API for Beat Saber
Documentation
use std::any::Any;
use std::cell::RefCell;
use std::collections::HashMap;
use std::ffi::{c_char, CString};
use once_cell::unsync::Lazy;
use paste::paste;

macro_rules! typed_ptr {
    ( $name:ident ) => {
        #[repr(C)]
        #[derive(Copy, Clone)]
        struct $name {
            ptr: *mut c_char
        }
    };
}

typed_ptr!(_Color);
typed_ptr!(_ColorNote);
typed_ptr!(_BombNote);
typed_ptr!(_ChainHeadNote);
typed_ptr!(_ChainLinkNote);
typed_ptr!(_ChainNote);
typed_ptr!(_Arc);
typed_ptr!(_Wall);
typed_ptr!(_Saber);
// typed_ptr!(_Player);
typed_ptr!(_Vec2);
typed_ptr!(_Vec3);
typed_ptr!(_Vec4);
typed_ptr!(_Quat);

macro_rules! color_methods {
    ( $tp:ty, $name:tt ) => {
        paste! {
            extern "C" {
                fn [<_ $name _set_color>]($name: $tp, color: _Color);
                fn [<_ $name _get_color>]($name: $tp) -> _Color;
            }
        }
    }
}

macro_rules! position_methods {
    ( $tp:ty, $name:tt ) => {
        paste! {
            extern "C" {
                fn [<_ $name _set_position>]($name: $tp, pos: _Vec3);
                fn [<_ $name _get_position>]($name: $tp) -> _Vec3;
                fn [<_ $name _set_orientation>]($name: $tp, orientation: _Quat);
                fn [<_ $name _get_orientation>]($name: $tp) -> _Quat;
            }
        }
    }
}

macro_rules! gameplay_object_methods {
    ( $tp:ty, $name:tt ) => {
        paste! {
            extern "C" {
                fn [<_create_ $name>](beat: f32) -> $tp;
                fn [<_beatmap_add_ $name>]($name: $tp);
                fn [<_beatmap_remove_ $name>]($name: $tp);
            }
        }
        position_methods! { $tp, $name }
        color_methods! { $tp, $name }
    };
}

macro_rules! attrs {
    ( $tp:ty, $name:tt, $( $attr:ident: $attr_tp:ty ),* $(,)? ) => {
        extern "C" {
            paste! {
                $(
                fn [<_ $name _get_attr_ $attr>]($name: [<_ $tp>]) -> $attr_tp;
                fn [<_ $name _set_attr_ $attr>]($name: [<_ $tp>], $attr: $attr_tp);
                )*
            }
        }

        impl $tp {
            $(

            paste! {
                pub fn [<get_ $attr>](&self) -> $attr_tp {
                    unsafe { [<_ $name _get_attr_ $attr>](self._inner) }
                }
                pub fn [<set_ $attr>](&self, $attr: $attr_tp) {
                    unsafe { [<_ $name _set_attr_ $attr>](self._inner, $attr) }
                }
            }

            )*
        }
    };
}

gameplay_object_methods! { _ColorNote, color_note }
gameplay_object_methods! { _BombNote, bomb_note }
gameplay_object_methods! { _Arc, arc }
gameplay_object_methods! { _Wall, wall }
gameplay_object_methods! { _ChainHeadNote, chain_head_note }
gameplay_object_methods! { _ChainLinkNote, chain_link_note }
gameplay_object_methods! { _ChainNote, chain_note }

color_methods! { _Saber, saber }

extern "C" {
    fn _get_left_saber() -> _Saber;
    fn _get_right_saber() -> _Saber;

    fn _log(message: *const c_char);

    // drops a host-managed object such as color notes, vectors, walls, etc
    fn _drop_reference(ptr: *mut c_char);


    fn _vec2_from_native(x: f32, y: f32) -> _Vec2;
    fn _vec3_from_native(x: f32, y: f32, z: f32) -> _Vec3;
    fn _vec4_from_native(x: f32, y: f32, z: f32, w: f32) -> _Vec4;
    fn _quat_from_native(x: f32, y: f32, z: f32, w: f32) -> _Quat;

    fn _color_set_rgb(color: _Color, r: f32, g: f32, b: f32);
    fn _color_set_rgba(color: _Color, r: f32, g: f32, b: f32, a: f32);

}

macro_rules! cstr {
    ( $str:expr ) => {
        CString::new($str).unwrap()
    };
}

// User facing definitions:

pub struct Log {}
impl Log {
    pub fn info(message: &str) {
        let s = format!("info: {}", message);
        let c = cstr!(s);
        unsafe { _log(c.as_ptr()) };
    }

    pub fn warning(message: &str) {
        let s = format!("warning: {}", message);
        let c = cstr!(s);
        unsafe { _log(c.as_ptr()) };
    }

    pub fn error(message: &str) {
        let s = format!("error: {}", message);
        let c = cstr!(s);
        unsafe { _log(c.as_ptr()) };
    }

    pub fn debug(message: &str) {
        let s = format!("debug: {}", message);
        let c = cstr!(s);
        unsafe { _log(c.as_ptr()) };
    }

}

macro_rules! wrapped {
    ( $name:tt ) => {
        pub struct $name {
            _inner: paste! { [<_ $name>] }
        }
    };
}

macro_rules! instantiable_obj {
    ( $ty:tt, $name:tt ) => {
        wrapped! { $ty }
        paste! {

            pub fn [<create_ $name>](beat: f32) -> $ty {
                unsafe { $ty { _inner: [<_create_ $name>](beat) } }
            }

            impl Beatmap {
                pub fn [<add_ $name>]($name: $ty) {
                    unsafe {
                        [<_beatmap_add_ $name>]($name._inner);
                    }
                }
            }
        }
    };
}

pub struct Beatmap;

thread_local! {
    static GLOBAL_MAP: Lazy<RefCell<HashMap<String, Box<dyn Any>>>> = Lazy::new(|| {
        RefCell::new(HashMap::new())
    });
}

pub struct Data;

impl Data {

    pub fn set_temp_value<T: Clone + 'static>(key: &str, v: T) {
        GLOBAL_MAP.with(|map| {
            map.borrow_mut().insert(key.to_string(), Box::new(v));
        })
    }

    pub fn get_temp_value<T: Clone + 'static>(key: &str) -> Option<T> {
        GLOBAL_MAP.with(|map| {
            map.borrow().get(key).and_then(|v| v.downcast_ref::<T>()).cloned()
        })
    }

    pub fn remove_temp_value(key: &str) {
        GLOBAL_MAP.with(|map| {
            map.borrow_mut().remove(key);
        })
    }

}

instantiable_obj! { ColorNote, color_note }
instantiable_obj! { BombNote, bomb_note }
instantiable_obj! { ChainHeadNote, chain_head_note }
instantiable_obj! { ChainLinkNote, chain_link_note }
instantiable_obj! { ChainNote, chain_note }
instantiable_obj! { Arc, arc }
instantiable_obj! { Wall, wall }

wrapped! { Vec2 }


wrapped! { Vec3 }
wrapped! { Vec4 }
wrapped! { Quat }
wrapped! { Color }

attrs! { Vec2, vec2, x: f32, y: f32 }
attrs! { Vec3, vec3, x: f32, y: f32, z: f32 }
attrs! { Vec4, vec4, x: f32, y: f32, z: f32, w: f32 }
attrs! { Quat, quat, x: f32, y: f32, z: f32, w: f32 }
attrs! { Color, color, r: f32, g: f32, b: f32, a: f32 }

pub trait UnityConvertible {
    type UnityType;
    fn to_unity_type(self) -> Self::UnityType;
    fn from_unity_type(t: Self::UnityType) -> Self;
}

impl UnityConvertible for glam::Vec2 {
    type UnityType = Vec2;
    fn to_unity_type(self) -> Self::UnityType {
        Vec2 { _inner: unsafe { _vec2_from_native(self.x, self.y) } }
    }
    fn from_unity_type(t: Self::UnityType) -> Self {
        glam::Vec2::new(t.get_x(), t.get_y())
    }
}

impl UnityConvertible for glam::Vec3 {
    type UnityType = Vec3;
    fn to_unity_type(self) -> Self::UnityType {
        Vec3 { _inner: unsafe { _vec3_from_native(self.x, self.y, self.z) } }
    }
    fn from_unity_type(t: Self::UnityType) -> Self {
        glam::Vec3::new(t.get_x(), t.get_y(), t.get_z())
    }
}

impl UnityConvertible for glam::Vec4 {
    type UnityType = Vec4;
    fn to_unity_type(self) -> Self::UnityType {
        Vec4 { _inner: unsafe { _vec4_from_native(self.x, self.y, self.z, self.w) } }
    }
    fn from_unity_type(t: Self::UnityType) -> Self {
        glam::Vec4::new(t.get_x(), t.get_y(), t.get_z(), t.get_w())
    }
}

impl UnityConvertible for glam::Quat {
    type UnityType = Quat;
    fn to_unity_type(self) -> Self::UnityType {
        Quat { _inner: unsafe { _quat_from_native(self.x, self.y, self.z, self.w) } }
    }
    fn from_unity_type(t: Self::UnityType) -> Self {
        glam::Quat::from_xyzw(t.get_x(), t.get_y(), t.get_z(), t.get_w())
    }
}


impl Color {
    pub fn set_rgb(&self, r: f32, g: f32, b: f32) {
        unsafe { _color_set_rgb(self._inner, r, g, b) };
    }

    pub fn set_rgba(&self, r: f32, g: f32, b: f32, a: f32) {
        unsafe { _color_set_rgba(self._inner, r, g, b, a) };
    }
}