Skip to content

Create RWops with custom impl #626

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions sdl2-sys/src/rwops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use sdl::SDL_bool;

#[allow(dead_code)]
#[repr(C)]
struct SDL_RWops_Anon {
pub struct SDL_RWops_Anon {
data: [c_uchar; 24],
}

Expand All @@ -23,7 +23,7 @@ pub struct SDL_RWops {
size: size_t, maxnum: size_t) -> size_t,
pub close: extern "C" fn(context: *mut SDL_RWops) -> c_int,
pub type_: uint32_t,
hidden: SDL_RWops_Anon
pub hidden: SDL_RWops_Anon,
}

extern "C" {
Expand Down
190 changes: 189 additions & 1 deletion src/sdl2/rwops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ use std::ffi::CString;
use std::io;
use std::path::Path;
use std::marker::PhantomData;
use libc::{c_void, c_int, size_t, c_char};
use std::slice;
use libc::{c_void, c_int, size_t, c_char, int64_t};
use get_error;

use sys::rwops as ll;
Expand Down Expand Up @@ -74,6 +75,14 @@ impl<'a> RWops<'a> {
}
}

/// Create an SDL RWops object from a Rust stream.
pub fn from_stream<R: io::Read + io::Seek + 'static>(reader: R) -> RWops<'static> {
CustomRWopsBuilder::new(reader)
.with_seek(|reader, from| reader.seek(from))
.with_read(|reader, buf| reader.read(buf))
.build()
}

/// Prepares a read-write memory buffer for use with `RWops`.
///
/// This method can only fail if the buffer size is zero.
Expand Down Expand Up @@ -159,3 +168,182 @@ impl<'a> io::Seek for RWops<'a> {
}
}
}

/// Builder for creating custom RWops implementations.
pub struct CustomRWopsBuilder<T> {
ptr: *mut T,
size: Option<Box<FnMut() -> i64>>,
seek: Option<Box<FnMut(io::SeekFrom) -> io::Result<u64>>>,
read: Option<Box<FnMut(&mut [u8]) -> io::Result<usize>>>,
write: Option<Box<FnMut(&[u8]) -> io::Result<usize>>>,
}

impl<T: 'static> CustomRWopsBuilder<T> {
/// Create a new custom RWops builder around a value.
pub fn new(value: T) -> Self {
let ptr = Box::into_raw(Box::new(value));

Self {
ptr: ptr,
size: None,
seek: None,
read: None,
write: None,
}
}

/// Set the callback for fetching the size.
pub fn with_size<F: FnMut(&mut T) -> i64 + 'static>(mut self, mut f: F) -> Self {
let ptr = self.ptr;
self.size = Some(Box::new(move || unsafe {
f(&mut *ptr)
}));

self
}

/// Set the callback for seeking.
pub fn with_seek<F: FnMut(&mut T, io::SeekFrom) -> io::Result<u64> + 'static>(mut self, mut f: F) -> Self {
let ptr = self.ptr;
self.seek = Some(Box::new(move |from| unsafe {
f(&mut *ptr, from)
}));

self
}

/// Set the callback for reading.
pub fn with_read<F: FnMut(&mut T, &mut [u8]) -> io::Result<usize> + 'static>(mut self, mut f: F) -> Self {
let ptr = self.ptr;
self.read = Some(Box::new(move |buf| unsafe {
f(&mut *ptr, buf)
}));

self
}

/// Set the callback for writing.
pub fn with_write<F: FnMut(&mut T, &[u8]) -> io::Result<usize> + 'static>(mut self, mut f: F) -> Self {
let ptr = self.ptr;
self.write = Some(Box::new(move |buf| unsafe {
f(&mut *ptr, buf)
}));

self
}

/// Create a RWops from the builder.
pub fn build(self) -> RWops<'static> {
struct Callbacks {
size: Option<Box<FnMut() -> i64>>,
seek: Option<Box<FnMut(io::SeekFrom) -> io::Result<u64>>>,
read: Option<Box<FnMut(&mut [u8]) -> io::Result<usize>>>,
write: Option<Box<FnMut(&[u8]) -> io::Result<usize>>>,
drop: Box<Fn()>,
}

unsafe fn get_callbacks<'a>(ptr: *mut ll::SDL_RWops) -> *mut *mut Callbacks {
&(*ptr).hidden as *const _ as *mut _
}

extern "C" fn stream_size(rwops: *mut ll::SDL_RWops) -> int64_t {
let mut callbacks = unsafe {
&mut **get_callbacks(rwops)
};

callbacks.size
.as_mut()
.map(|f| f())
.unwrap_or(-1)
}

extern "C" fn stream_seek(rwops: *mut ll::SDL_RWops, offset: int64_t, whence: c_int) -> int64_t {
let mut callbacks = unsafe {
&mut **get_callbacks(rwops)
};

let from = match whence {
SEEK_SET => io::SeekFrom::Start(offset as u64),
SEEK_CUR => io::SeekFrom::Current(offset),
SEEK_END => io::SeekFrom::End(offset),
_ => return -1,
};

callbacks.seek
.as_mut()
.and_then(|f| f(from).ok())
.map(|pos| pos as i64)
.unwrap_or(-1)
}

extern "C" fn stream_read(rwops: *mut ll::SDL_RWops, ptr: *mut c_void, size: size_t, maxnum: size_t) -> size_t {
let mut callbacks = unsafe {
&mut **get_callbacks(rwops)
};

let buf = unsafe {
slice::from_raw_parts_mut(ptr as *mut u8, size * maxnum)
};

callbacks.read
.as_mut()
.and_then(|f| f(buf).ok())
.unwrap_or(0)
}

extern "C" fn stream_write(rwops: *mut ll::SDL_RWops, ptr: *const c_void, size: size_t, maxnum: size_t) -> size_t {
let mut callbacks = unsafe {
&mut **get_callbacks(rwops)
};

let buf = unsafe {
slice::from_raw_parts(ptr as *mut u8, size * maxnum)
};

callbacks.write
.as_mut()
.and_then(|f| f(buf).ok())
.unwrap_or(0)
}

extern "C" fn stream_close(rwops: *mut ll::SDL_RWops) -> c_int {
if !rwops.is_null() {
let callbacks = unsafe {
&mut **get_callbacks(rwops)
};

(callbacks.drop)();

unsafe {
ll::SDL_FreeRW(rwops);
}
}
0
}

let value_ptr = self.ptr;
let callbacks = Callbacks {
size: self.size,
seek: self.seek,
read: self.read,
write: self.write,
drop: Box::new(move || unsafe {
Box::from_raw(value_ptr);
}),
};

unsafe {
let rwops_ptr = ll::SDL_AllocRW();

*get_callbacks(rwops_ptr) = Box::into_raw(Box::new(callbacks));
(*rwops_ptr).type_ = 0;
(*rwops_ptr).size = stream_size;
(*rwops_ptr).seek = stream_seek;
(*rwops_ptr).read = stream_read;
(*rwops_ptr).write = stream_write;
(*rwops_ptr).close = stream_close;

RWops::from_ll(rwops_ptr)
}
}
}