luaur_rt/buffer.rs
1//! The [`Buffer`] handle (Luau `buffer` type). Mirrors `mlua::Buffer`.
2//!
3//! A buffer is a fixed-size, mutable, GC-managed byte array — Luau's answer to
4//! a `Vec<u8>` / `ArrayBuffer`. Like the other reference-typed handles
5//! ([`Table`](crate::Table), [`Function`](crate::Function)) it holds a registry
6//! reference ([`LuaRef`]) that keeps the underlying object alive and lets us
7//! re-push it onto the stack on demand.
8//!
9//! The data lives in VM-managed memory; [`Buffer::as_slice`] / [`as_slice_mut`]
10//! borrow the raw bytes directly through luaur's `lua_tobuffer`, so reads and
11//! writes go straight to the buffer with no copy.
12
13use std::io;
14
15use crate::error::Result;
16use crate::state::{Lua, LuaRef};
17use crate::sync::{NotSync, XRc, NOT_SYNC};
18use crate::sys::*;
19
20/// A Luau buffer type.
21///
22/// See the buffer [documentation] for more information.
23///
24/// Mirrors `mlua::Buffer`. Holds a registry reference keeping the buffer alive.
25///
26/// Under the `send` feature it is `Send` but never `Sync` — see
27/// [`crate::sync::NotSync`].
28///
29/// [documentation]: https://luau.org/library#buffer-library
30#[derive(Clone)]
31pub struct Buffer {
32 pub(crate) reference: XRc<LuaRef>,
33 pub(crate) _not_sync: NotSync,
34}
35
36impl Buffer {
37 pub(crate) fn from_ref(reference: LuaRef) -> Buffer {
38 Buffer {
39 reference: XRc::new(reference),
40 _not_sync: NOT_SYNC,
41 }
42 }
43
44 /// Push this buffer onto the owning state's stack.
45 pub(crate) unsafe fn push_to_stack(&self) {
46 self.reference.push();
47 }
48
49 /// The owning [`Lua`].
50 pub(crate) fn lua(&self) -> Lua {
51 self.reference.lua()
52 }
53
54 /// Copies the buffer data into a new `Vec<u8>`.
55 ///
56 /// Mirrors `mlua::Buffer::to_vec`.
57 pub fn to_vec(&self) -> Vec<u8> {
58 self.as_slice().to_vec()
59 }
60
61 /// Returns the length of the buffer.
62 ///
63 /// Mirrors `mlua::Buffer::len`.
64 pub fn len(&self) -> usize {
65 self.as_slice().len()
66 }
67
68 /// Returns `true` if the buffer is empty.
69 ///
70 /// Mirrors `mlua::Buffer::is_empty`.
71 pub fn is_empty(&self) -> bool {
72 self.len() == 0
73 }
74
75 /// Reads given number of bytes from the buffer at the given offset.
76 ///
77 /// Offset is 0-based.
78 ///
79 /// Mirrors `mlua::Buffer::read_bytes`.
80 #[track_caller]
81 pub fn read_bytes<const N: usize>(&self, offset: usize) -> [u8; N] {
82 let data = self.as_slice();
83 let mut bytes = [0u8; N];
84 bytes.copy_from_slice(&data[offset..offset + N]);
85 bytes
86 }
87
88 /// Writes given bytes to the buffer at the given offset.
89 ///
90 /// Offset is 0-based.
91 ///
92 /// Mirrors `mlua::Buffer::write_bytes`.
93 #[track_caller]
94 pub fn write_bytes(&self, offset: usize, bytes: &[u8]) {
95 let data = self.as_slice_mut();
96 data[offset..offset + bytes.len()].copy_from_slice(bytes);
97 }
98
99 /// Returns an adaptor implementing [`io::Read`], [`io::Write`] and
100 /// [`io::Seek`] over the buffer.
101 ///
102 /// Buffer operations are infallible, none of the read/write functions will
103 /// return an `Err`.
104 ///
105 /// Mirrors `mlua::Buffer::cursor`.
106 pub fn cursor(self) -> impl io::Read + io::Write + io::Seek {
107 BufferCursor(self, 0)
108 }
109
110 /// A raw pointer identifying this buffer (for identity comparison).
111 /// Mirrors `Value::Buffer(_).to_pointer()`.
112 pub(crate) fn to_pointer(&self) -> *const std::ffi::c_void {
113 let state = self.reference.state();
114 unsafe {
115 self.reference.push();
116 let p = lua_topointer(state, -1);
117 lua_pop(state, 1);
118 p
119 }
120 }
121
122 /// Borrow the buffer's bytes directly (no copy).
123 pub(crate) fn as_slice(&self) -> &[u8] {
124 unsafe {
125 let (buf, size) = self.as_raw_parts();
126 std::slice::from_raw_parts(buf, size)
127 }
128 }
129
130 /// Mutably borrow the buffer's bytes directly (no copy).
131 #[allow(clippy::mut_from_ref)]
132 pub(crate) fn as_slice_mut(&self) -> &mut [u8] {
133 unsafe {
134 let (buf, size) = self.as_raw_parts();
135 std::slice::from_raw_parts_mut(buf, size)
136 }
137 }
138
139 /// The raw `(ptr, len)` of the underlying buffer object via luaur's
140 /// `lua_tobuffer`. Pushes the buffer, reads the parts, then pops — the
141 /// pointer remains valid because the registry ref keeps the object alive.
142 unsafe fn as_raw_parts(&self) -> (*mut u8, usize) {
143 let state = self.reference.state();
144 unsafe {
145 self.reference.push();
146 let mut size = 0usize;
147 let buf = lua_tobuffer(state, -1, &mut size);
148 lua_pop(state, 1);
149 assert!(!buf.is_null(), "invalid Luau buffer");
150 (buf as *mut u8, size)
151 }
152 }
153}
154
155impl std::fmt::Debug for Buffer {
156 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157 // Mirror mlua: a buffer renders as its byte contents (a byte slice).
158 write!(f, "Buffer({:?})", self.as_slice())
159 }
160}
161
162impl PartialEq for Buffer {
163 fn eq(&self, other: &Self) -> bool {
164 // Reference (pointer) identity, matching mlua: two handles are equal iff
165 // they point at the *same* buffer object (NOT byte-wise content).
166 self.to_pointer() == other.to_pointer()
167 }
168}
169
170/// Cursor adapter returned by [`Buffer::cursor`]. The `usize` is the current
171/// 0-based offset into the buffer. Mirrors mlua's `BufferCursor`.
172struct BufferCursor(Buffer, usize);
173
174impl io::Read for BufferCursor {
175 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
176 let data = self.0.as_slice();
177 if self.1 == data.len() {
178 return Ok(0);
179 }
180 let len = buf.len().min(data.len() - self.1);
181 buf[..len].copy_from_slice(&data[self.1..self.1 + len]);
182 self.1 += len;
183 Ok(len)
184 }
185}
186
187impl io::Write for BufferCursor {
188 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
189 let data = self.0.as_slice_mut();
190 if self.1 == data.len() {
191 return Ok(0);
192 }
193 let len = buf.len().min(data.len() - self.1);
194 data[self.1..self.1 + len].copy_from_slice(&buf[..len]);
195 self.1 += len;
196 Ok(len)
197 }
198
199 fn flush(&mut self) -> io::Result<()> {
200 Ok(())
201 }
202}
203
204impl io::Seek for BufferCursor {
205 fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
206 let data = self.0.as_slice();
207 let new_offset = match pos {
208 io::SeekFrom::Start(offset) => offset as i64,
209 io::SeekFrom::End(offset) => data.len() as i64 + offset,
210 io::SeekFrom::Current(offset) => self.1 as i64 + offset,
211 };
212 if new_offset < 0 {
213 return Err(io::Error::new(
214 io::ErrorKind::InvalidInput,
215 "invalid seek to a negative position",
216 ));
217 }
218 if new_offset as usize > data.len() {
219 return Err(io::Error::new(
220 io::ErrorKind::InvalidInput,
221 "invalid seek to a position beyond the end of the buffer",
222 ));
223 }
224 self.1 = new_offset as usize;
225 Ok(self.1 as u64)
226 }
227}
228
229// ---------------------------------------------------------------------------
230// Buffer creation
231//
232// `lua_newbuffer` allocates a GC buffer and pushes it. When `size` exceeds the
233// VM's `MAX_BUFFER_SIZE` (1GB) the underlying `luaM_toobig` *raises* a Lua
234// error (longjmp) rather than returning. Unwinding that across Rust frames is
235// UB, so we run `lua_newbuffer` inside `lua_pcall` via a small C trampoline:
236// the requested size is passed as a number argument, and a raising allocation
237// is reported as an ordinary non-zero status with the error on the stack.
238// ---------------------------------------------------------------------------
239
240/// C trampoline: stack is `[size]` (a number). Allocates a buffer of that many
241/// bytes via `lua_newbuffer`, leaving the buffer object on top.
242unsafe fn c_newbuffer(state: *mut lua_State) -> c_int {
243 unsafe {
244 let size = lua_tonumberx(state, 1, core::ptr::null_mut()) as usize;
245 lua_settop(state, 0);
246 lua_newbuffer(state, size);
247 1
248 }
249}
250
251/// Create a buffer of `size` zero-initialized bytes, catching an over-limit
252/// allocation as an `Err` rather than letting the VM longjmp.
253pub(crate) fn create_buffer_with_capacity(lua: &Lua, size: usize) -> Result<Buffer> {
254 let state = lua.state();
255 unsafe {
256 lua_pushcclosurek(
257 state,
258 Some(c_newbuffer),
259 c"luaur-rt-newbuffer".as_ptr(),
260 0,
261 None,
262 );
263 lua_pushnumber(state, size as f64);
264 let status = lua_pcall(state, 1, 1, 0);
265 if status != 0 {
266 return Err(lua.pop_error(status));
267 }
268 Ok(Buffer::from_ref(lua.pop_ref()))
269 }
270}