Skip to content

Commit 774a63b

Browse files
committed
Add Buffer::cursor() method
This can be useful for providing access to buffers through core IO traits.
1 parent c481c87 commit 774a63b

File tree

2 files changed

+134
-4
lines changed

2 files changed

+134
-4
lines changed

src/buffer.rs

Lines changed: 79 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::io;
2+
13
#[cfg(feature = "serde")]
24
use serde::ser::{Serialize, Serializer};
35

@@ -50,20 +52,33 @@ impl Buffer {
5052
#[track_caller]
5153
pub fn write_bytes(&self, offset: usize, bytes: &[u8]) {
5254
let lua = self.0.lua.lock();
53-
let data = unsafe {
54-
let (buf, size) = self.as_raw_parts(&lua);
55-
std::slice::from_raw_parts_mut(buf, size)
56-
};
55+
let data = self.as_slice_mut(&lua);
5756
data[offset..offset + bytes.len()].copy_from_slice(bytes);
5857
}
5958

59+
/// Returns an adaptor implementing [`io::Read`], [`io::Write`] and [`io::Seek`] over the
60+
/// buffer.
61+
///
62+
/// Buffer operations are infallible, none of the read/write functions will return a Err.
63+
pub fn cursor(self) -> impl io::Read + io::Write + io::Seek {
64+
BufferCursor(self, 0)
65+
}
66+
6067
pub(crate) fn as_slice(&self, lua: &RawLua) -> &[u8] {
6168
unsafe {
6269
let (buf, size) = self.as_raw_parts(lua);
6370
std::slice::from_raw_parts(buf, size)
6471
}
6572
}
6673

74+
#[allow(clippy::mut_from_ref)]
75+
fn as_slice_mut(&self, lua: &RawLua) -> &mut [u8] {
76+
unsafe {
77+
let (buf, size) = self.as_raw_parts(lua);
78+
std::slice::from_raw_parts_mut(buf, size)
79+
}
80+
}
81+
6782
#[cfg(feature = "luau")]
6883
unsafe fn as_raw_parts(&self, lua: &RawLua) -> (*mut u8, usize) {
6984
let mut size = 0usize;
@@ -78,6 +93,66 @@ impl Buffer {
7893
}
7994
}
8095

96+
struct BufferCursor(Buffer, usize);
97+
98+
impl io::Read for BufferCursor {
99+
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
100+
let lua = self.0 .0.lua.lock();
101+
let data = self.0.as_slice(&lua);
102+
if self.1 == data.len() {
103+
return Ok(0);
104+
}
105+
let len = buf.len().min(data.len() - self.1);
106+
buf[..len].copy_from_slice(&data[self.1..self.1 + len]);
107+
self.1 += len;
108+
Ok(len)
109+
}
110+
}
111+
112+
impl io::Write for BufferCursor {
113+
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
114+
let lua = self.0 .0.lua.lock();
115+
let data = self.0.as_slice_mut(&lua);
116+
if self.1 == data.len() {
117+
return Ok(0);
118+
}
119+
let len = buf.len().min(data.len() - self.1);
120+
data[self.1..self.1 + len].copy_from_slice(&buf[..len]);
121+
self.1 += len;
122+
Ok(len)
123+
}
124+
125+
fn flush(&mut self) -> io::Result<()> {
126+
Ok(())
127+
}
128+
}
129+
130+
impl io::Seek for BufferCursor {
131+
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
132+
let lua = self.0 .0.lua.lock();
133+
let data = self.0.as_slice(&lua);
134+
let new_offset = match pos {
135+
io::SeekFrom::Start(offset) => offset as i64,
136+
io::SeekFrom::End(offset) => data.len() as i64 + offset,
137+
io::SeekFrom::Current(offset) => self.1 as i64 + offset,
138+
};
139+
if new_offset < 0 {
140+
return Err(io::Error::new(
141+
io::ErrorKind::InvalidInput,
142+
"invalid seek to a negative position",
143+
));
144+
}
145+
if new_offset as usize > data.len() {
146+
return Err(io::Error::new(
147+
io::ErrorKind::InvalidInput,
148+
"invalid seek to a position beyond the end of the buffer",
149+
));
150+
}
151+
self.1 = new_offset as usize;
152+
Ok(self.1 as u64)
153+
}
154+
}
155+
81156
#[cfg(feature = "serde")]
82157
impl Serialize for Buffer {
83158
fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {

tests/buffer.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#![cfg(feature = "luau")]
22

3+
use std::io::{Read, Seek, SeekFrom, Write};
4+
35
use mlua::{Lua, Result, Value};
46

57
#[test]
@@ -66,3 +68,56 @@ fn create_large_buffer() {
6668
let buf = lua.create_buffer_with_capacity(1024 * 1024).unwrap();
6769
assert_eq!(buf.len(), 1024 * 1024);
6870
}
71+
72+
#[test]
73+
fn test_buffer_cursor() -> Result<()> {
74+
let lua = Lua::new();
75+
let mut cursor = lua.create_buffer(b"hello, world")?.cursor();
76+
77+
let mut data = Vec::new();
78+
cursor.read_to_end(&mut data)?;
79+
assert_eq!(data, b"hello, world");
80+
81+
// No more data to read
82+
let mut one = [0u8; 1];
83+
assert_eq!(cursor.read(&mut one)?, 0);
84+
85+
// Seek to start
86+
cursor.seek(SeekFrom::Start(0))?;
87+
cursor.read_exact(&mut one)?;
88+
assert_eq!(one, [b'h']);
89+
90+
// Seek to end -5
91+
cursor.seek(SeekFrom::End(-5))?;
92+
let mut five = [0u8; 5];
93+
cursor.read_exact(&mut five)?;
94+
assert_eq!(&five, b"world");
95+
96+
// Seek to current -1
97+
cursor.seek(SeekFrom::Current(-1))?;
98+
cursor.read_exact(&mut one)?;
99+
assert_eq!(one, [b'd']);
100+
101+
// Invalid seek
102+
assert!(cursor.seek(SeekFrom::Current(-100)).is_err());
103+
assert!(cursor.seek(SeekFrom::End(1)).is_err());
104+
105+
// Write data
106+
let buf = lua.create_buffer_with_capacity(100)?;
107+
cursor = buf.clone().cursor();
108+
109+
cursor.write_all(b"hello, ...")?;
110+
cursor.seek(SeekFrom::Current(-3))?;
111+
cursor.write_all(b"Rust!")?;
112+
113+
assert_eq!(&buf.read_bytes::<12>(0), b"hello, Rust!");
114+
115+
// Writing beyond the end of the buffer does nothing
116+
cursor.seek(SeekFrom::End(0))?;
117+
assert_eq!(cursor.write(b".")?, 0);
118+
119+
// Flush is no-op
120+
cursor.flush()?;
121+
122+
Ok(())
123+
}

0 commit comments

Comments
 (0)