fmod/core/
filesystem.rs

1// Copyright (c) 2024 Melody Madeline Lyons
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at https://mozilla.org/MPL/2.0/.
6
7use fmod_sys::*;
8
9use crate::{FmodResultExt, Result};
10use lanyard::Utf8CStr;
11use std::ffi::{c_char, c_int, c_uint, c_void};
12
13#[cfg(doc)]
14use crate::{Error, System};
15
16// I was lost on how to do this for a while, so I took some pointers from https://github.com/CAD97/fmod-rs/blob/main/crates/fmod-rs/src/core/common/file.rs#L181
17// It's not copied verbatim, I made some different design choices (like opting to make handle be a *mut c_void instead)
18// for similar reasons to this crate not handling userdata.
19// This is such a power user feature that I'm not sure it's worth hiding away most of the implementation details
20
21// TODO test and validate my assumptions are correct
22
23/// The base trait for all filesystems.
24pub trait FileSystem {
25    /// Callback for opening a file.
26    ///
27    /// Return the appropriate error code such as [`Error::FileNotFound`] if the file fails to open.
28    /// If the callback is from [`System::attach_filesystem`], then the return value is ignored.
29    fn open(name: &Utf8CStr, userdata: *mut c_void) -> Result<(*mut c_void, c_uint)>;
30
31    /// Callback for closing a file.
32    ///
33    /// Close any user created file handle and perform any cleanup necessary for the file here.
34    /// If the callback is from [`System::attach_filesystem`], then the return value is ignored.
35    fn close(handle: *mut c_void, userdata: *mut c_void) -> Result<()>;
36}
37
38/// A mutable file buffer.
39///
40/// It's a lot like [`std::io::Cursor`].
41#[derive(Debug)]
42pub struct FileBuffer<'a> {
43    buffer: &'a mut [u8],
44    written: &'a mut u32,
45}
46
47impl FileBuffer<'_> {
48    /// The capacity of this file buffer.
49    pub fn capacity(&self) -> usize {
50        self.buffer.len()
51    }
52
53    /// The total amount of bytes written.
54    pub fn written(&self) -> u32 {
55        *self.written
56    }
57
58    /// Returns true if [`Self::written`] == [`Self::capacity`].
59    pub fn is_full(&self) -> bool {
60        self.written() == self.capacity() as u32
61    }
62}
63
64impl std::io::Write for FileBuffer<'_> {
65    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
66        let unwritten_region = &mut self.buffer[*self.written as usize..];
67        let len = buf.len().min(unwritten_region.len());
68        self.buffer[..len].copy_from_slice(&buf[..len]);
69        *self.written += len as u32;
70        Ok(len)
71    }
72
73    // no-op
74    fn flush(&mut self) -> std::io::Result<()> {
75        Ok(())
76    }
77}
78
79/// A synchronous filesystem.
80///
81/// You should prefer implementing this trait over [`FileSystemAsync`]- it's a lot easier to do.
82pub trait FileSystemSync: FileSystem {
83    /// Callback for reading from a file.
84    ///
85    /// If the callback is from [`System::attach_filesystem`], then the return value is ignored.
86    ///
87    /// If there is not enough data to read the requested number of bytes,
88    /// return fewer bytes in the bytesread parameter and and return [`Error::FileEof`].
89    fn read(handle: *mut c_void, userdata: *mut c_void, buffer: FileBuffer<'_>) -> Result<()>;
90
91    /// Callback for seeking within a file.
92    ///
93    /// If the callback is from [`System::attach_filesystem`], then the return value is ignored.
94    fn seek(handle: *mut c_void, userdata: *mut c_void, position: c_uint) -> Result<()>;
95}
96
97/// Information about a single asynchronous file operation.
98#[derive(Debug)]
99pub struct AsyncReadInfo {
100    raw: *mut FMOD_ASYNCREADINFO,
101}
102
103impl AsyncReadInfo {
104    /// File handle that was returned in [`FileSystem::open`].
105    pub fn handle(&self) -> *mut c_void {
106        unsafe { *self.raw }.handle
107    }
108
109    /// Offset within the file where the read operation should occur.
110    pub fn offset(&self) -> c_uint {
111        unsafe { *self.raw }.offset
112    }
113
114    /// Number of bytes to read.
115    pub fn size(&self) -> c_uint {
116        unsafe { *self.raw }.sizebytes
117    }
118
119    /// Priority hint for how quickly this operation should be serviced where 0 represents low importance and 100 represents extreme importance.
120    /// This could be used to prioritize the read order of a file job queue for example.
121    /// FMOD decides the importance of the read based on if it could degrade audio or not.
122    pub fn priority(&self) -> c_int {
123        unsafe { *self.raw }.priority
124    }
125
126    /// User value associated with this async operation, passed to [`FileSystemAsync::cancel`].
127    pub fn userdata(&self) -> *mut c_void {
128        unsafe { *self.raw }.userdata
129    }
130
131    /// Set the user value associated with this async operation.
132    ///
133    /// # Safety
134    ///
135    /// You cannot call this while a [`AsyncCancelInfo`] with the same raw pointer is live.
136    // FIXME: make this safe somehow?
137    pub unsafe fn set_userdata(&mut self, userdata: *mut c_void) {
138        unsafe { *self.raw }.userdata = userdata;
139    }
140
141    /// Get the raw pointer associated with this [`AsyncReadInfo`].
142    pub fn raw(&self) -> *mut FMOD_ASYNCREADINFO {
143        self.raw
144    }
145
146    /// Number of bytes currently read.
147    pub fn written(&self) -> c_uint {
148        unsafe { *self.raw }.bytesread
149    }
150
151    /// Get the [`FileBuffer`] associated with this [`AsyncReadInfo`].
152    // Normally this would be really unsafe because FMOD hands out the same *mut FMOD_ASYNCREADINFO to `read()` and `cancel()`.
153    // AsyncCancelInfo doesn't support accessing the buffer, so this should be safe.
154    pub fn buffer(&mut self) -> FileBuffer<'_> {
155        let ptr = unsafe { *self.raw }.buffer;
156        let len = self.size();
157
158        let buffer = unsafe { std::slice::from_raw_parts_mut(ptr.cast(), len as usize) };
159        let written = &mut unsafe { &mut *self.raw }.bytesread;
160        FileBuffer { buffer, written }
161    }
162
163    /// Signal the async read is done.
164    ///
165    /// If [`AsyncReadInfo::written`] != [`AsyncReadInfo::size`] this function will send an [`Error::FileEof`] for you.
166    ///
167    /// # Safety
168    ///
169    /// If you have a [`AsyncCancelInfo`] with the same raw pointer, it is immediately invalid after calling this function.
170    // I *really* don't like taking a result like this, but I can't think of another way...
171    pub unsafe fn finish(self, result: Result<()>) {
172        let mut fmod_result = FMOD_RESULT::from_result(result);
173        if fmod_result == FMOD_RESULT::FMOD_OK && self.written() < self.size() {
174            fmod_result = FMOD_RESULT::FMOD_ERR_FILE_EOF;
175        }
176        // Should never be null
177        unsafe { (*self.raw).done.unwrap_unchecked()(self.raw, fmod_result) }
178    }
179}
180
181/// Information about cancelling a asynchronous file operation.
182#[derive(Debug)]
183pub struct AsyncCancelInfo {
184    raw: *mut FMOD_ASYNCREADINFO,
185}
186
187impl AsyncCancelInfo {
188    /// File handle that was returned in [`FileSystem::open`].
189    pub fn handle(&self) -> *mut c_void {
190        unsafe { *self.raw }.handle
191    }
192
193    /// Offset within the file where the read operation should occur.
194    pub fn offset(&self) -> c_uint {
195        unsafe { *self.raw }.offset
196    }
197
198    /// Number of bytes to read.
199    pub fn size(&self) -> c_uint {
200        unsafe { *self.raw }.sizebytes
201    }
202
203    /// Priority hint for how quickly this operation should be serviced where 0 represents low importance and 100 represents extreme importance.
204    /// This could be used to prioritize the read order of a file job queue for example.
205    /// FMOD decides the importance of the read based on if it could degrade audio or not.
206    pub fn priority(&self) -> c_int {
207        unsafe { *self.raw }.priority
208    }
209
210    /// User value associated with this async operation, passed to [`FileSystemAsync::cancel`].
211    pub fn userdata(&self) -> *mut c_void {
212        unsafe { *self.raw }.userdata
213    }
214
215    /// Get the raw pointer associated with this [`AsyncCancelInfo`].
216    pub fn raw(&self) -> *mut FMOD_ASYNCREADINFO {
217        self.raw
218    }
219}
220
221/// An async filesystem.
222///
223/// # Safety
224///
225/// This trait is marked as unsafe because a correct implementation of [`FileSystemAsync`] is hard to get right.
226/// I'd suggest reading up on the FMOD documentation to get a better idea of how to write this.
227pub unsafe trait FileSystemAsync: FileSystem {
228    /// Callback for reading from a file asynchronously.
229    ///
230    /// This callback allows you to accept a file I/O request without servicing it immediately.
231    ///
232    /// The callback can queue or store the [`AsyncReadInfo`],
233    ///  so that a 'servicing routine' can read the data and mark the job as done.
234    ///
235    /// Marking an asynchronous job as 'done' outside of this callback can be done by calling [`AsyncReadInfo::finish`] with the file read result as a parameter.
236    ///
237    /// If the servicing routine is processed in the same thread as the thread that invokes this callback
238    /// (for example the thread that calls [`System::create_sound`] or [`System::create_stream`]),
239    /// a deadlock will occur because while [`System::create_sound`] or [`System::create_stream`] waits for the file data,
240    /// the servicing routine in the main thread won't be able to execute.
241    ///
242    /// This typically means an outside servicing routine should typically be run in a separate thread.
243    ///
244    /// The read request can be queued or stored and this callback can return immediately with [`Ok`].
245    /// Returning an error at this point will cause FMOD to stop what it was doing and return back to the caller.
246    /// If it is from FMOD's stream thread, the stream will typically stop.
247    fn read(info: AsyncReadInfo, userdata: *mut c_void) -> Result<()>;
248
249    /// Callback for cancelling a pending asynchronous read.
250    ///
251    /// This callback is called to stop/release or shut down the resource that is holding the file,
252    /// for example: releasing a Sound stream.
253    ///
254    /// Before returning from this callback the implementation must ensure that all references to info are relinquished.
255    fn cancel(info: AsyncCancelInfo, userdata: *mut c_void) -> Result<()>;
256}
257
258pub(crate) unsafe extern "C" fn filesystem_open<F: FileSystem>(
259    name: *const c_char,
260    raw_filesize: *mut c_uint,
261    raw_handle: *mut *mut c_void,
262    userdata: *mut c_void,
263) -> FMOD_RESULT {
264    let name = unsafe { Utf8CStr::from_ptr_unchecked(name) };
265    let (handle, file_size) = match F::open(name, userdata) {
266        Ok(h) => h,
267        Err(e) => return e.into(),
268    };
269    unsafe {
270        *raw_filesize = file_size;
271        *raw_handle = handle;
272    }
273    FMOD_RESULT::FMOD_OK
274}
275
276pub(crate) unsafe extern "C" fn filesystem_close<F: FileSystem>(
277    handle: *mut c_void,
278    userdata: *mut c_void,
279) -> FMOD_RESULT {
280    let result = F::close(handle, userdata);
281    FMOD_RESULT::from_result(result)
282}
283
284pub(crate) unsafe extern "C" fn filesystem_read<F: FileSystemSync>(
285    handle: *mut c_void,
286    buffer: *mut c_void,
287    size_bytes: c_uint,
288    bytes_read: *mut c_uint,
289    userdata: *mut c_void,
290) -> FMOD_RESULT {
291    let buffer = unsafe {
292        FileBuffer {
293            buffer: std::slice::from_raw_parts_mut(buffer.cast(), size_bytes as usize),
294            written: &mut *bytes_read,
295        }
296    };
297    if let Err(e) = F::read(handle, userdata, buffer) {
298        return e.into();
299    }
300
301    if unsafe { *bytes_read } < size_bytes {
302        FMOD_RESULT::FMOD_ERR_FILE_EOF
303    } else {
304        FMOD_RESULT::FMOD_OK
305    }
306}
307
308pub(crate) unsafe extern "C" fn filesystem_seek<F: FileSystemSync>(
309    handle: *mut c_void,
310    pos: c_uint,
311    userdata: *mut c_void,
312) -> FMOD_RESULT {
313    let result = F::seek(handle, userdata, pos);
314    FMOD_RESULT::from_result(result)
315}
316
317pub(crate) unsafe extern "C" fn async_filesystem_read<F: FileSystemAsync>(
318    raw: *mut FMOD_ASYNCREADINFO,
319    userdata: *mut c_void,
320) -> FMOD_RESULT {
321    let result = F::read(AsyncReadInfo { raw }, userdata);
322    FMOD_RESULT::from_result(result)
323}
324
325pub(crate) unsafe extern "C" fn async_filesystem_cancel<F: FileSystemAsync>(
326    raw: *mut FMOD_ASYNCREADINFO,
327    userdata: *mut c_void,
328) -> FMOD_RESULT {
329    let result = F::cancel(AsyncCancelInfo { raw }, userdata);
330    FMOD_RESULT::from_result(result)
331}