fmod/studio/event_instance/
callback.rs

1// Copyright (c) 2024 Lily 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_void;
10
11use crate::{
12    studio::{
13        EventCallbackMask, EventInstance, PluginInstanceProperties, ProgrammerSoundProperties,
14        TimelineBeatProperties, TimelineMarkerProperties, TimelineNestedBeatProperties,
15    },
16    Sound,
17};
18
19#[cfg(feature = "userdata-abstraction")]
20use crate::userdata::{get_userdata, insert_userdata, set_userdata, Userdata};
21
22#[allow(unused_variables)]
23pub trait EventInstanceCallback {
24    fn created(event: EventInstance) -> Result<()> {
25        Ok(())
26    }
27
28    fn destroyed(event: EventInstance) -> Result<()> {
29        Ok(())
30    }
31
32    fn starting(event: EventInstance) -> Result<()> {
33        Ok(())
34    }
35
36    fn started(event: EventInstance) -> Result<()> {
37        Ok(())
38    }
39
40    fn restarted(event: EventInstance) -> Result<()> {
41        Ok(())
42    }
43
44    fn stopped(event: EventInstance) -> Result<()> {
45        Ok(())
46    }
47
48    fn start_failed(event: EventInstance) -> Result<()> {
49        Ok(())
50    }
51
52    fn create_programmer_sound(
53        event: EventInstance,
54        sound_props: ProgrammerSoundProperties<'_>,
55    ) -> Result<()> {
56        Ok(())
57    }
58
59    fn destroy_programmer_sound(
60        event: EventInstance,
61        sound_props: ProgrammerSoundProperties<'_>,
62    ) -> Result<()> {
63        Ok(())
64    }
65
66    fn plugin_created(event: EventInstance, plugin_props: PluginInstanceProperties) -> Result<()> {
67        Ok(())
68    }
69
70    fn plugin_destroyed(
71        event: EventInstance,
72        plugin_props: PluginInstanceProperties,
73    ) -> Result<()> {
74        Ok(())
75    }
76
77    fn timeline_marker(
78        event: EventInstance,
79        timeline_props: TimelineMarkerProperties,
80    ) -> Result<()> {
81        Ok(())
82    }
83
84    fn timeline_beat(event: EventInstance, timeline_beat: TimelineBeatProperties) -> Result<()> {
85        Ok(())
86    }
87
88    fn sound_played(event: EventInstance, sound: Sound) -> Result<()> {
89        Ok(())
90    }
91
92    fn sound_stopped(event: EventInstance, sound: Sound) -> Result<()> {
93        Ok(())
94    }
95
96    fn real_to_virtual(event: EventInstance) -> Result<()> {
97        Ok(())
98    }
99
100    fn virtual_to_real(event: EventInstance) -> Result<()> {
101        Ok(())
102    }
103
104    fn start_event_command(event: EventInstance, new_event: EventInstance) -> Result<()> {
105        Ok(())
106    }
107
108    fn nested_timeline_beat(
109        event: EventInstance,
110        timeline_props: TimelineNestedBeatProperties,
111    ) -> Result<()> {
112        Ok(())
113    }
114}
115
116pub(crate) unsafe extern "C" fn event_callback_impl<C: EventInstanceCallback>(
117    kind: FMOD_STUDIO_EVENT_CALLBACK_TYPE,
118    event: *mut FMOD_STUDIO_EVENTINSTANCE,
119    parameters: *mut c_void,
120) -> FMOD_RESULT {
121    // FIXME handle panics
122    let event = EventInstance::from(event);
123    let result = match kind {
124        FMOD_STUDIO_EVENT_CALLBACK_CREATED => C::created(event),
125        FMOD_STUDIO_EVENT_CALLBACK_DESTROYED => C::destroyed(event),
126        FMOD_STUDIO_EVENT_CALLBACK_STARTING => C::starting(event),
127        FMOD_STUDIO_EVENT_CALLBACK_STARTED => C::started(event),
128        FMOD_STUDIO_EVENT_CALLBACK_RESTARTED => C::restarted(event),
129        FMOD_STUDIO_EVENT_CALLBACK_STOPPED => C::stopped(event),
130        FMOD_STUDIO_EVENT_CALLBACK_START_FAILED => C::start_failed(event),
131        FMOD_STUDIO_EVENT_CALLBACK_CREATE_PROGRAMMER_SOUND => {
132            let props = unsafe {
133                let props = &mut *parameters.cast::<FMOD_STUDIO_PROGRAMMER_SOUND_PROPERTIES>();
134                ProgrammerSoundProperties {
135                    name: Utf8CStr::from_ptr_unchecked(props.name).to_cstring(),
136                    sound: &mut *std::ptr::addr_of_mut!(props.sound).cast(),
137                    subsound_index: &mut props.subsoundIndex,
138                }
139            };
140            C::create_programmer_sound(event, props)
141        }
142        FMOD_STUDIO_EVENT_CALLBACK_DESTROY_PROGRAMMER_SOUND => {
143            let props = unsafe {
144                let props = &mut *parameters.cast::<FMOD_STUDIO_PROGRAMMER_SOUND_PROPERTIES>();
145                ProgrammerSoundProperties {
146                    name: Utf8CStr::from_ptr_unchecked(props.name).to_cstring(),
147                    sound: &mut *std::ptr::addr_of_mut!(props.sound).cast(),
148                    subsound_index: &mut props.subsoundIndex,
149                }
150            };
151            C::destroy_programmer_sound(event, props)
152        }
153        FMOD_STUDIO_EVENT_CALLBACK_PLUGIN_CREATED => {
154            let props = unsafe { PluginInstanceProperties::from_ffi(*parameters.cast()) };
155            C::plugin_created(event, props)
156        }
157        FMOD_STUDIO_EVENT_CALLBACK_PLUGIN_DESTROYED => {
158            let props = unsafe { PluginInstanceProperties::from_ffi(*parameters.cast()) };
159            C::plugin_destroyed(event, props)
160        }
161        FMOD_STUDIO_EVENT_CALLBACK_TIMELINE_MARKER => {
162            let props = unsafe { TimelineMarkerProperties::from_ffi(*parameters.cast()) };
163            C::timeline_marker(event, props)
164        }
165        FMOD_STUDIO_EVENT_CALLBACK_TIMELINE_BEAT => {
166            let props = unsafe {
167                TimelineBeatProperties::from(
168                    *parameters.cast::<FMOD_STUDIO_TIMELINE_BEAT_PROPERTIES>(),
169                )
170            };
171            C::timeline_beat(event, props)
172        }
173        FMOD_STUDIO_EVENT_CALLBACK_SOUND_PLAYED => {
174            let sound = parameters.cast::<FMOD_SOUND>().into();
175            C::sound_played(event, sound)
176        }
177        FMOD_STUDIO_EVENT_CALLBACK_SOUND_STOPPED => {
178            let sound = parameters.cast::<FMOD_SOUND>().into();
179            C::sound_stopped(event, sound)
180        }
181        FMOD_STUDIO_EVENT_CALLBACK_REAL_TO_VIRTUAL => C::real_to_virtual(event),
182        FMOD_STUDIO_EVENT_CALLBACK_VIRTUAL_TO_REAL => C::virtual_to_real(event),
183        FMOD_STUDIO_EVENT_CALLBACK_START_EVENT_COMMAND => {
184            let new_event = EventInstance::from(parameters.cast());
185            C::start_event_command(event, new_event)
186        }
187        FMOD_STUDIO_EVENT_CALLBACK_NESTED_TIMELINE_BEAT => {
188            let props = unsafe {
189                TimelineNestedBeatProperties::from(
190                    *parameters.cast::<FMOD_STUDIO_TIMELINE_NESTED_BEAT_PROPERTIES>(),
191                )
192            };
193            C::nested_timeline_beat(event, props)
194        }
195        _ => {
196            eprintln!("warning: unknown event callback type {kind}");
197            return FMOD_RESULT::FMOD_OK;
198        }
199    };
200    result.into()
201}
202
203#[cfg(feature = "userdata-abstraction")]
204impl EventInstance {
205    pub fn set_userdata(&self, userdata: Userdata) -> Result<()> {
206        let pointer = self.get_raw_userdata()?;
207        let desc_pointer = self.get_description()?.get_raw_userdata()?;
208
209        // if the pointer is null or the same as the description pointer, insert the userdata
210        if pointer.is_null() || pointer == desc_pointer {
211            let key = insert_userdata(userdata, *self);
212            self.set_raw_userdata(key.into())?;
213        // if not then we already have a key, so just set the userdata
214        } else {
215            set_userdata(pointer.into(), userdata);
216        }
217
218        Ok(())
219    }
220
221    pub fn get_userdata(&self) -> Result<Option<Userdata>> {
222        let pointer = self.get_raw_userdata()?;
223        Ok(get_userdata(pointer.into()))
224    }
225}
226
227impl EventInstance {
228    #[allow(clippy::not_unsafe_ptr_arg_deref)] // fmod doesn't dereference the passed in pointer, and the user dereferencing it is unsafe anyway
229    pub fn set_raw_userdata(&self, userdata: *mut c_void) -> Result<()> {
230        unsafe { FMOD_Studio_EventInstance_SetUserData(self.inner, userdata).to_result() }
231    }
232
233    pub fn get_raw_userdata(&self) -> Result<*mut c_void> {
234        let mut userdata = std::ptr::null_mut();
235        unsafe {
236            FMOD_Studio_EventInstance_GetUserData(self.inner, &mut userdata).to_result()?;
237        }
238        Ok(userdata)
239    }
240
241    pub fn set_callback<C: EventInstanceCallback>(&self, mask: EventCallbackMask) -> Result<()> {
242        unsafe {
243            FMOD_Studio_EventInstance_SetCallback(
244                self.inner,
245                Some(event_callback_impl::<C>),
246                mask.into(),
247            )
248            .to_result()
249        }
250    }
251}