Skip to content
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)
}
}
}