From f87cb37621e542fc75519e5aabf0ca064714db0d Mon Sep 17 00:00:00 2001 From: cat_or_not <41955154+catornot@users.noreply.github.com> Date: Mon, 19 Aug 2024 01:55:15 -0400 Subject: [PATCH] probably safe userdata + get sqvm backend rewrite --- rrplug_proc/src/lib.rs | 12 ++- rrplug_proc/src/parsing.rs | 10 +-- src/high/squirrel.rs | 172 +++++++++++++++++++++++++++++++++++- src/high/squirrel_traits.rs | 21 ++++- 4 files changed, 197 insertions(+), 18 deletions(-) diff --git a/rrplug_proc/src/lib.rs b/rrplug_proc/src/lib.rs index 3a93c41..85abafd 100644 --- a/rrplug_proc/src/lib.rs +++ b/rrplug_proc/src/lib.rs @@ -58,7 +58,6 @@ pub fn sqfunction(attr: TokenStream, item: TokenStream) -> TokenStream { } = input; let stmts = block.stmts; - let mut sub_stms: Vec = Vec::new(); let ident = &sig.ident; let input = &sig.inputs; let input_vec: Vec = input.iter().filter_map(|arg| filter_args(arg)).collect(); @@ -112,17 +111,13 @@ pub fn sqfunction(attr: TokenStream, item: TokenStream) -> TokenStream { match input_mapping(input, &mut sq_stack_pos) { Ok(tks) => { for tk in tks { - push_stmts!(sq_gets_stmts, tk); + sq_gets_stmts.push(parse_macro_input!(tk as Stmt)); } } Err(err) => { return err.to_compile_error().into(); } } - sq_gets_stmts.reverse(); - for s in sq_gets_stmts { - sub_stms.insert(0, parse(quote! {#[allow(unused_mut)] #s}.into()).unwrap()); - } let mut script_vm: Punctuated = Punctuated::new(); @@ -177,7 +172,10 @@ pub fn sqfunction(attr: TokenStream, item: TokenStream) -> TokenStream { use rrplug::high::squirrel_traits::{GetFromSquirrelVm,ReturnToVm}; let sq_functions = SQFUNCTIONS.from_sqvm(sqvm); - #(#sub_stms)* + #[allow(unused)] + let mut current_stack_pos = 1; + + #(#sq_gets_stmts)* fn inner_function( sqvm: std::ptr::NonNull, sq_functions: &'static SquirrelFunctions #(, #input_vec)* ) #output { let engine_token = unsafe { rrplug::high::engine::EngineToken::new_unchecked() }; diff --git a/rrplug_proc/src/parsing.rs b/rrplug_proc/src/parsing.rs index 4fb099c..572f595 100644 --- a/rrplug_proc/src/parsing.rs +++ b/rrplug_proc/src/parsing.rs @@ -40,13 +40,6 @@ impl Parse for Args { } } -macro_rules! push_stmts { - ($stmts:ident, $tk:ident) => { - let new_stmt = parse_macro_input!($tk as Stmt); - $stmts.insert(0, new_stmt); - }; -} - pub fn input_mapping( args: &Punctuated, sq_stack_pos: &mut i32, @@ -63,7 +56,8 @@ pub fn input_mapping( let ty = get_arg_type(arg)?; let tk = quote! { - let #name: #ty = GetFromSquirrelVm::get_from_sqvm(sqvm, sq_functions, #sq_stack_pos); + #[allow(unused_mut)] + let #name: #ty = GetFromSquirrelVm::get_from_sqvm_internal(sqvm, sq_functions, &mut current_stack_pos); }.into(); token_streams.push(tk); diff --git a/src/high/squirrel.rs b/src/high/squirrel.rs index 4903ea0..6df6e3a 100644 --- a/src/high/squirrel.rs +++ b/src/high/squirrel.rs @@ -3,10 +3,19 @@ //! squirrel vm related function and statics use parking_lot::Mutex; -use std::{marker::PhantomData, ptr::NonNull}; +use std::{ + any::TypeId, + hash::{DefaultHasher, Hash, Hasher}, + marker::PhantomData, + ops::{Add, Deref, DerefMut}, + ptr::{self, NonNull}, +}; use super::{ - squirrel_traits::{GetFromSQObject, IntoSquirrelArgs, IsSQObject}, + squirrel_traits::{ + GetFromSQObject, GetFromSquirrelVm, IntoSquirrelArgs, IsSQObject, PushToSquirrelVm, + SQVMName, + }, UnsafeHandle, }; use crate::{ @@ -243,6 +252,165 @@ impl AsRef> for SquirrelFn { } } +/// [`UserData`] is used to push user provided data to the sqvm for storage +/// +/// [`UserDataRef`] can be used to access the data from functions calls +/// +/// [`UserData`] handles dropping the data via a release hook in sqvm +pub struct UserData(Box); + +impl UserData { + /// Creates a new [`UserData`]. + /// + /// # Example + /// + /// ``` + /// # use rrplug::prelude::*; + /// # use rrplug::high::squirrel::UserData; + /// // cannot be pushed to sqvm normally + /// struct HttpClient; + /// + /// #[rrplug::sqfunction(VM = "SERVER | CLIENT | UI")] + /// fn build_client() -> UserData { + /// UserData::new(HttpClient) + /// } + /// ``` + pub fn new(userdata: T) -> Self { + Self(userdata.into()) + } + + /// Creates a new [`UserData`] from boxed data + pub fn from_boxed(userdata: Box) -> Self { + Self(userdata) + } +} + +impl From for UserData { + fn from(value: T) -> Self { + Self(value.into()) + } +} + +impl From> for UserData { + fn from(value: Box) -> Self { + Self(value) + } +} + +impl SQVMName for UserData { + fn get_sqvm_name() -> String { + "userdata".to_string() + } +} + +impl PushToSquirrelVm for UserData { + fn push_to_sqvm(self, sqvm: NonNull, sqfunctions: &SquirrelFunctions) { + unsafe { + (sqfunctions.sq_createuserdata)(sqvm.as_ptr(), std::mem::size_of::<*mut T>() as i32) + .cast::<*mut T>() + .write(Box::leak(self.0)); + + let mut hasher = DefaultHasher::new(); + TypeId::of::().hash(&mut hasher); + + (sqfunctions.sq_setuserdatatypeid)(sqvm.as_ptr(), -1, hasher.finish()); + (sqfunctions.sq_setreleasehook)(sqvm.as_ptr(), -1, releasehook::); + }; + + extern "C" fn releasehook(userdata: *const std::ffi::c_void, _: i32) { + unsafe { + let _ = Box::from_raw(*userdata.cast::<*mut T>()); + }; + } + } +} + +/// Used to refrence [`UserData`] stored on the sqvm +/// +/// the data cannot be moved out of since it's referenec counted in the sqvm +/// +/// # SAFETY +/// +/// [`UserDataRef`] **MUST** remain within the same stackframe (aka get dropped before the squirrel function returns it was created in) +/// otherwise bad things will happen since the underlying [`UserData`] might get dropped +/// +/// This should be inforced by `!Move` and `!Sync` constraints so this is just a warning +/// +/// # Example +/// +/// ``` +/// # use rrplug::prelude::*; +/// # use rrplug::high::squirrel::UserDataRef; +/// struct HiddenMessage(String); // msg cannot be accessed from the sqvm +/// +/// #[rrplug::sqfunction(VM = "SERVER")] +/// fn get_secret_msg(msg: UserDataRef) -> String { +/// msg.0.chars().map(|_| '*').collect() +/// } +/// ``` +pub struct UserDataRef<'a, T>(&'a mut T, PhantomData<*mut ()>); + +impl<'a, T: 'static> GetFromSquirrelVm for UserDataRef<'a, T> { + fn get_from_sqvm( + sqvm: NonNull, + sqfunctions: &'static SquirrelFunctions, + stack_pos: i32, + ) -> Self { + let mut out_ptr = ptr::null_mut(); + + let mut hasher = DefaultHasher::new(); + TypeId::of::().hash(&mut hasher); + let type_id = hasher.finish(); + + let mut out_type_id = type_id; + + unsafe { + debug_assert!( + (sqfunctions.sq_getuserdata)( + sqvm.as_ptr(), + stack_pos, + &mut out_ptr, + &mut out_type_id + ) != SQRESULT::SQRESULT_ERROR + ) + } + + debug_assert_eq!(type_id, out_type_id, "script provided incorrect userdata"); + + UserDataRef(unsafe { &mut **out_ptr.cast::<*mut T>() }, PhantomData) + } + + fn get_from_sqvm_internal( + sqvm: NonNull, + sqfunctions: &'static SquirrelFunctions, + stack_pos: &mut i32, + ) -> Self { + let userdata = Self::get_from_sqvm(sqvm, sqfunctions, stack_pos.add(1)); + *stack_pos += 2; // for some reason userdata also has a table pushed with it + userdata + } +} + +impl<'a, T> SQVMName for UserDataRef<'a, T> { + fn get_sqvm_name() -> String { + "userdata".to_string() + } +} + +impl<'a, T> Deref for UserDataRef<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.0 + } +} + +impl<'a, T> DerefMut for UserDataRef<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.0 + } +} + /// Adds a sqfunction to the registration list /// /// The sqfunction will be registered when its vm is loaded diff --git a/src/high/squirrel_traits.rs b/src/high/squirrel_traits.rs index a7ea035..012c186 100644 --- a/src/high/squirrel_traits.rs +++ b/src/high/squirrel_traits.rs @@ -271,14 +271,33 @@ macro_rules! get_from_sqvm { /// /// # Use cases /// - getting the arguments in native closures -pub trait GetFromSquirrelVm { +pub trait GetFromSquirrelVm: Sized { /// tries to get the value out of the squirrel stack but it cannot fail /// so this can panic + /// + /// this is the user function do not overwrite the other one fn get_from_sqvm( sqvm: NonNull, sqfunctions: &'static SquirrelFunctions, stack_pos: i32, ) -> Self; + + /// this is only for certain internal apis + /// + /// don't use this only for internal apis + fn get_from_sqvm_internal( + sqvm: NonNull, + sqfunctions: &'static SquirrelFunctions, + stack_pos: &mut i32, + ) -> Self { + let s = Self::get_from_sqvm(sqvm, sqfunctions, *stack_pos); + + // increament by the size this thing in the stack + // for some reason userdata also has a table pushed with it; quite annoying + *stack_pos += 1; + + s + } } get_from_sqvm! {