use fmod_sys::*;
use crate::{FmodResultExt, Result};
use lanyard::Utf8CStr;
use std::ffi::{c_char, c_int, c_uint, c_void};
#[cfg(doc)]
use crate::{Error, System};
pub trait FileSystem {
fn open(name: &Utf8CStr, userdata: *mut c_void) -> Result<(*mut c_void, c_uint)>;
fn close(handle: *mut c_void, userdata: *mut c_void) -> Result<()>;
}
#[derive(Debug)]
pub struct FileBuffer<'a> {
buffer: &'a mut [u8],
written: &'a mut u32,
}
impl FileBuffer<'_> {
pub fn capacity(&self) -> usize {
self.buffer.len()
}
pub fn written(&self) -> u32 {
*self.written
}
pub fn is_full(&self) -> bool {
self.written() == self.capacity() as u32
}
}
impl std::io::Write for FileBuffer<'_> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let unwritten_region = &mut self.buffer[*self.written as usize..];
let len = buf.len().min(unwritten_region.len());
self.buffer[..len].copy_from_slice(&buf[..len]);
*self.written += len as u32;
Ok(len)
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
pub trait FileSystemSync: FileSystem {
fn read(handle: *mut c_void, userdata: *mut c_void, buffer: FileBuffer<'_>) -> Result<()>;
fn seek(handle: *mut c_void, userdata: *mut c_void, position: c_uint) -> Result<()>;
}
#[derive(Debug)]
pub struct AsyncReadInfo {
raw: *mut FMOD_ASYNCREADINFO,
}
impl AsyncReadInfo {
pub fn handle(&self) -> *mut c_void {
unsafe { *self.raw }.handle
}
pub fn offset(&self) -> c_uint {
unsafe { *self.raw }.offset
}
pub fn size(&self) -> c_uint {
unsafe { *self.raw }.sizebytes
}
pub fn priority(&self) -> c_int {
unsafe { *self.raw }.priority
}
pub fn userdata(&self) -> *mut c_void {
unsafe { *self.raw }.userdata
}
pub unsafe fn set_userdata(&mut self, userdata: *mut c_void) {
unsafe { *self.raw }.userdata = userdata;
}
pub fn raw(&self) -> *mut FMOD_ASYNCREADINFO {
self.raw
}
pub fn written(&self) -> c_uint {
unsafe { *self.raw }.bytesread
}
pub fn buffer(&mut self) -> FileBuffer<'_> {
let ptr = unsafe { *self.raw }.buffer;
let len = self.size();
let buffer = unsafe { std::slice::from_raw_parts_mut(ptr.cast(), len as usize) };
let written = &mut unsafe { &mut *self.raw }.bytesread;
FileBuffer { buffer, written }
}
pub unsafe fn finish(self, result: Result<()>) {
let mut fmod_result = FMOD_RESULT::from_result(result);
if fmod_result == FMOD_RESULT::FMOD_OK && self.written() < self.size() {
fmod_result = FMOD_RESULT::FMOD_ERR_FILE_EOF;
}
unsafe { (*self.raw).done.unwrap_unchecked()(self.raw, fmod_result) }
}
}
#[derive(Debug)]
pub struct AsyncCancelInfo {
raw: *mut FMOD_ASYNCREADINFO,
}
impl AsyncCancelInfo {
pub fn handle(&self) -> *mut c_void {
unsafe { *self.raw }.handle
}
pub fn offset(&self) -> c_uint {
unsafe { *self.raw }.offset
}
pub fn size(&self) -> c_uint {
unsafe { *self.raw }.sizebytes
}
pub fn priority(&self) -> c_int {
unsafe { *self.raw }.priority
}
pub fn userdata(&self) -> *mut c_void {
unsafe { *self.raw }.userdata
}
pub fn raw(&self) -> *mut FMOD_ASYNCREADINFO {
self.raw
}
}
pub unsafe trait FileSystemAsync: FileSystem {
fn read(info: AsyncReadInfo, userdata: *mut c_void) -> Result<()>;
fn cancel(info: AsyncCancelInfo, userdata: *mut c_void) -> Result<()>;
}
pub(crate) unsafe extern "C" fn filesystem_open<F: FileSystem>(
name: *const c_char,
raw_filesize: *mut c_uint,
raw_handle: *mut *mut c_void,
userdata: *mut c_void,
) -> FMOD_RESULT {
let name = unsafe { Utf8CStr::from_ptr_unchecked(name) };
let (handle, file_size) = match F::open(name, userdata) {
Ok(h) => h,
Err(e) => return e.into(),
};
unsafe {
*raw_filesize = file_size;
*raw_handle = handle;
}
FMOD_RESULT::FMOD_OK
}
pub(crate) unsafe extern "C" fn filesystem_close<F: FileSystem>(
handle: *mut c_void,
userdata: *mut c_void,
) -> FMOD_RESULT {
let result = F::close(handle, userdata);
FMOD_RESULT::from_result(result)
}
pub(crate) unsafe extern "C" fn filesystem_read<F: FileSystemSync>(
handle: *mut c_void,
buffer: *mut c_void,
size_bytes: c_uint,
bytes_read: *mut c_uint,
userdata: *mut c_void,
) -> FMOD_RESULT {
let buffer = unsafe {
FileBuffer {
buffer: std::slice::from_raw_parts_mut(buffer.cast(), size_bytes as usize),
written: &mut *bytes_read,
}
};
if let Err(e) = F::read(handle, userdata, buffer) {
return e.into();
}
if unsafe { *bytes_read } < size_bytes {
FMOD_RESULT::FMOD_ERR_FILE_EOF
} else {
FMOD_RESULT::FMOD_OK
}
}
pub(crate) unsafe extern "C" fn filesystem_seek<F: FileSystemSync>(
handle: *mut c_void,
pos: c_uint,
userdata: *mut c_void,
) -> FMOD_RESULT {
let result = F::seek(handle, userdata, pos);
FMOD_RESULT::from_result(result)
}
pub(crate) unsafe extern "C" fn async_filesystem_read<F: FileSystemAsync>(
raw: *mut FMOD_ASYNCREADINFO,
userdata: *mut c_void,
) -> FMOD_RESULT {
let result = F::read(AsyncReadInfo { raw }, userdata);
FMOD_RESULT::from_result(result)
}
pub(crate) unsafe extern "C" fn async_filesystem_cancel<F: FileSystemAsync>(
raw: *mut FMOD_ASYNCREADINFO,
userdata: *mut c_void,
) -> FMOD_RESULT {
let result = F::cancel(AsyncCancelInfo { raw }, userdata);
FMOD_RESULT::from_result(result)
}