fmod/studio/system/
bank.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::*;
8use lanyard::Utf8CStr;
9use std::ffi::{c_char, c_int, c_void};
10use std::marker::PhantomData;
11
12use crate::studio::{Bank, LoadBankFlags, System};
13use crate::{
14    FileSystemSync, Guid, filesystem_close, filesystem_open, filesystem_read, filesystem_seek,
15};
16use crate::{FmodResultExt, Result};
17
18#[cfg(doc)]
19use crate::{FileSystem, studio::AdvancedSettings};
20
21/// User data to be passed to the file callbacks.
22#[derive(Debug, Clone, Copy)]
23pub struct LoadBankUserdata<'a> {
24    data: *mut c_void,
25    size: c_int,
26    _marker: PhantomData<&'a [u8]>,
27}
28
29impl Default for LoadBankUserdata<'_> {
30    fn default() -> Self {
31        Self {
32            data: std::ptr::null_mut(),
33            size: 0,
34            _marker: PhantomData,
35        }
36    }
37}
38
39impl<'a> LoadBankUserdata<'a> {
40    /// userdata will be copied internally;
41    /// this copy will be kept until the bank has been unloaded and all calls to
42    /// [`FileSystem::open`] have been matched by a call to [`FileSystem::close`].
43    pub fn from_slice(slice: &'a [u8]) -> Self {
44        Self {
45            data: slice.as_ptr().cast::<c_void>().cast_mut(),
46            size: slice.len() as c_int,
47            _marker: PhantomData,
48        }
49    }
50
51    /// # Safety
52    ///
53    /// `userdata` must remain valid until the bank has been unloadedand all calls to
54    /// [`FileSystem::open`] have been matched by a call to [`FileSystem::close`].
55    ///
56    /// This requirement allows playback of shared streaming assets to continue after a bank is unloaded.
57    pub unsafe fn from_pointer(userdata: *mut c_void) -> Self {
58        Self {
59            data: userdata,
60            size: 0,
61            _marker: PhantomData,
62        }
63    }
64}
65
66impl System {
67    /// Loads the metadata of a bank using custom read callbacks.
68    ///
69    /// Sample data must be loaded separately see Sample Data Loading for details.
70    ///
71    /// By default this function blocks until the load finishes.
72    ///
73    /// Using the [`LoadBankFlags::NONBLOCKING`] flag causes the bank to be loaded asynchronously.
74    /// In that case, this function always returns [`Ok`] and bank contains a valid bank handle.
75    /// Load errors for asynchronous banks can be detected by calling [`Bank::get_loading_state`].
76    /// Failed asynchronous banks should be released by calling [`Bank::unload`].
77    ///
78    /// If a bank is split, separating out sample data and optionally streams from the metadata bank,
79    /// all parts must be loaded before any APIs that use the data are called.
80    /// We recommend you load each part one after another (the order in which they are loaded is not important),
81    /// then proceed with dependent API calls such as [`Bank::load_sample_data`] or [`System::get_event`].
82    pub fn load_bank_custom<F: FileSystemSync>(
83        &self,
84        userdata: LoadBankUserdata<'_>,
85        load_flags: LoadBankFlags,
86    ) -> Result<Bank> {
87        let bank_info = FMOD_STUDIO_BANK_INFO {
88            size: size_of::<FMOD_STUDIO_BANK_INFO>() as c_int,
89            userdata: userdata.data,
90            userdatalength: userdata.size,
91            opencallback: Some(filesystem_open::<F>),
92            closecallback: Some(filesystem_close::<F>),
93            readcallback: Some(filesystem_read::<F>),
94            seekcallback: Some(filesystem_seek::<F>),
95        };
96
97        let mut bank = std::ptr::null_mut();
98        unsafe {
99            FMOD_Studio_System_LoadBankCustom(
100                self.inner.as_ptr(),
101                &raw const bank_info,
102                load_flags.into(),
103                &raw mut bank,
104            )
105            .to_result()?;
106
107            Ok(Bank::from_ffi(bank))
108        }
109    }
110
111    /// Sample data must be loaded separately.
112    ///
113    /// By default this function will block until the file load finishes.
114    ///
115    /// Using the [`LoadBankFlags::NONBLOCKING`] flag will cause the bank to be loaded asynchronously.
116    /// In that case this function will always return [`Ok`] and bank will contain a valid bank handle.
117    /// Load errors for asynchronous banks can be detected by calling [`Bank::get_loading_state`].
118    /// Failed asynchronous banks should be released by calling [`Bank::unload`].
119    ///
120    /// If a bank has been split, separating out assets and optionally streams from the metadata bank, all parts must be loaded before any APIs that use the data are called.
121    /// It is recommended you load each part one after another (order is not important), then proceed with dependent API calls such as [`Bank::load_sample_data`] or [`System::get_event`].
122    pub fn load_bank_file(&self, filename: &Utf8CStr, load_flags: LoadBankFlags) -> Result<Bank> {
123        let mut bank = std::ptr::null_mut();
124        unsafe {
125            FMOD_Studio_System_LoadBankFile(
126                self.inner.as_ptr(),
127                filename.as_ptr(),
128                load_flags.bits(),
129                &raw mut bank,
130            )
131            .to_result()?;
132            Ok(Bank::from_ffi(bank))
133        }
134    }
135
136    /// Sample data must be loaded separately.
137    ///
138    /// This function is the safe counterpart of [`System::load_bank_pointer`].
139    /// FMOD will allocate an internal buffer and copy the data from the passed in buffer before using it.
140    /// The buffer passed to this function may be cleaned up at any time after this function returns.
141    ///
142    /// By default this function will block until the load finishes.
143    ///
144    /// Using the [`LoadBankFlags::NONBLOCKING`] flag will cause the bank to be loaded asynchronously.
145    /// In that case this function will always return [`Ok`] and bank will contain a valid bank handle.
146    /// Load errors for asynchronous banks can be detected by calling [`Bank::get_loading_state`].
147    /// Failed asynchronous banks should be released by calling [`Bank::unload`].
148    ///
149    /// This function is not compatible with [`AdvancedSettings::encryption_key`], using them together will cause an error to be returned.
150    ///
151    /// If a bank has been split, separating out assets and optionally streams from the metadata bank, all parts must be loaded before any APIs that use the data are called.
152    /// It is recommended you load each part one after another (order is not important), then proceed with dependent API calls such as [`Bank::load_sample_data`] or [`System::get_event`].
153    pub fn load_bank_memory(&self, buffer: &[u8], flags: LoadBankFlags) -> Result<Bank> {
154        let mut bank = std::ptr::null_mut();
155        unsafe {
156            FMOD_Studio_System_LoadBankMemory(
157                self.inner.as_ptr(),
158                buffer.as_ptr().cast::<c_char>(),
159                buffer.len() as c_int,
160                FMOD_STUDIO_LOAD_MEMORY,
161                flags.bits(),
162                &raw mut bank,
163            )
164            .to_result()?;
165            Ok(Bank::from_ffi(bank))
166        }
167    }
168
169    /// Sample data must be loaded separately.
170    ///
171    /// This function is the unsafe counterpart of [`System::load_bank_memory`].
172    /// FMOD will use the passed memory buffer directly.
173    ///
174    /// By default this function will block until the load finishes.
175    ///
176    /// Using the [`LoadBankFlags::NONBLOCKING`] flag will cause the bank to be loaded asynchronously.
177    /// In that case this function will always return [`Ok`] and bank will contain a valid bank handle.
178    /// Load errors for asynchronous banks can be detected by calling [`Bank::get_loading_state`].
179    /// Failed asynchronous banks should be released by calling [`Bank::unload`].
180    ///
181    /// This function is not compatible with [`AdvancedSettings::encryption_key`], using them together will cause an error to be returned.
182    ///
183    /// If a bank has been split, separating out assets and optionally streams from the metadata bank, all parts must be loaded before any APIs that use the data are called.
184    /// It is recommended you load each part one after another (order is not important), then proceed with dependent API calls such as [`Bank::load_sample_data`] or [`System::get_event`].
185    ///
186    /// # Safety
187    /// When using this function the buffer must be aligned to [`FMOD_STUDIO_LOAD_MEMORY_ALIGNMENT`]
188    /// and the memory must persist until the bank has been fully unloaded, which can be some time after calling [`Bank::unload`] to unload the bank.
189    /// You can ensure the memory is not being freed prematurely by only freeing it after receiving the [`FMOD_STUDIO_SYSTEM_CALLBACK_BANK_UNLOAD`] callback.
190    pub unsafe fn load_bank_pointer(&self, buffer: &[u8], flags: LoadBankFlags) -> Result<Bank> {
191        let mut bank = std::ptr::null_mut();
192        unsafe {
193            FMOD_Studio_System_LoadBankMemory(
194                self.inner.as_ptr(),
195                buffer.as_ptr().cast::<c_char>(),
196                buffer.len() as c_int,
197                FMOD_STUDIO_LOAD_MEMORY_POINT,
198                flags.bits(),
199                &raw mut bank,
200            )
201            .to_result()?;
202            Ok(Bank::from_ffi(bank))
203        }
204    }
205
206    /// Unloads all currently loaded banks.
207    pub fn unload_all_banks(&self) -> Result<()> {
208        unsafe { FMOD_Studio_System_UnloadAll(self.inner.as_ptr()).to_result() }
209    }
210
211    /// Retrieves a loaded bank
212    ///
213    /// `path_or_id` may be a path, such as `bank:/Weapons` or an ID string such as `{793cddb6-7fa1-4e06-b805-4c74c0fd625b}`.
214    ///
215    /// Note that path lookups will only succeed if the strings bank has been loaded.
216    pub fn get_bank(&self, path_or_id: &Utf8CStr) -> Result<Bank> {
217        let mut bank = std::ptr::null_mut();
218        unsafe {
219            FMOD_Studio_System_GetBank(self.inner.as_ptr(), path_or_id.as_ptr(), &raw mut bank)
220                .to_result()?;
221            Ok(Bank::from_ffi(bank))
222        }
223    }
224
225    /// Retrieves a loaded bank.
226    pub fn get_bank_by_id(&self, id: Guid) -> Result<Bank> {
227        let mut bank = std::ptr::null_mut();
228        unsafe {
229            FMOD_Studio_System_GetBankByID(self.inner.as_ptr(), &id.into(), &raw mut bank)
230                .to_result()?;
231            Ok(Bank::from_ffi(bank))
232        }
233    }
234
235    /// Retrieves the number of loaded banks.
236    pub fn bank_count(&self) -> Result<c_int> {
237        let mut count = 0;
238        unsafe {
239            FMOD_Studio_System_GetBankCount(self.inner.as_ptr(), &raw mut count).to_result()?;
240        }
241        Ok(count)
242    }
243
244    /// Retrieves a list of the currently-loaded banks.
245    pub fn get_bank_list(&self) -> Result<Vec<Bank>> {
246        let expected_count = self.bank_count()?;
247        let mut count = 0;
248        let mut list = vec![std::ptr::null_mut(); expected_count as usize];
249
250        unsafe {
251            FMOD_Studio_System_GetBankList(
252                self.inner.as_ptr(),
253                // bank is repr transparent and has the same layout as *mut FMOD_STUDIO_BANK, so this cast is ok
254                list.as_mut_ptr(),
255                list.capacity() as c_int,
256                &raw mut count,
257            )
258            .to_result()?;
259
260            debug_assert_eq!(count, expected_count);
261
262            Ok(std::mem::transmute::<
263                Vec<*mut fmod_sys::FMOD_STUDIO_BANK>,
264                Vec<Bank>,
265            >(list))
266        }
267    }
268
269    /// Retrieves a list of the currently-loaded banks.
270    ///
271    /// Fills in the provided slice instead of allocating a [`Vec`], like [`System::get_bank_list`] does.
272    /// Any banks not filled in are left as [`None`].
273    ///
274    /// Returns how many banks were fetched.
275    pub fn get_bank_list_into(&self, slice: &mut [Option<Bank>]) -> Result<c_int> {
276        let mut count = 0;
277
278        unsafe {
279            FMOD_Studio_System_GetBankList(
280                self.inner.as_ptr(),
281                // Because we use NonNull, Option<Bank> has the same layout as *mut FMOD_BANK, so this is ok!
282                slice.as_mut_ptr().cast(),
283                slice.len() as c_int,
284                &raw mut count,
285            )
286            .to_result()?;
287
288            Ok(count)
289        }
290    }
291}