fmod/studio/command_replay/
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_char, c_float, c_int, c_uint, c_void};
10
11use crate::{
12    studio::{Bank, CommandReplay, EventDescription, EventInstance, LoadBankFlags},
13    Guid,
14};
15
16#[cfg(feature = "userdata-abstraction")]
17use crate::userdata::{get_userdata, insert_userdata, set_userdata, Userdata};
18
19#[cfg(feature = "userdata-abstraction")]
20pub trait CreateInstanceCallback {
21    fn create_instance_callback(
22        replay: CommandReplay,
23        command_index: c_int,
24        description: EventDescription,
25        userdata: Option<Userdata>,
26    ) -> Result<Option<EventInstance>>;
27}
28
29#[cfg(not(feature = "userdata-abstraction"))]
30pub trait CreateInstanceCallback {
31    fn create_instance_callback(
32        replay: CommandReplay,
33        command_index: c_int,
34        description: EventDescription,
35        userdata: *mut c_void,
36    ) -> Result<Option<EventInstance>>;
37}
38
39unsafe extern "C" fn create_instance_impl<C: CreateInstanceCallback>(
40    replay: *mut FMOD_STUDIO_COMMANDREPLAY,
41    command_index: c_int,
42    event_description: *mut FMOD_STUDIO_EVENTDESCRIPTION,
43    event_instance: *mut *mut FMOD_STUDIO_EVENTINSTANCE,
44    userdata: *mut c_void,
45) -> FMOD_RESULT {
46    #[cfg(feature = "userdata-abstraction")]
47    let userdata = get_userdata(userdata.into());
48
49    unsafe {
50        let replay = CommandReplay::from(replay);
51        let description = EventDescription::from(event_description);
52        let result = C::create_instance_callback(replay, command_index, description, userdata);
53        match result {
54            Ok(Some(instance)) => {
55                std::ptr::write(event_instance, instance.into());
56                FMOD_RESULT::FMOD_OK
57            }
58            Ok(None) => FMOD_RESULT::FMOD_OK,
59            Err(e) => e.into(),
60        }
61    }
62}
63
64#[cfg(feature = "userdata-abstraction")]
65pub trait FrameCallback {
66    fn frame_callback(
67        replay: CommandReplay,
68        command_index: c_int,
69        current_time: c_float,
70        userdata: Option<Userdata>,
71    ) -> Result<()>;
72}
73
74#[cfg(not(feature = "userdata-abstraction"))]
75pub trait FrameCallback {
76    fn frame_callback(
77        replay: CommandReplay,
78        command_index: c_int,
79        current_time: c_float,
80        userdata: *mut c_void,
81    ) -> Result<()>;
82}
83
84unsafe extern "C" fn frame_impl<C: FrameCallback>(
85    replay: *mut FMOD_STUDIO_COMMANDREPLAY,
86    command_index: c_int,
87    current_time: c_float,
88    userdata: *mut c_void,
89) -> FMOD_RESULT {
90    #[cfg(feature = "userdata-abstraction")]
91    let userdata = get_userdata(userdata.into());
92
93    let replay = CommandReplay::from(replay);
94    C::frame_callback(replay, command_index, current_time, userdata).into()
95}
96
97#[cfg(feature = "userdata-abstraction")]
98pub trait LoadBankCallback {
99    fn load_bank_callback(
100        replay: CommandReplay,
101        command_index: c_int,
102        guid: Option<Guid>,
103        filename: Option<&Utf8CStr>,
104        flags: LoadBankFlags,
105        userdata: Option<Userdata>,
106    ) -> Result<Option<Bank>>;
107}
108
109#[cfg(not(feature = "userdata-abstraction"))]
110pub trait LoadBankCallback {
111    fn load_bank_callback(
112        replay: CommandReplay,
113        command_index: c_int,
114        guid: Option<Guid>,
115        filename: Option<&Utf8CStr>,
116        flags: LoadBankFlags,
117        userdata: *mut c_void,
118    ) -> Result<Option<Bank>>;
119}
120
121unsafe extern "C" fn load_bank_impl<C: LoadBankCallback>(
122    replay: *mut FMOD_STUDIO_COMMANDREPLAY,
123    command_index: c_int,
124    guid: *const FMOD_GUID,
125    filename: *const c_char,
126    flags: c_uint,
127    bank_ptr: *mut *mut FMOD_STUDIO_BANK,
128    userdata: *mut c_void,
129) -> FMOD_RESULT {
130    #[cfg(feature = "userdata-abstraction")]
131    let userdata = get_userdata(userdata.into());
132
133    let replay = CommandReplay::from(replay);
134    let flags = LoadBankFlags::from(flags);
135    let guid = if guid.is_null() {
136        None
137    } else {
138        Some(unsafe { std::ptr::read(guid.cast()) })
139    };
140    let filename = if filename.is_null() {
141        None
142    } else {
143        Some(unsafe { Utf8CStr::from_ptr_unchecked(filename) })
144    };
145    let result = C::load_bank_callback(replay, command_index, guid, filename, flags, userdata);
146    match result {
147        Ok(Some(bank)) => {
148            unsafe {
149                std::ptr::write(bank_ptr, bank.into());
150            }
151            FMOD_RESULT::FMOD_OK
152        }
153        Ok(None) => FMOD_RESULT::FMOD_OK,
154        Err(e) => e.into(),
155    }
156}
157
158#[cfg(feature = "userdata-abstraction")]
159impl CommandReplay {
160    pub fn set_userdata(&self, userdata: Userdata) -> Result<()> {
161        let pointer = self.get_raw_userdata()?;
162        if pointer.is_null() {
163            let key = insert_userdata(userdata, *self);
164            self.set_raw_userdata(key.into())?;
165        } else {
166            set_userdata(pointer.into(), userdata);
167        }
168
169        Ok(())
170    }
171
172    pub fn get_userdata(&self) -> Result<Option<Userdata>> {
173        let pointer = self.get_raw_userdata()?;
174        Ok(get_userdata(pointer.into()))
175    }
176}
177
178impl CommandReplay {
179    #[allow(clippy::not_unsafe_ptr_arg_deref)] // fmod doesn't dereference the passed in pointer, and the user dereferencing it is unsafe anyway
180    pub fn set_raw_userdata(&self, userdata: *mut c_void) -> Result<()> {
181        unsafe { FMOD_Studio_CommandReplay_SetUserData(self.inner, userdata).to_result() }
182    }
183
184    pub fn get_raw_userdata(&self) -> Result<*mut c_void> {
185        let mut userdata = std::ptr::null_mut();
186        unsafe {
187            FMOD_Studio_CommandReplay_GetUserData(self.inner, &mut userdata).to_result()?;
188        }
189        Ok(userdata)
190    }
191
192    pub fn set_create_instance_callback<C: CreateInstanceCallback>(&self) -> Result<()> {
193        unsafe {
194            FMOD_Studio_CommandReplay_SetCreateInstanceCallback(
195                self.inner,
196                Some(create_instance_impl::<C>),
197            )
198            .to_result()
199        }
200    }
201
202    pub fn set_frame_callback<C: FrameCallback>(&self) -> Result<()> {
203        unsafe {
204            FMOD_Studio_CommandReplay_SetFrameCallback(self.inner, Some(frame_impl::<C>))
205                .to_result()
206        }
207    }
208
209    pub fn set_load_bank_callback<C: LoadBankCallback>(&self) -> Result<()> {
210        unsafe {
211            FMOD_Studio_CommandReplay_SetLoadBankCallback(self.inner, Some(load_bank_impl::<C>))
212                .to_result()
213        }
214    }
215}