Add new Buffer type for Luau.

Previously it was represented as `AnyUserData` which is not always convenient.
This commit is contained in:
Alex Orlenko
2024-10-01 15:21:19 +01:00
parent 4dddf3c18d
commit 529361fcbc
15 changed files with 248 additions and 91 deletions
+86
View File
@@ -0,0 +1,86 @@
#[cfg(feature = "serialize")]
use serde::ser::{Serialize, Serializer};
use crate::types::ValueRef;
/// A Luau buffer type.
///
/// See the buffer [documentation] for more information.
///
/// [documentation]: https://luau.org/library#buffer-library
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
#[derive(Clone, Debug, PartialEq)]
pub struct Buffer(pub(crate) ValueRef);
#[cfg_attr(not(feature = "luau"), allow(unused))]
impl Buffer {
/// Copies the buffer data into a new `Vec<u8>`.
pub fn to_vec(&self) -> Vec<u8> {
unsafe { self.as_slice().to_vec() }
}
/// Returns the length of the buffer.
pub fn len(&self) -> usize {
unsafe { self.as_slice().len() }
}
/// Returns `true` if the buffer is empty.
#[doc(hidden)]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Reads given number of bytes from the buffer at the given offset.
///
/// Offset is 0-based.
#[track_caller]
pub fn read_bytes<const N: usize>(&self, offset: usize) -> [u8; N] {
let data = unsafe { self.as_slice() };
let mut bytes = [0u8; N];
bytes.copy_from_slice(&data[offset..offset + N]);
bytes
}
/// Writes given bytes to the buffer at the given offset.
///
/// Offset is 0-based.
#[track_caller]
pub fn write_bytes(&self, offset: usize, bytes: &[u8]) {
let data = unsafe {
let (buf, size) = self.as_raw_parts();
std::slice::from_raw_parts_mut(buf, size)
};
data[offset..offset + bytes.len()].copy_from_slice(bytes);
}
pub(crate) unsafe fn as_slice(&self) -> &[u8] {
let (buf, size) = self.as_raw_parts();
std::slice::from_raw_parts(buf, size)
}
#[cfg(feature = "luau")]
unsafe fn as_raw_parts(&self) -> (*mut u8, usize) {
let lua = self.0.lua.lock();
let mut size = 0usize;
let buf = ffi::lua_tobuffer(lua.ref_thread(), self.0.index, &mut size);
mlua_assert!(!buf.is_null(), "invalid Luau buffer");
(buf as *mut u8, size)
}
#[cfg(not(feature = "luau"))]
unsafe fn as_raw_parts(&self) -> (*mut u8, usize) {
unreachable!()
}
}
#[cfg(feature = "serialize")]
impl Serialize for Buffer {
fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
serializer.serialize_bytes(unsafe { self.as_slice() })
}
}
#[cfg(feature = "luau")]
impl crate::types::LuaType for Buffer {
const TYPE_ID: std::os::raw::c_int = ffi::LUA_TBUFFER;
}
+38 -7
View File
@@ -367,6 +367,43 @@ impl FromLua for crate::types::Vector {
}
}
#[cfg(feature = "luau")]
impl IntoLua for crate::Buffer {
#[inline]
fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::Buffer(self))
}
}
#[cfg(feature = "luau")]
impl IntoLua for &crate::Buffer {
#[inline]
fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::Buffer(self.clone()))
}
#[inline]
unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> {
lua.push_ref(&self.0);
Ok(())
}
}
#[cfg(feature = "luau")]
impl FromLua for crate::Buffer {
#[inline]
fn from_lua(value: Value, _: &Lua) -> Result<Self> {
match value {
Value::Buffer(buf) => Ok(buf),
_ => Err(Error::FromLuaConversionError {
from: value.type_name(),
to: "buffer".to_string(),
message: None,
}),
}
}
}
impl IntoLua for StdString {
#[inline]
fn into_lua(self, lua: &Lua) -> Result<Value> {
@@ -515,13 +552,7 @@ impl FromLua for BString {
match value {
Value::String(s) => Ok((*s.as_bytes()).into()),
#[cfg(feature = "luau")]
Value::UserData(ud) if ud.1 == crate::types::SubtypeId::Buffer => unsafe {
let lua = ud.0.lua.lock();
let mut size = 0usize;
let buf = ffi::lua_tobuffer(lua.ref_thread(), ud.0.index, &mut size);
mlua_assert!(!buf.is_null(), "invalid Luau buffer");
Ok(slice::from_raw_parts(buf as *const u8, size).into())
},
Value::Buffer(buf) => unsafe { Ok(buf.as_slice().into()) },
_ => Ok((*lua
.coerce_string(value)?
.ok_or_else(|| Error::FromLuaConversionError {
+2 -1
View File
@@ -78,6 +78,7 @@
#[macro_use]
mod macros;
mod buffer;
mod chunk;
mod conversion;
mod error;
@@ -130,7 +131,7 @@ pub use crate::hook::HookTriggers;
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub use crate::{chunk::Compiler, function::CoverageInfo, types::Vector};
pub use crate::{buffer::Buffer, chunk::Compiler, function::CoverageInfo, types::Vector};
#[cfg(feature = "async")]
pub use crate::{thread::AsyncThread, traits::LuaNativeAsyncFn};
+2 -9
View File
@@ -145,14 +145,7 @@ impl<'de> serde::Deserializer<'de> for Deserializer {
serde_userdata(ud, |value| value.deserialize_any(visitor))
}
#[cfg(feature = "luau")]
Value::UserData(ud) if ud.1 == crate::types::SubtypeId::Buffer => unsafe {
let lua = ud.0.lua.lock();
let mut size = 0usize;
let buf = ffi::lua_tobuffer(lua.ref_thread(), ud.0.index, &mut size);
mlua_assert!(!buf.is_null(), "invalid Luau buffer");
let buf = std::slice::from_raw_parts(buf as *const u8, size);
visitor.visit_bytes(buf)
},
Value::Buffer(buf) => visitor.visit_bytes(unsafe { buf.as_slice() }),
Value::Function(_)
| Value::Thread(_)
| Value::UserData(_)
@@ -463,7 +456,7 @@ impl<'a> MapPairs<'a> {
pub(crate) fn new(t: &'a Table, sort_keys: bool) -> Result<Self> {
if sort_keys {
let mut pairs = t.pairs::<Value, Value>().collect::<Result<Vec<_>>>()?;
pairs.sort_by(|(a, _), (b, _)| b.cmp(a)); // reverse order as we pop values from the end
pairs.sort_by(|(a, _), (b, _)| b.sort_cmp(a)); // reverse order as we pop values from the end
Ok(MapPairs::Vec(pairs))
} else {
Ok(MapPairs::Iter(t.pairs::<Value, Value>()))
+6 -7
View File
@@ -31,7 +31,7 @@ use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil
use crate::hook::HookTriggers;
#[cfg(any(feature = "luau", doc))]
use crate::chunk::Compiler;
use crate::{buffer::Buffer, chunk::Compiler};
#[cfg(feature = "async")]
use {
@@ -996,22 +996,21 @@ impl Lua {
/// Requires `feature = "luau"`
///
/// [buffer]: https://luau-lang.org/library#buffer-library
#[cfg(feature = "luau")]
pub fn create_buffer(&self, buf: impl AsRef<[u8]>) -> Result<AnyUserData> {
use crate::types::SubtypeId;
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
#[cfg(any(feature = "luau", doc))]
pub fn create_buffer(&self, buf: impl AsRef<[u8]>) -> Result<Buffer> {
let lua = self.lock();
let state = lua.state();
unsafe {
if lua.unlikely_memory_error() {
crate::util::push_buffer(lua.ref_thread(), buf.as_ref(), false)?;
return Ok(AnyUserData(lua.pop_ref_thread(), SubtypeId::Buffer));
return Ok(Buffer(lua.pop_ref_thread()));
}
let _sg = StackGuard::new(state);
check_stack(state, 4)?;
crate::util::push_buffer(state, buf.as_ref(), true)?;
Ok(AnyUserData(lua.pop_ref(), SubtypeId::Buffer))
Ok(Buffer(lua.pop_ref()))
}
}
+3 -2
View File
@@ -551,6 +551,8 @@ impl RawLua {
Value::Function(f) => self.push_ref(&f.0),
Value::Thread(t) => self.push_ref(&t.0),
Value::UserData(ud) => self.push_ref(&ud.0),
#[cfg(feature = "luau")]
Value::Buffer(buf) => self.push_ref(&buf.0),
Value::Error(err) => {
let protect = !self.unlikely_memory_error();
push_internal_userdata(state, WrappedFailure::Error(*err.clone()), protect)?;
@@ -652,9 +654,8 @@ impl RawLua {
#[cfg(feature = "luau")]
ffi::LUA_TBUFFER => {
// Buffer is represented as a userdata type
ffi::lua_xpush(state, self.ref_thread(), idx);
Value::UserData(AnyUserData(self.pop_ref_thread(), SubtypeId::Buffer))
Value::Buffer(crate::Buffer(self.pop_ref_thread()))
}
#[cfg(feature = "luajit")]
+1 -1
View File
@@ -785,7 +785,7 @@ impl Table {
// Collect key/value pairs into a vector so we can sort them
let mut pairs = self.pairs::<Value, Value>().flatten().collect::<Vec<_>>();
// Sort keys
pairs.sort_by(|(a, _), (b, _)| a.cmp(b));
pairs.sort_by(|(a, _), (b, _)| a.sort_cmp(b));
if pairs.is_empty() {
return write!(fmt, "{{}}");
}
-2
View File
@@ -32,8 +32,6 @@ pub type Number = ffi::lua_Number;
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub(crate) enum SubtypeId {
None,
#[cfg(feature = "luau")]
Buffer,
#[cfg(feature = "luajit")]
CData,
}
+1 -1
View File
@@ -10,7 +10,7 @@ use super::LuaType;
/// By default vectors are 3-dimensional, but can be 4-dimensional
/// if the `luau-vector4` feature is enabled.
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
#[derive(Debug, Default, Clone, Copy, PartialEq)]
#[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd)]
pub struct Vector(pub(crate) [f32; Self::SIZE]);
impl fmt::Display for Vector {
-15
View File
@@ -937,8 +937,6 @@ impl AnyUserData {
pub(crate) fn type_name(&self) -> Result<Option<StdString>> {
match self.1 {
SubtypeId::None => {}
#[cfg(feature = "luau")]
SubtypeId::Buffer => return Ok(Some("buffer".to_owned())),
#[cfg(feature = "luajit")]
SubtypeId::CData => return Ok(Some("cdata".to_owned())),
}
@@ -1111,19 +1109,6 @@ impl Serialize for AnyUserData {
S: Serializer,
{
let lua = self.0.lua.lock();
// Special case for Luau buffer type
#[cfg(feature = "luau")]
if self.1 == SubtypeId::Buffer {
let buf = unsafe {
let mut size = 0usize;
let buf = ffi::lua_tobuffer(lua.ref_thread(), self.0.index, &mut size);
mlua_assert!(!buf.is_null(), "invalid Luau buffer");
std::slice::from_raw_parts(buf as *const u8, size)
};
return serializer.serialize_bytes(buf);
}
unsafe {
let _ = lua
.get_userdata_ref_type_id(&self.0)
+41 -10
View File
@@ -48,7 +48,7 @@ pub enum Value {
/// A Luau vector.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
Vector(crate::types::Vector),
Vector(crate::Vector),
/// An interned string, managed by Lua.
///
/// Unlike Rust strings, Lua strings may not be valid UTF-8.
@@ -60,8 +60,13 @@ pub enum Value {
/// Reference to a Lua thread (or coroutine).
Thread(Thread),
/// Reference to a userdata object that holds a custom type which implements `UserData`.
///
/// Special builtin userdata types will be represented as other `Value` variants.
UserData(AnyUserData),
/// A Luau buffer.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
Buffer(crate::Buffer),
/// `Error` is a special builtin userdata type. When received from Lua it is implicitly cloned.
Error(Box<Error>),
}
@@ -89,10 +94,10 @@ impl Value {
Value::Function(_) => "function",
Value::Thread(_) => "thread",
Value::UserData(AnyUserData(_, SubtypeId::None)) => "userdata",
#[cfg(feature = "luau")]
Value::UserData(AnyUserData(_, SubtypeId::Buffer)) => "buffer",
#[cfg(feature = "luajit")]
Value::UserData(AnyUserData(_, SubtypeId::CData)) => "cdata",
#[cfg(feature = "luau")]
Value::Buffer(_) => "buffer",
Value::Error(_) => "error",
}
}
@@ -131,6 +136,8 @@ impl Value {
| Value::Function(Function(r))
| Value::Thread(Thread(r, ..))
| Value::UserData(AnyUserData(r, ..)) => r.to_pointer(),
#[cfg(feature = "luau")]
Value::Buffer(crate::Buffer(r)) => r.to_pointer(),
_ => ptr::null(),
}
}
@@ -165,6 +172,8 @@ impl Value {
})?;
Ok(String(lua.pop_ref()).to_str()?.to_string())
},
#[cfg(feature = "luau")]
Value::Buffer(buf) => StdString::from_utf8(buf.to_vec()).map_err(Error::external),
Value::Error(err) => Ok(err.to_string()),
}
}
@@ -416,15 +425,25 @@ impl Value {
}
}
/// Returns `true` if the value is a Buffer wrapped in [`AnyUserData`].
/// Cast the value to a `Buffer`.
///
/// If the value is `Buffer`, returns it or `None` otherwise.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
#[inline]
pub fn as_buffer(&self) -> Option<&crate::Buffer> {
match self {
Value::Buffer(b) => Some(b),
_ => None,
}
}
/// Returns `true` if the value is a `Buffer`.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
#[doc(hidden)]
#[inline]
pub fn is_buffer(&self) -> bool {
self.as_userdata()
.map(|ud| ud.1 == SubtypeId::Buffer)
.unwrap_or_default()
self.as_buffer().is_some()
}
/// Returns `true` if the value is a CData wrapped in [`AnyUserData`].
@@ -450,7 +469,7 @@ impl Value {
// Compares two values.
// Used to sort values for Debug printing.
pub(crate) fn cmp(&self, other: &Self) -> Ordering {
pub(crate) fn sort_cmp(&self, other: &Self) -> Ordering {
fn cmp_num(a: Number, b: Number) -> Ordering {
match (a, b) {
_ if a < b => Ordering::Less,
@@ -479,11 +498,14 @@ impl Value {
(&Value::Number(a), &Value::Number(b)) => cmp_num(a, b),
(Value::Integer(_) | Value::Number(_), _) => Ordering::Less,
(_, Value::Integer(_) | Value::Number(_)) => Ordering::Greater,
// Vector (Luau)
#[cfg(feature = "luau")]
(Value::Vector(a), Value::Vector(b)) => a.partial_cmp(b).unwrap_or(Ordering::Equal),
// String
(Value::String(a), Value::String(b)) => a.as_bytes().cmp(&b.as_bytes()),
(Value::String(_), _) => Ordering::Less,
(_, Value::String(_)) => Ordering::Greater,
// Other variants can be randomly ordered
// Other variants can be ordered by their pointer
(a, b) => a.to_pointer().cmp(&b.to_pointer()),
}
}
@@ -520,6 +542,8 @@ impl Value {
.unwrap_or_else(|| format!("userdata: {:?}", u.to_pointer()));
write!(fmt, "{s}")
}
#[cfg(feature = "luau")]
buf @ Value::Buffer(_) => write!(fmt, "buffer: {:?}", buf.to_pointer()),
Value::Error(e) if recursive => write!(fmt, "{e:?}"),
Value::Error(_) => write!(fmt, "error"),
}
@@ -531,6 +555,7 @@ impl fmt::Debug for Value {
if fmt.alternate() {
return self.fmt_pretty(fmt, true, 0, &mut HashSet::new());
}
match self {
Value::Nil => write!(fmt, "Nil"),
Value::Boolean(b) => write!(fmt, "Boolean({b})"),
@@ -544,6 +569,8 @@ impl fmt::Debug for Value {
Value::Function(f) => write!(fmt, "{f:?}"),
Value::Thread(t) => write!(fmt, "{t:?}"),
Value::UserData(ud) => write!(fmt, "{ud:?}"),
#[cfg(feature = "luau")]
Value::Buffer(buf) => write!(fmt, "{buf:?}"),
Value::Error(e) => write!(fmt, "Error({e:?})"),
}
}
@@ -566,6 +593,8 @@ impl PartialEq for Value {
(Value::Function(a), Value::Function(b)) => a == b,
(Value::Thread(a), Value::Thread(b)) => a == b,
(Value::UserData(a), Value::UserData(b)) => a == b,
#[cfg(feature = "luau")]
(Value::Buffer(a), Value::Buffer(b)) => a == b,
_ => false,
}
}
@@ -674,6 +703,8 @@ impl<'a> Serialize for SerializableValue<'a> {
Value::UserData(ud) if ud.is_serializable() || self.options.deny_unsupported_types => {
ud.serialize(serializer)
}
#[cfg(feature = "luau")]
Value::Buffer(buf) => buf.serialize(serializer),
Value::Function(_)
| Value::Thread(_)
| Value::UserData(_)
+56
View File
@@ -0,0 +1,56 @@
#![cfg(feature = "luau")]
use mlua::{Lua, Result, Value};
#[test]
fn test_buffer() -> Result<()> {
let lua = Lua::new();
let buf1 = lua
.load(
r#"
local buf = buffer.fromstring("hello")
assert(buffer.len(buf) == 5)
return buf
"#,
)
.eval::<Value>()?;
assert!(buf1.is_buffer());
assert_eq!(buf1.type_name(), "buffer");
let buf2 = lua.load("buffer.fromstring('hello')").eval::<Value>()?;
assert_ne!(buf1, buf2);
// Check that we can pass buffer type to Lua
let buf1 = buf1.as_buffer().unwrap();
let func = lua.create_function(|_, buf: Value| return buf.to_string())?;
assert_eq!(func.call::<String>(buf1)?, "hello");
// Check buffer methods
assert_eq!(buf1.len(), 5);
assert_eq!(buf1.to_vec(), b"hello");
assert_eq!(buf1.read_bytes::<3>(1), [b'e', b'l', b'l']);
buf1.write_bytes(1, b"i");
assert_eq!(buf1.to_vec(), b"hillo");
let buf3 = lua.create_buffer(b"")?;
assert!(buf3.is_empty());
Ok(())
}
#[test]
#[should_panic(expected = "range end index 14 out of range for slice of length 13")]
fn test_buffer_out_of_bounds_read() {
let lua = Lua::new();
let buf = lua.create_buffer(b"hello, world!").unwrap();
_ = buf.read_bytes::<1>(13);
}
#[test]
#[should_panic(expected = "range end index 16 out of range for slice of length 13")]
fn test_buffer_out_of_bounds_write() {
let lua = Lua::new();
let buf = lua.create_buffer(b"hello, world!").unwrap();
buf.write_bytes(14, b"!!");
}
+2 -2
View File
@@ -384,8 +384,8 @@ fn test_bstring_from_lua() -> Result<()> {
fn test_bstring_from_lua_buffer() -> Result<()> {
let lua = Lua::new();
let b = lua.create_buffer("hello, world")?;
let bstr = lua.unpack::<BString>(Value::UserData(b))?;
let buf = lua.create_buffer("hello, world")?;
let bstr = lua.convert::<BString>(buf)?;
assert_eq!(bstr, "hello, world");
// Test from stack
-26
View File
@@ -465,32 +465,6 @@ fn test_coverage() -> Result<()> {
Ok(())
}
#[test]
fn test_buffer() -> Result<()> {
let lua = Lua::new();
let buf1 = lua
.load(
r#"
local buf = buffer.fromstring("hello")
assert(buffer.len(buf) == 5)
return buf
"#,
)
.eval::<Value>()?;
assert!(buf1.is_userdata() && buf1.is_buffer());
assert_eq!(buf1.type_name(), "buffer");
let buf2 = lua.load("buffer.fromstring('hello')").eval::<Value>()?;
assert_ne!(buf1, buf2);
// Check that we can pass buffer type to Lua
let func = lua.create_function(|_, buf: Value| return buf.to_string())?;
assert!(func.call::<String>(buf1)?.starts_with("buffer:"));
Ok(())
}
#[test]
fn test_fflags() {
// We cannot really on any particular feature flag to be present
+10 -8
View File
@@ -717,27 +717,29 @@ fn test_arbitrary_precision() {
#[cfg(feature = "luau")]
#[test]
fn test_buffer_serialize() {
fn test_buffer_serialize() -> LuaResult<()> {
let lua = Lua::new();
let buf = lua.create_buffer(&[1, 2, 3, 4]).unwrap();
let buf = lua.create_buffer(&[1, 2, 3, 4])?;
let val = serde_value::to_value(&buf).unwrap();
assert_eq!(val, serde_value::Value::Bytes(vec![1, 2, 3, 4]));
// Try empty buffer
let buf = lua.create_buffer(&[]).unwrap();
let buf = lua.create_buffer(&[])?;
let val = serde_value::to_value(&buf).unwrap();
assert_eq!(val, serde_value::Value::Bytes(vec![]));
Ok(())
}
#[cfg(feature = "luau")]
#[test]
fn test_buffer_from_value() {
fn test_buffer_from_value() -> LuaResult<()> {
let lua = Lua::new();
let buf = lua.create_buffer(&[1, 2, 3, 4]).unwrap();
let val = lua
.from_value::<serde_value::Value>(Value::UserData(buf))
.unwrap();
let buf = lua.create_buffer(&[1, 2, 3, 4])?;
let val = lua.from_value::<serde_value::Value>(Value::Buffer(buf)).unwrap();
assert_eq!(val, serde_value::Value::Bytes(vec![1, 2, 3, 4]));
Ok(())
}