fmod/studio/command_replay/
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
7use crate::{FmodResultExt, Result};
8use fmod_sys::*;
9use lanyard::Utf8CStr;
10use std::ffi::{c_char, c_float, c_int, c_uint, c_void};
11
12use crate::{
13    Guid, panic_wrapper,
14    studio::{Bank, CommandReplay, EventDescription, EventInstance, LoadBankFlags},
15};
16
17/// Trait for this particular FMOD callback.
18///
19/// No `self` parameter is passed to the callback!
20pub trait CreateInstanceCallback {
21    /// Callback for command replay event instance creation.
22    fn create_instance_callback(
23        replay: CommandReplay,
24        command_index: c_int,
25        description: EventDescription,
26        userdata: *mut c_void,
27    ) -> Result<Option<EventInstance>>;
28}
29
30unsafe extern "C" fn create_instance_impl<C: CreateInstanceCallback>(
31    replay: *mut FMOD_STUDIO_COMMANDREPLAY,
32    command_index: c_int,
33    event_description: *mut FMOD_STUDIO_EVENTDESCRIPTION,
34    event_instance: *mut *mut FMOD_STUDIO_EVENTINSTANCE,
35    userdata: *mut c_void,
36) -> FMOD_RESULT {
37    panic_wrapper(|| unsafe {
38        let replay = CommandReplay::from_ffi(replay);
39        let description = EventDescription::from_ffi(event_description);
40        let result = C::create_instance_callback(replay, command_index, description, userdata);
41        match result {
42            Ok(Some(instance)) => {
43                std::ptr::write(event_instance, instance.into());
44                FMOD_RESULT::FMOD_OK
45            }
46            Ok(None) => FMOD_RESULT::FMOD_OK,
47            Err(e) => e.into(),
48        }
49    })
50}
51
52/// Trait for this particular FMOD callback.
53///
54/// No `self` parameter is passed to the callback!
55pub trait FrameCallback {
56    /// Callback for when the command replay goes to the next frame.
57    fn frame_callback(
58        replay: CommandReplay,
59        command_index: c_int,
60        current_time: c_float,
61        userdata: *mut c_void,
62    ) -> Result<()>;
63}
64
65unsafe extern "C" fn frame_impl<C: FrameCallback>(
66    replay: *mut FMOD_STUDIO_COMMANDREPLAY,
67    command_index: c_int,
68    current_time: c_float,
69    userdata: *mut c_void,
70) -> FMOD_RESULT {
71    panic_wrapper(|| {
72        let replay = unsafe { CommandReplay::from_ffi(replay) };
73        let result = C::frame_callback(replay, command_index, current_time, userdata);
74        FMOD_RESULT::from_result(result)
75    })
76}
77
78/// Trait for this particular FMOD callback.
79///
80/// No `self` parameter is passed to the callback!
81pub trait LoadBankCallback {
82    /// Callback for command replay bank loading.
83    fn load_bank_callback(
84        replay: CommandReplay,
85        command_index: c_int,
86        guid: Option<Guid>,
87        filename: Option<&Utf8CStr>,
88        flags: LoadBankFlags,
89        userdata: *mut c_void,
90    ) -> Result<Option<Bank>>;
91}
92
93unsafe extern "C" fn load_bank_impl<C: LoadBankCallback>(
94    replay: *mut FMOD_STUDIO_COMMANDREPLAY,
95    command_index: c_int,
96    guid: *const FMOD_GUID,
97    filename: *const c_char,
98    flags: c_uint,
99    bank_ptr: *mut *mut FMOD_STUDIO_BANK,
100    userdata: *mut c_void,
101) -> FMOD_RESULT {
102    panic_wrapper(|| {
103        let replay = unsafe { CommandReplay::from_ffi(replay) };
104        let flags = LoadBankFlags::from(flags);
105        let guid = if guid.is_null() {
106            None
107        } else {
108            Some(unsafe { std::ptr::read(guid.cast()) })
109        };
110        let filename = if filename.is_null() {
111            None
112        } else {
113            Some(unsafe { Utf8CStr::from_ptr_unchecked(filename) })
114        };
115        let result = C::load_bank_callback(replay, command_index, guid, filename, flags, userdata);
116        match result {
117            Ok(Some(bank)) => {
118                unsafe {
119                    std::ptr::write(bank_ptr, bank.into());
120                }
121                FMOD_RESULT::FMOD_OK
122            }
123            Ok(None) => FMOD_RESULT::FMOD_OK,
124            Err(e) => e.into(),
125        }
126    })
127}
128
129impl CommandReplay {
130    /// Sets user data.
131    #[allow(clippy::not_unsafe_ptr_arg_deref)] // fmod doesn't dereference the passed in pointer, and the user dereferencing it is unsafe anyway
132    pub fn set_userdata(&self, userdata: *mut c_void) -> Result<()> {
133        unsafe { FMOD_Studio_CommandReplay_SetUserData(self.inner.as_ptr(), userdata).to_result() }
134    }
135
136    /// Retrieves user data.
137    pub fn get_userdata(&self) -> Result<*mut c_void> {
138        let mut userdata = std::ptr::null_mut();
139        unsafe {
140            FMOD_Studio_CommandReplay_GetUserData(self.inner.as_ptr(), &raw mut userdata)
141                .to_result()?;
142        }
143        Ok(userdata)
144    }
145
146    /// Sets the create event instance callback.
147    ///
148    /// The create instance callback is invoked each time a `EventDescription::createInstance` command is processed.
149    ///
150    /// The callback can either create a new event instance based on the callback parameters or skip creating the instance.
151    /// If the instance is not created then subsequent commands for the event instance will be ignored in the replay.
152    ///
153    /// If this callback is not set then the system will always create an event instance.
154    pub fn set_create_instance_callback<C: CreateInstanceCallback>(&self) -> Result<()> {
155        unsafe {
156            FMOD_Studio_CommandReplay_SetCreateInstanceCallback(
157                self.inner.as_ptr(),
158                Some(create_instance_impl::<C>),
159            )
160            .to_result()
161        }
162    }
163
164    /// Sets a callback that is issued each time the replay reaches a new frame.
165    pub fn set_frame_callback<C: FrameCallback>(&self) -> Result<()> {
166        unsafe {
167            FMOD_Studio_CommandReplay_SetFrameCallback(self.inner.as_ptr(), Some(frame_impl::<C>))
168                .to_result()
169        }
170    }
171
172    /// Sets the bank loading callback.
173    ///
174    /// The load bank callback is invoked whenever any of the Studio load bank functions are reached.
175    ///
176    /// This callback is required to be implemented to successfully replay `Studio::System::loadBankMemory` and `Studio::System::loadBankCustom` commands.
177    ///
178    /// The callback is responsible for loading the bank based on the callback parameters.
179    /// If the bank is not loaded subsequent commands which reference objects in the bank will fail.
180    ///
181    /// If this callback is not set then the system will attempt to load banks from file according to recorded
182    /// `Studio::System::loadBankFile` commands and skip other load commands.
183    pub fn set_load_bank_callback<C: LoadBankCallback>(&self) -> Result<()> {
184        unsafe {
185            FMOD_Studio_CommandReplay_SetLoadBankCallback(
186                self.inner.as_ptr(),
187                Some(load_bank_impl::<C>),
188            )
189            .to_result()
190        }
191    }
192}