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}