diff --git a/mlua-sys/src/lua55/lua.rs b/mlua-sys/src/lua55/lua.rs index 60bd4ec..dd6067d 100644 --- a/mlua-sys/src/lua55/lua.rs +++ b/mlua-sys/src/lua55/lua.rs @@ -204,7 +204,7 @@ unsafe extern "C-unwind" { L: *mut lua_State, s: *const c_char, len: usize, - falloc: lua_Alloc, + falloc: Option, ud: *mut c_void, ) -> *const c_char; pub fn lua_pushstring(L: *mut lua_State, s: *const c_char) -> *const c_char; diff --git a/src/conversion.rs b/src/conversion.rs index f1f6ddc..b02e861 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -500,11 +500,21 @@ impl FromLua for crate::Buffer { impl IntoLua for StdString { #[inline] fn into_lua(self, lua: &Lua) -> Result { + #[cfg(feature = "lua55")] + if true { + return Ok(Value::String(lua.create_external_string(self)?)); + } + Ok(Value::String(lua.create_string(self)?)) } #[inline] unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { + #[cfg(feature = "lua55")] + if lua.unlikely_memory_error() { + return crate::util::push_external_string(lua.state(), self.into(), false); + } + push_bytes_into_stack(self, lua) } } @@ -591,6 +601,11 @@ impl FromLua for Box { impl IntoLua for CString { #[inline] fn into_lua(self, lua: &Lua) -> Result { + #[cfg(feature = "lua55")] + if true { + return Ok(Value::String(lua.create_external_string(self)?)); + } + Ok(Value::String(lua.create_string(self.as_bytes())?)) } } @@ -635,6 +650,11 @@ impl IntoLua for Cow<'_, CStr> { impl IntoLua for BString { #[inline] fn into_lua(self, lua: &Lua) -> Result { + #[cfg(feature = "lua55")] + if true { + return Ok(Value::String(lua.create_external_string(self)?)); + } + Ok(Value::String(lua.create_string(self)?)) } } diff --git a/src/state.rs b/src/state.rs index 5698c30..0808ad2 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1219,6 +1219,17 @@ impl Lua { unsafe { self.lock().create_string(s.as_ref()) } } + /// Creates and returns an external Lua string. + /// + /// External string is a string where the memory is managed by Rust code, and Lua only holds a + /// reference to it. This can be used to avoid copying large strings into Lua memory. + #[cfg(feature = "lua55")] + #[cfg_attr(docsrs, doc(cfg(feature = "lua55")))] + #[inline] + pub fn create_external_string(&self, s: impl Into>) -> Result { + unsafe { self.lock().create_external_string(s.into()) } + } + /// Creates and returns a Luau [buffer] object from a byte slice of data. /// /// [buffer]: https://luau.org/library#buffer-library diff --git a/src/state/raw.rs b/src/state/raw.rs index 416adea..a24fec3 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -529,6 +529,23 @@ impl RawLua { Ok(String(self.pop_ref())) } + /// Creates an external string, that is, a string that uses memory not managed by Lua. + /// + /// Modifies the input data to add `\0` terminator. + #[cfg(feature = "lua55")] + pub(crate) unsafe fn create_external_string(&self, bytes: Vec) -> Result { + let state = self.state(); + if self.unlikely_memory_error() { + crate::util::push_external_string(state, bytes, false)?; + return Ok(String(self.pop_ref())); + } + + let _sg = StackGuard::new(state); + check_stack(state, 3)?; + crate::util::push_external_string(state, bytes, true)?; + Ok(String(self.pop_ref())) + } + #[cfg(feature = "luau")] pub(crate) unsafe fn create_buffer_with_capacity(&self, size: usize) -> Result<(*mut u8, crate::Buffer)> { let state = self.state(); diff --git a/src/util/mod.rs b/src/util/mod.rs index 9f93f55..8905708 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -99,6 +99,38 @@ pub(crate) unsafe fn push_string(state: *mut ffi::lua_State, s: &[u8], protect: } } +// Uses 3 (or 1 if unprotected) stack spaces, does not call checkstack. +#[cfg(feature = "lua55")] +pub(crate) unsafe fn push_external_string( + state: *mut ffi::lua_State, + mut bytes: Vec, + protect: bool, +) -> Result<()> { + bytes.push(0); + let s_len = bytes.len() - 1; // exclude null terminator + let s_ptr = bytes.as_ptr() as *const c_char; + let bytes_ud = Box::into_raw(Box::new(bytes)); + + unsafe extern "C" fn dealloc(ud: *mut c_void, _: *mut c_void, _: usize, _: usize) -> *mut c_void { + drop(Box::from_raw(ud as *mut Vec)); + ptr::null_mut() + } + + if protect { + let res = protect_lua!(state, 0, 1, move |state| { + ffi::lua_pushexternalstring(state, s_ptr, s_len, Some(dealloc), bytes_ud as *mut _); + }); + if res.is_err() { + // Deallocate on error + drop(Box::from_raw(bytes_ud)); + return res; + } + } else { + ffi::lua_pushexternalstring(state, s_ptr, s_len, Some(dealloc), bytes_ud as *mut _); + } + Ok(()) +} + // Uses 3 stack spaces (when protect), does not call checkstack. #[cfg(feature = "luau")] #[inline(always)]