fmod/core/system/
callback.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#![allow(missing_docs, deprecated)]
7
8use crate::{Error, FmodResultExt, Result};
9use std::ffi::{c_int, c_void};
10
11use fmod_sys::*;
12use lanyard::Utf8CStr;
13
14#[cfg(feature = "studio")]
15use crate::studio;
16use crate::{
17    Channel, ChannelControl, ChannelGroup, Dsp, DspConnection, Geometry, OutputType, Reverb3D,
18    Sound, SoundGroup, System, panic_wrapper,
19};
20
21/// Information describing an error that has occurred.
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct ErrorCallbackInfo<'a> {
24    /// Error code result.
25    pub error: Error,
26    /// Type of instance the error occurred on.
27    pub instance: Instance,
28    /// Function that the error occurred on.
29    pub function_name: &'a Utf8CStr,
30    /// Function parameters that the error ocurred on.
31    pub function_params: &'a Utf8CStr,
32}
33
34/// Identifier used to represent the different types of instance in the error callback.
35#[derive(Clone, Copy, Debug, PartialEq, Eq)]
36pub enum Instance {
37    /// Type representing no known instance type.
38    None,
39    /// Type representing [`System`].
40    System(System),
41    /// Type representing [`Channel`].
42    Channel(Channel),
43    ChannelGroup(ChannelGroup),
44    /// Type representing [`ChannelControl`].
45    ChannelControl(ChannelControl),
46    Sound(Sound),
47    /// Type representing [`Sound`].
48    SoundGroup(SoundGroup),
49    /// Type representing [`Dsp`].
50    Dsp(Dsp),
51    /// Type representing [`DspConnection`].
52    DspConnection(DspConnection),
53    /// Type representing [`Geometry`].
54    Geometry(Geometry),
55    /// Type representing [`Reverb3D`].
56    Reverb3D(Reverb3D),
57    /// Deprecated.
58    #[deprecated]
59    StudioParameterInstance,
60
61    /// Type representing [`studio::System`].
62    #[cfg(feature = "studio")]
63    StudioSystem(studio::System),
64    /// Type representing [`studio::EventDescription`].
65    #[cfg(feature = "studio")]
66    StudioEventDescription(studio::EventDescription),
67    #[cfg(feature = "studio")]
68    /// Type representing [`studio::EventInstance`].
69    StudioEventInstance(studio::EventInstance),
70    #[cfg(feature = "studio")]
71    /// Type representing [`studio::Bus`].
72    StudioBus(studio::Bus),
73    #[cfg(feature = "studio")]
74    /// Type representing [`studio::Vca`].
75    StudioVCA(studio::Vca),
76    #[cfg(feature = "studio")]
77    /// Type representing [`studio::Bank`].
78    StudioBank(studio::Bank),
79    #[cfg(feature = "studio")]
80    /// Type representing [`studio::CommandReplay`].
81    StudioCommandReplay(studio::CommandReplay),
82
83    /// Represents a raw FMOD Studio type.
84    /// Because the Studio feature is disabled, you shouldn't be able to get this variant.
85    #[cfg(not(feature = "studio"))]
86    /// Represents a raw FMOD Studio type.
87    /// Because the Studio feature is disabled, you shouldn't be able to get this variant.
88    StudioSystem(*mut c_void),
89    #[cfg(not(feature = "studio"))]
90    /// Represents a raw FMOD Studio type.
91    /// Because the Studio feature is disabled, you shouldn't be able to get this variant.
92    StudioEventDescription(*mut c_void),
93    #[cfg(not(feature = "studio"))]
94    /// Represents a raw FMOD Studio type.
95    /// Because the Studio feature is disabled, you shouldn't be able to get this variant.
96    StudioEventInstance(*mut c_void),
97    #[cfg(not(feature = "studio"))]
98    /// Represents a raw FMOD Studio type.
99    /// Because the Studio feature is disabled, you shouldn't be able to get this variant.
100    StudioBus(*mut c_void),
101    #[cfg(not(feature = "studio"))]
102    /// Represents a raw FMOD Studio type.
103    /// Because the Studio feature is disabled, you shouldn't be able to get this variant.
104    StudioVCA(*mut c_void),
105    #[cfg(not(feature = "studio"))]
106    /// Represents a raw FMOD Studio type.
107    /// Because the Studio feature is disabled, you shouldn't be able to get this variant.
108    StudioBank(*mut c_void),
109    /// Represents a raw FMOD Studio type.
110    /// Because the Studio feature is disabled, you shouldn't be able to get this variant.
111    #[cfg(not(feature = "studio"))]
112    StudioCommandReplay(*mut c_void),
113}
114
115impl Instance {
116    fn from_raw(kind: FMOD_ERRORCALLBACK_INSTANCETYPE, pointer: *mut c_void) -> Self {
117        match kind {
118            FMOD_ERRORCALLBACK_INSTANCETYPE_NONE => Instance::None,
119            FMOD_ERRORCALLBACK_INSTANCETYPE_SYSTEM => {
120                Instance::System(unsafe { System::from_ffi(pointer.cast()) })
121            }
122            FMOD_ERRORCALLBACK_INSTANCETYPE_CHANNEL => {
123                Instance::Channel(unsafe { Channel::from_ffi(pointer.cast()) })
124            }
125            FMOD_ERRORCALLBACK_INSTANCETYPE_CHANNELGROUP => {
126                Instance::ChannelGroup(unsafe { ChannelGroup::from_ffi(pointer.cast()) })
127            }
128            FMOD_ERRORCALLBACK_INSTANCETYPE_CHANNELCONTROL => {
129                Instance::ChannelControl(unsafe { ChannelControl::from_ffi(pointer.cast()) })
130            }
131            FMOD_ERRORCALLBACK_INSTANCETYPE_SOUND => {
132                Instance::Sound(unsafe { Sound::from_ffi(pointer.cast()) })
133            }
134            FMOD_ERRORCALLBACK_INSTANCETYPE_SOUNDGROUP => {
135                Instance::SoundGroup(unsafe { SoundGroup::from_ffi(pointer.cast()) })
136            }
137            FMOD_ERRORCALLBACK_INSTANCETYPE_DSP => {
138                Instance::Dsp(unsafe { Dsp::from_ffi(pointer.cast()) })
139            }
140            FMOD_ERRORCALLBACK_INSTANCETYPE_DSPCONNECTION => {
141                Instance::DspConnection(unsafe { DspConnection::from_ffi(pointer.cast()) })
142            }
143            FMOD_ERRORCALLBACK_INSTANCETYPE_GEOMETRY => {
144                Instance::Geometry(unsafe { Geometry::from_ffi(pointer.cast()) })
145            }
146            FMOD_ERRORCALLBACK_INSTANCETYPE_REVERB3D => {
147                Instance::Reverb3D(unsafe { Reverb3D::from_ffi(pointer.cast()) })
148            }
149            FMOD_ERRORCALLBACK_INSTANCETYPE_STUDIO_PARAMETERINSTANCE => {
150                Instance::StudioParameterInstance
151            }
152            #[cfg(not(feature = "studio"))]
153            FMOD_ERRORCALLBACK_INSTANCETYPE_STUDIO_SYSTEM => Instance::StudioSystem(pointer),
154            #[cfg(not(feature = "studio"))]
155            FMOD_ERRORCALLBACK_INSTANCETYPE_STUDIO_EVENTDESCRIPTION => {
156                Instance::StudioEventDescription(pointer)
157            }
158            #[cfg(not(feature = "studio"))]
159            FMOD_ERRORCALLBACK_INSTANCETYPE_STUDIO_EVENTINSTANCE => {
160                Instance::StudioEventInstance(pointer)
161            }
162            #[cfg(not(feature = "studio"))]
163            FMOD_ERRORCALLBACK_INSTANCETYPE_STUDIO_BUS => Instance::StudioBus(pointer),
164            #[cfg(not(feature = "studio"))]
165            FMOD_ERRORCALLBACK_INSTANCETYPE_STUDIO_VCA => Instance::StudioVCA(pointer),
166            #[cfg(not(feature = "studio"))]
167            FMOD_ERRORCALLBACK_INSTANCETYPE_STUDIO_BANK => Instance::StudioBank(pointer),
168            #[cfg(not(feature = "studio"))]
169            FMOD_ERRORCALLBACK_INSTANCETYPE_STUDIO_COMMANDREPLAY => {
170                Instance::StudioCommandReplay(pointer)
171            }
172            #[cfg(feature = "studio")]
173            FMOD_ERRORCALLBACK_INSTANCETYPE_STUDIO_SYSTEM => {
174                Instance::StudioSystem(unsafe { studio::System::from_ffi(pointer.cast()) })
175            }
176            #[cfg(feature = "studio")]
177            FMOD_ERRORCALLBACK_INSTANCETYPE_STUDIO_EVENTDESCRIPTION => {
178                Instance::StudioEventDescription(unsafe {
179                    studio::EventDescription::from_ffi(pointer.cast())
180                })
181            }
182            #[cfg(feature = "studio")]
183            FMOD_ERRORCALLBACK_INSTANCETYPE_STUDIO_EVENTINSTANCE => {
184                Instance::StudioEventInstance(unsafe {
185                    studio::EventInstance::from_ffi(pointer.cast())
186                })
187            }
188            #[cfg(feature = "studio")]
189            FMOD_ERRORCALLBACK_INSTANCETYPE_STUDIO_BUS => {
190                Instance::StudioBus(unsafe { studio::Bus::from_ffi(pointer.cast()) })
191            }
192            #[cfg(feature = "studio")]
193            FMOD_ERRORCALLBACK_INSTANCETYPE_STUDIO_VCA => {
194                Instance::StudioVCA(unsafe { studio::Vca::from_ffi(pointer.cast()) })
195            }
196            #[cfg(feature = "studio")]
197            FMOD_ERRORCALLBACK_INSTANCETYPE_STUDIO_BANK => {
198                Instance::StudioBank(unsafe { studio::Bank::from_ffi(pointer.cast()) })
199            }
200            #[cfg(feature = "studio")]
201            FMOD_ERRORCALLBACK_INSTANCETYPE_STUDIO_COMMANDREPLAY => {
202                Instance::StudioCommandReplay(unsafe {
203                    studio::CommandReplay::from_ffi(pointer.cast())
204                })
205            }
206            _ => {
207                eprintln!("warning: unknown instance type {kind}");
208                Instance::None
209            }
210        }
211    }
212}
213
214impl ErrorCallbackInfo<'_> {
215    /// # Safety
216    ///
217    /// The function name and function params fields of [`FMOD_ERRORCALLBACK_INFO`] must be a null-terminated and must be valid for reads of bytes up to and including the nul terminator.
218    ///
219    /// See [`Utf8CStr::from_ptr_unchecked`] for more information.
220    pub unsafe fn from_ffi(value: FMOD_ERRORCALLBACK_INFO) -> Self {
221        Self {
222            error: value.result.into(),
223            instance: Instance::from_raw(value.instancetype, value.instance),
224            function_name: unsafe { Utf8CStr::from_ptr_unchecked(value.functionname) },
225            function_params: unsafe { Utf8CStr::from_ptr_unchecked(value.functionparams) },
226        }
227    }
228}
229
230bitflags::bitflags! {
231  #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
232  pub struct SystemCallbackMask: FMOD_SYSTEM_CALLBACK_TYPE {
233      const DEVICELISTCHANGED     = FMOD_SYSTEM_CALLBACK_DEVICELISTCHANGED;
234      const DEVICELOST            = FMOD_SYSTEM_CALLBACK_DEVICELOST;
235      const MEMORYALLOCATIONFAILED= FMOD_SYSTEM_CALLBACK_MEMORYALLOCATIONFAILED;
236      const THREADCREATED         = FMOD_SYSTEM_CALLBACK_THREADCREATED;
237      const BADDSPCONNECTION      = FMOD_SYSTEM_CALLBACK_BADDSPCONNECTION;
238      const PREMIX                = FMOD_SYSTEM_CALLBACK_PREMIX;
239      const POSTMIX               = FMOD_SYSTEM_CALLBACK_POSTMIX;
240      const ERROR                 = FMOD_SYSTEM_CALLBACK_ERROR;
241      #[cfg(fmod_eq_2_2)]
242      const MIDMIX                = FMOD_SYSTEM_CALLBACK_MIDMIX;
243      const THREADDESTROYED       = FMOD_SYSTEM_CALLBACK_THREADDESTROYED;
244      const PREUPDATE             = FMOD_SYSTEM_CALLBACK_PREUPDATE;
245      const POSTUPDATE            = FMOD_SYSTEM_CALLBACK_POSTUPDATE;
246      const RECORDLISTCHANGED     = FMOD_SYSTEM_CALLBACK_RECORDLISTCHANGED;
247      const BUFFEREDNOMIX         = FMOD_SYSTEM_CALLBACK_BUFFEREDNOMIX;
248      const DEVICEREINITIALIZE    = FMOD_SYSTEM_CALLBACK_DEVICEREINITIALIZE;
249      const OUTPUTUNDERRUN        = FMOD_SYSTEM_CALLBACK_OUTPUTUNDERRUN;
250      const RECORDPOSITIONCHANGED = FMOD_SYSTEM_CALLBACK_RECORDPOSITIONCHANGED ;
251      const ALL                   = FMOD_SYSTEM_CALLBACK_ALL;
252  }
253}
254
255impl From<SystemCallbackMask> for FMOD_SYSTEM_CALLBACK_TYPE {
256    fn from(mask: SystemCallbackMask) -> Self {
257        mask.bits()
258    }
259}
260
261impl From<FMOD_SYSTEM_CALLBACK_TYPE> for SystemCallbackMask {
262    fn from(mask: FMOD_SYSTEM_CALLBACK_TYPE) -> Self {
263        Self::from_bits_truncate(mask)
264    }
265}
266
267/// Trait for this particular FMOD callback.
268///
269/// No `self` parameter is passed to the callback!
270#[allow(unused_variables)]
271pub trait SystemCallback {
272    /// Called from [`System::update`] when the enumerated list of devices has changed.
273    ///
274    /// Called from the main (calling) thread when set from the Core API or Studio API in synchronous mode,
275    /// and from the Studio Update Thread when in default / async mode.
276    fn device_list_changed(system: System, userdata: *mut c_void) -> Result<()> {
277        Ok(())
278    }
279
280    /// Deprecated.
281    #[deprecated]
282    fn device_lost(system: System, userdata: *mut c_void) -> Result<()> {
283        Ok(())
284    }
285
286    /// Called directly when a memory allocation fails.
287    fn memory_allocation_failed(
288        system: System,
289        file: &Utf8CStr,
290        size: c_int,
291        userdata: *mut c_void,
292    ) -> Result<()> {
293        Ok(())
294    }
295
296    /// Called from the game thread when a thread is created.
297    fn thread_created(
298        system: System,
299        handle: *mut c_void,
300        thread_name: &Utf8CStr,
301        userdata: *mut c_void,
302    ) -> Result<()> {
303        Ok(())
304    }
305
306    /// Deprecated.
307    #[deprecated]
308    fn bad_dsp_connection(system: System, userdata: *mut c_void) -> Result<()> {
309        Ok(())
310    }
311
312    /// Called from the mixer thread before it starts the next block.
313    fn premix(system: System, userdata: *mut c_void) -> Result<()> {
314        Ok(())
315    }
316
317    /// Called from the mixer thread after it finishes a block.
318    fn postmix(system: System, userdata: *mut c_void) -> Result<()> {
319        Ok(())
320    }
321
322    /// Called directly when an API function returns an error, including delayed async functions.
323    fn error(
324        system: System,
325        error_info: ErrorCallbackInfo<'_>,
326        userdata: *mut c_void,
327    ) -> Result<()> {
328        Ok(())
329    }
330
331    #[cfg(fmod_eq_2_2)]
332    fn mid_mix(system: System, userdata: *mut c_void) -> Result<()> {
333        Ok(())
334    }
335
336    /// Called from the game thread when a thread is destroyed.
337    fn thread_destroyed(
338        system: System,
339        handle: *mut c_void,
340        thread_name: &Utf8CStr,
341        userdata: *mut c_void,
342    ) -> Result<()> {
343        Ok(())
344    }
345
346    /// Called at start of [`System::update`] from the main (calling)
347    /// thread when set from the Core API or Studio API in synchronous mode,
348    /// and from the Studio Update Thread when in default / async mode.
349    fn pre_update(system: System, userdata: *mut c_void) -> Result<()> {
350        Ok(())
351    }
352
353    /// Called at end of [`System::update`] from the main (calling)
354    /// thread when set from the Core API or Studio API in synchronous mode,
355    /// and from the Studio Update Thread when in default / async mode.
356    fn post_update(system: System, userdata: *mut c_void) -> Result<()> {
357        Ok(())
358    }
359
360    /// Called from [`System::update`] when the enumerated list of recording devices has changed.
361    /// Called from the main (calling) thread when set from the Core API or Studio API in synchronous mode,
362    /// and from the Studio Update Thread when in default / async mode.
363    fn record_list_changed(system: System, userdata: *mut c_void) -> Result<()> {
364        Ok(())
365    }
366
367    /// Called from the feeder thread after audio was consumed from the ring buffer,
368    /// but not enough to allow another mix to run.
369    fn buffered_no_mix(system: System, userdata: *mut c_void) -> Result<()> {
370        Ok(())
371    }
372
373    /// Called from [`System::update`] when an output device is re-initialized.
374    /// Called from the main (calling) thread when set from the Core API or Studio API in synchronous mode,
375    /// and from the Studio Update Thread when in default / async mode.
376    fn device_reinitialize(
377        system: System,
378        output_type: OutputType,
379        driver_index: c_int,
380        userdata: *mut c_void,
381    ) -> Result<()> {
382        Ok(())
383    }
384
385    /// Called from the mixer thread when the device output attempts to read more samples than are available in the output buffer.
386    fn output_underrun(system: System, userdata: *mut c_void) -> Result<()> {
387        Ok(())
388    }
389
390    /// Called from the mixer thread when the System record position changed.
391    fn record_position_changed(
392        system: System,
393        sound: Sound,
394        record_position: c_int,
395        userdata: *mut c_void,
396    ) -> Result<()> {
397        Ok(())
398    }
399}
400
401unsafe extern "C" fn callback_impl<C: SystemCallback>(
402    system: *mut FMOD_SYSTEM,
403    callback_type: FMOD_SYSTEM_CALLBACK_TYPE,
404    command_data_1: *mut c_void,
405    command_data_2: *mut c_void,
406    userdata: *mut c_void,
407) -> FMOD_RESULT {
408    #[allow(deprecated)]
409    panic_wrapper(|| {
410        let system = unsafe { System::from_ffi(system) };
411        let result = match callback_type {
412            FMOD_SYSTEM_CALLBACK_DEVICELISTCHANGED => C::device_list_changed(system, userdata),
413            FMOD_SYSTEM_CALLBACK_DEVICELOST => C::device_lost(system, userdata),
414            FMOD_SYSTEM_CALLBACK_MEMORYALLOCATIONFAILED => {
415                let file = unsafe { Utf8CStr::from_ptr_unchecked(command_data_1.cast()) };
416                C::memory_allocation_failed(system, file, command_data_2 as c_int, userdata)
417            }
418            FMOD_SYSTEM_CALLBACK_THREADCREATED => {
419                let thread_name = unsafe { Utf8CStr::from_ptr_unchecked(command_data_2.cast()) };
420                C::thread_created(system, command_data_1, thread_name, userdata)
421            }
422            FMOD_SYSTEM_CALLBACK_BADDSPCONNECTION => C::bad_dsp_connection(system, userdata),
423            FMOD_SYSTEM_CALLBACK_PREMIX => C::premix(system, userdata),
424            FMOD_SYSTEM_CALLBACK_POSTMIX => C::postmix(system, userdata),
425            FMOD_SYSTEM_CALLBACK_ERROR => {
426                let error_info = unsafe { ErrorCallbackInfo::from_ffi(*command_data_1.cast()) };
427                C::error(system, error_info, userdata)
428            }
429            #[cfg(fmod_eq_2_2)]
430            FMOD_SYSTEM_CALLBACK_MIDMIX => C::mid_mix(system, userdata),
431            FMOD_SYSTEM_CALLBACK_THREADDESTROYED => {
432                let thread_name = unsafe { Utf8CStr::from_ptr_unchecked(command_data_2.cast()) };
433                C::thread_destroyed(system, command_data_1, thread_name, userdata)
434            }
435            FMOD_SYSTEM_CALLBACK_PREUPDATE => C::pre_update(system, userdata),
436            FMOD_SYSTEM_CALLBACK_POSTUPDATE => C::post_update(system, userdata),
437            FMOD_SYSTEM_CALLBACK_RECORDLISTCHANGED => C::record_list_changed(system, userdata),
438            FMOD_SYSTEM_CALLBACK_BUFFEREDNOMIX => C::buffered_no_mix(system, userdata),
439            FMOD_SYSTEM_CALLBACK_DEVICEREINITIALIZE => {
440                let output_type = OutputType::try_from(command_data_1 as FMOD_OUTPUTTYPE)
441                    .expect("invalid output type");
442                C::device_reinitialize(system, output_type, command_data_2 as c_int, userdata)
443            }
444            FMOD_SYSTEM_CALLBACK_OUTPUTUNDERRUN => C::output_underrun(system, userdata),
445            FMOD_SYSTEM_CALLBACK_RECORDPOSITIONCHANGED => {
446                let sound = unsafe { Sound::from_ffi(command_data_1.cast()) };
447                C::record_position_changed(system, sound, command_data_2 as c_int, userdata)
448            }
449            _ => {
450                eprintln!("warning: unknown callback type {callback_type}");
451                return FMOD_RESULT::FMOD_OK;
452            }
453        };
454        FMOD_RESULT::from_result(result)
455    })
456}
457
458impl System {
459    pub fn set_callback<C: SystemCallback>(&self, mask: SystemCallbackMask) -> Result<()> {
460        unsafe {
461            FMOD_System_SetCallback(self.inner.as_ptr(), Some(callback_impl::<C>), mask.into())
462                .to_result()
463        }
464    }
465}