Skip to content

Commit

Permalink
probably safe userdata + get sqvm backend rewrite
Browse files Browse the repository at this point in the history
  • Loading branch information
catornot committed Aug 19, 2024
1 parent be5b626 commit f87cb37
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 18 deletions.
12 changes: 5 additions & 7 deletions rrplug_proc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ pub fn sqfunction(attr: TokenStream, item: TokenStream) -> TokenStream {
} = input;

let stmts = block.stmts;
let mut sub_stms: Vec<Stmt> = Vec::new();
let ident = &sig.ident;
let input = &sig.inputs;
let input_vec: Vec<FnArg> = input.iter().filter_map(|arg| filter_args(arg)).collect();
Expand Down Expand Up @@ -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<TypePath, Token![|]> = Punctuated::new();

Expand Down Expand Up @@ -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<rrplug::bindings::squirreldatatypes::HSquirrelVM>, sq_functions: &'static SquirrelFunctions #(, #input_vec)* ) #output {
let engine_token = unsafe { rrplug::high::engine::EngineToken::new_unchecked() };
Expand Down
10 changes: 2 additions & 8 deletions rrplug_proc/src/parsing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<FnArg, Comma>,
sq_stack_pos: &mut i32,
Expand All @@ -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);
Expand Down
172 changes: 170 additions & 2 deletions src/high/squirrel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -243,6 +252,165 @@ impl<T: IntoSquirrelArgs> AsRef<SQHandle<SQClosure>> for SquirrelFn<T> {
}
}

/// [`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<T>(Box<T>);

impl<T: 'static> UserData<T> {
/// Creates a new [`UserData<T>`].
///
/// # 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<HttpClient> {
/// UserData::new(HttpClient)
/// }
/// ```
pub fn new(userdata: T) -> Self {
Self(userdata.into())
}

/// Creates a new [`UserData<T>`] from boxed data
pub fn from_boxed(userdata: Box<T>) -> Self {
Self(userdata)
}
}

impl<T: 'static> From<T> for UserData<T> {
fn from(value: T) -> Self {
Self(value.into())
}
}

impl<T: 'static> From<Box<T>> for UserData<T> {
fn from(value: Box<T>) -> Self {
Self(value)
}
}

impl<T> SQVMName for UserData<T> {
fn get_sqvm_name() -> String {
"userdata".to_string()
}
}

impl<T: 'static> PushToSquirrelVm for UserData<T> {
fn push_to_sqvm(self, sqvm: NonNull<HSquirrelVM>, 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::<T>().hash(&mut hasher);

(sqfunctions.sq_setuserdatatypeid)(sqvm.as_ptr(), -1, hasher.finish());
(sqfunctions.sq_setreleasehook)(sqvm.as_ptr(), -1, releasehook::<T>);
};

extern "C" fn releasehook<T>(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<HiddenMessage>) -> 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<HSquirrelVM>,
sqfunctions: &'static SquirrelFunctions,
stack_pos: i32,
) -> Self {
let mut out_ptr = ptr::null_mut();

let mut hasher = DefaultHasher::new();
TypeId::of::<T>().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<HSquirrelVM>,
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
Expand Down
21 changes: 20 additions & 1 deletion src/high/squirrel_traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<HSquirrelVM>,
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<HSquirrelVM>,
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! {
Expand Down

0 comments on commit f87cb37

Please sign in to comment.