From d8b203afec8bea2120bfba2fb3b8bc631db7c33c Mon Sep 17 00:00:00 2001 From: CoffeeKat <56778689+jupyterkat@users.noreply.github.com> Date: Wed, 29 Nov 2023 19:44:29 +1100 Subject: [PATCH] Adds some call by id helpers, add a macro for getting string id on init, get string changes (#8) * bump crate version * other changes * add usage * from_vec_with_nul --- crates/byondapi-rs-test/src/lib.rs | 10 +- crates/byondapi-rs/src/lib.rs | 17 ++++ crates/byondapi-rs/src/value/functions.rs | 109 +++++++++++----------- 3 files changed, 82 insertions(+), 54 deletions(-) diff --git a/crates/byondapi-rs-test/src/lib.rs b/crates/byondapi-rs-test/src/lib.rs index f82b05e..d6234bd 100644 --- a/crates/byondapi-rs-test/src/lib.rs +++ b/crates/byondapi-rs-test/src/lib.rs @@ -1,6 +1,7 @@ #![allow(clippy::missing_safety_doc)] use byondapi::{ + byond_string, map::{byond_block, byond_length, ByondXYZ}, parse_args, typecheck_trait::ByondTypeCheck, @@ -70,8 +71,7 @@ pub unsafe extern "C" fn test_proc_call( setup_panic_handler(); let args = parse_args(argc, argv); - // FIXME: Byond will change this in the future - let result = args[0].call("get name", &[]); + let result = args[0].call("get_name", &[]); match result { Ok(res) => res, @@ -88,6 +88,12 @@ pub unsafe extern "C" fn test_readwrite_var( let args = parse_args(argc, argv); let object = &args[0]; + object + .read_var_id(byond_string!("name")) + .unwrap() + .get_string() + .unwrap(); + match object.read_string("name") { Ok(res) => res.try_into().unwrap(), Err(e) => format!("{:#?}", e).try_into().unwrap(), diff --git a/crates/byondapi-rs/src/lib.rs b/crates/byondapi-rs/src/lib.rs index e62cd43..2860032 100644 --- a/crates/byondapi-rs/src/lib.rs +++ b/crates/byondapi-rs/src/lib.rs @@ -40,3 +40,20 @@ inventory::collect!(InitFunc); ///byondapi::inventory::submit! {InitFunc(func)} ///``` pub struct InitFunc(pub fn() -> ()); + +///This macro caches string ids and returns it instead of doing a stringid lookup everytime +///The macro will panic if the string doesn't already exist on byond init lib +///Example usage: +///``` +///byondapi::call_global_id(byond_string!("get_name"),&[]).unwrap() +///``` +#[macro_export] +macro_rules! byond_string { + ($s:literal) => {{ + thread_local! { + static STRING_ID: ::std::cell::OnceCell = ::std::cell::OnceCell::new(); + }; + STRING_ID + .with(|cell| *cell.get_or_init(|| ::byondapi::byond_string::str_id_of($s).unwrap())) + }}; +} diff --git a/crates/byondapi-rs/src/value/functions.rs b/crates/byondapi-rs/src/value/functions.rs index d9eae89..24387f6 100644 --- a/crates/byondapi-rs/src/value/functions.rs +++ b/crates/byondapi-rs/src/value/functions.rs @@ -32,59 +32,44 @@ impl ByondValue { /// Try to get a [`CString`] or fail if this isn't a string type pub fn get_cstring(&self) -> Result { + use std::cell::RefCell; if !self.is_str() { return Err(Error::NotAString); } - // add one for le null terminator - let len = self.builtin_length()?.get_number()? as u32 + 1; - let mut buff: Vec = Vec::with_capacity(len as usize); - let mut capacity = buff.capacity() as u32; - // Safety: buffer capacity is passed to byond, which makes sure it writes in-bound - unsafe { - map_byond_error!(byond().Byond_ToString( - &self.0, - buff.as_mut_ptr().cast(), - &mut capacity - ))? + + thread_local! { + static BUFFER: RefCell> = RefCell::new(Vec::with_capacity(1)); } - // Safety: buffer should be written to at this point, ignoring null terminator - unsafe { buff.set_len(len as usize - 1) }; - - CString::new(buff).map_err(|_| Error::NonUtf8String) - - // use std::cell::RefCell; - // thread_local! { - // static BUFFER: RefCell> = RefCell::new(Vec::with_capacity(1)); - // } - - // let bytes = BUFFER.with_borrow_mut(|buff| -> Result, Error> { - // let mut len = buff.capacity() as u32; - // // Safety: buffer capacity is passed to byond, which makes sure it writes in-bound - // let initial_res = - // unsafe { byond().Byond_ToString(&self.0, buff.as_mut_ptr().cast(), &mut len) }; - // match (initial_res, len) { - // (false, 1..) => { - // buff.reserve_exact(len as usize - buff.capacity()); - // // Safety: buffer capacity is passed to byond, which makes sure it writes in-bound - // unsafe { - // map_byond_error!(byond().Byond_ToString( - // &self.0, - // buff.as_mut_ptr().cast(), - // &mut len - // ))? - // }; - // // Safety: buffer should be written to at this point - // unsafe { buff.set_len(len as usize) }; - // Ok(std::mem::take(buff)) - // } - // (true, _) => { - // // Safety: buffer should be written to at this point - // unsafe { buff.set_len(len as usize) }; - // Ok(std::mem::take(buff)) - // } - // (false, 0) => Err(Error::get_last_byond_error()), - // } - // })?; + + let bytes = BUFFER.with_borrow_mut(|buff| -> Result, Error> { + let mut len = buff.capacity() as u32; + // Safety: buffer capacity is passed to byond, which makes sure it writes in-bound + let initial_res = + unsafe { byond().Byond_ToString(&self.0, buff.as_mut_ptr().cast(), &mut len) }; + match (initial_res, len) { + (false, 1..) => { + buff.reserve_exact(len as usize); + // Safety: buffer capacity is passed to byond, which makes sure it writes in-bound + unsafe { + map_byond_error!(byond().Byond_ToString( + &self.0, + buff.as_mut_ptr().cast(), + &mut len + ))? + }; + // Safety: buffer should be written to at this point + unsafe { buff.set_len(len as usize) }; + Ok(std::mem::take(buff)) + } + (true, _) => { + // Safety: buffer should be written to at this point + unsafe { buff.set_len(len as usize) }; + Ok(std::mem::take(buff)) + } + (false, 0) => Err(Error::get_last_byond_error()), + } + })?; + CString::from_vec_with_nul(bytes).map_err(|_| Error::NonUtf8String) } /// Try to get a [`String`] or fail if this isn't a string type or isn't utf8 @@ -210,6 +195,9 @@ impl ByondValue { impl ByondValue { /// Read a variable through the ref. Fails if this isn't a ref type, or the id is invalid. pub fn read_var_id(&self, name: u4c) -> Result { + if self.is_num() || self.is_str() || self.is_ptr() || self.is_null() || self.is_list() { + return Err(Error::NotReferencable); + } let mut new_value = ByondValue::new(); unsafe { map_byond_error!(byond().Byond_ReadVarByStrId(&self.0, name, &mut new_value.0))?; @@ -263,21 +251,36 @@ impl ByondValue { /// # Helpers impl ByondValue { - /// Reads a number through the ref. Fails if this isn't a ref type or this isn't a number. + /// Reads a number from a var. Fails if this isn't a ref type or this isn't a number. pub fn read_number>>(&self, name: T) -> Result { self.read_var(name)?.get_number() } - /// Reads a string through the ref. Fails if this isn't a ref type or this isn't a string. + /// Reads a string from a var. Fails if this isn't a ref type or this isn't a string. pub fn read_string>>(&self, name: T) -> Result { self.read_var(name)?.get_string() } - /// Reads a list through the ref. Fails if this isn't a ref type or this isn't a list. + /// Reads a list from a var. Fails if this isn't a ref type or this isn't a list. pub fn read_list>>(&self, name: T) -> Result, Error> { self.read_var(name)?.get_list() } + /// Reads a number from a var id. Fails if this isn't a ref type or this isn't a number. + pub fn read_number_id(&self, id: u32) -> Result { + self.read_var_id(id)?.get_number() + } + + /// Reads a string from a var id. Fails if this isn't a ref type or this isn't a string. + pub fn read_string_id(&self, id: u32) -> Result { + self.read_var_id(id)?.get_string() + } + + /// Reads a list from a var id. Fails if this isn't a ref type or this isn't a list. + pub fn read_list_id(&self, id: u32) -> Result, Error> { + self.read_var_id(id)?.get_list() + } + /// Iterates through the assoc values of the list if this value is a list, if the value isn't a list then it returns an error. /// Non assoc lists will have the second field of the tuple be null /// (key, value) for proper assoc lists @@ -292,6 +295,8 @@ impl ByondValue { ctr: 1, }) } + + /// Iterates through key values of the list if the list is an assoc list, if not, just iterates through values pub fn values(&self) -> Result + '_, Error> { if !self.is_list() { return Err(Error::NotAList);