fmod/
userdata.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
7#[cfg(not(target_pointer_width = "64"))]
8compile_error!("fmod-oxide's userdata system is currently only supported on 64-bit platforms.");
9
10use std::{
11    any::Any,
12    sync::{Arc, RwLock},
13};
14
15use once_cell::sync::Lazy;
16use slotmap::{HopSlotMap, Key, KeyData};
17
18use crate::{
19    studio::{Bank, CommandReplay, EventDescription, EventInstance, System as StudioSystem},
20    ChannelControl, Dsp, DspConnection, Geometry, Reverb3D, Sound, SoundGroup, System,
21};
22
23#[derive(Default)]
24struct UserdataStorage {
25    // we use hopslotmap as we expect to iterate over all userdata often
26    // denseslotmap might be better? it has vec iter times
27    slotmap: HopSlotMap<UserdataKey, UserdataValue>,
28}
29
30slotmap::new_key_type! {
31    pub struct UserdataKey;
32}
33
34struct UserdataValue {
35    userdata: Userdata,
36    owner: HasUserdata,
37}
38
39#[derive(PartialEq)]
40pub(crate) enum HasUserdata {
41    StudioSystem(StudioSystem),
42    Bank(Bank),
43    EventDescription(EventDescription),
44    EventInstance(EventInstance),
45    CommandReplay(CommandReplay),
46    //
47    Reverb3D(Reverb3D),
48    SoundGroup(SoundGroup),
49    ChannelControl(ChannelControl),
50    Dsp(Dsp),
51    Sound(Sound),
52    Geometry(Geometry),
53    DspConnection(DspConnection),
54    System(System),
55}
56
57impl HasUserdata {
58    fn get_raw_userdata(&self) -> fmod_sys::Result<*mut std::ffi::c_void> {
59        match self {
60            HasUserdata::StudioSystem(s) => s.get_raw_userdata(),
61            HasUserdata::Bank(b) => b.get_raw_userdata(),
62            HasUserdata::EventDescription(e) => e.get_raw_userdata(),
63            HasUserdata::EventInstance(e) => e.get_raw_userdata(),
64            HasUserdata::CommandReplay(c) => c.get_raw_userdata(),
65            HasUserdata::Reverb3D(r) => r.get_raw_userdata(),
66            HasUserdata::SoundGroup(s) => s.get_raw_userdata(),
67            // may occasionally be called when the channel is lost, but this should return Err() in that case
68            HasUserdata::ChannelControl(c) => c.get_raw_userdata(),
69            HasUserdata::Dsp(d) => d.get_raw_userdata(),
70            HasUserdata::Sound(s) => s.get_raw_userdata(),
71            HasUserdata::Geometry(g) => g.get_raw_userdata(),
72            HasUserdata::DspConnection(c) => c.get_raw_userdata(),
73            HasUserdata::System(s) => s.get_raw_userdata(),
74        }
75    }
76
77    fn is_valid(&self, key: UserdataKey) -> bool {
78        match self {
79            HasUserdata::StudioSystem(s) => s.is_valid(),
80            HasUserdata::Bank(b) => b.is_valid(),
81            HasUserdata::EventDescription(e) => e.is_valid(),
82            HasUserdata::EventInstance(e) => e.is_valid(),
83            HasUserdata::CommandReplay(c) => c.is_valid(),
84            // System is ALWAYS valid
85            HasUserdata::System(_) => true,
86            // when released, get_raw_userdata will return null
87            // this is a bit of a hack though (and not very safe)
88            // this should almost never be called when released though, so it should be fine...?
89            _ => {
90                let userdata = self.get_raw_userdata();
91                userdata.is_ok_and(|ptr| ptr == key.into())
92            }
93        }
94    }
95}
96
97pub type Userdata = Arc<dyn Any + Send + Sync + 'static>;
98
99static STORAGE: Lazy<RwLock<UserdataStorage>> = Lazy::new(Default::default);
100
101pub(crate) fn insert_userdata(userdata: Userdata, owner: impl Into<HasUserdata>) -> UserdataKey {
102    let mut storage = STORAGE.write().unwrap();
103    storage.slotmap.insert(UserdataValue {
104        userdata,
105        owner: owner.into(),
106    })
107}
108
109pub(crate) fn remove_userdata(key: UserdataKey) -> Option<Userdata> {
110    let mut storage = STORAGE.write().unwrap();
111    storage.slotmap.remove(key).map(|v| v.userdata)
112}
113
114pub(crate) fn get_userdata(key: UserdataKey) -> Option<Userdata> {
115    let storage = STORAGE.read().unwrap();
116    storage.slotmap.get(key).map(|v| v.userdata.clone())
117}
118
119pub(crate) fn set_userdata(key: UserdataKey, userdata: Userdata) {
120    let mut storage = STORAGE.write().unwrap();
121    match storage.slotmap.get_mut(key) {
122        Some(v) => v.userdata = userdata,
123        None => eprintln!("Warning: userdata key does not exist!"),
124    }
125}
126
127pub(crate) fn cleanup_userdata() {
128    let mut storage = STORAGE.write().unwrap();
129    storage.slotmap.retain(|k, v| v.owner.is_valid(k));
130}
131
132pub(crate) fn clear_userdata() {
133    let mut storage = STORAGE.write().unwrap();
134    storage.slotmap.clear();
135}
136
137impl From<UserdataKey> for *mut std::ffi::c_void {
138    fn from(key: UserdataKey) -> Self {
139        key.data().as_ffi() as *mut std::ffi::c_void
140    }
141}
142
143impl From<*mut std::ffi::c_void> for UserdataKey {
144    fn from(ptr: *mut std::ffi::c_void) -> Self {
145        UserdataKey::from(KeyData::from_ffi(ptr as u64))
146    }
147}
148
149impl From<StudioSystem> for HasUserdata {
150    fn from(s: StudioSystem) -> Self {
151        HasUserdata::StudioSystem(s)
152    }
153}
154
155impl From<Bank> for HasUserdata {
156    fn from(b: Bank) -> Self {
157        HasUserdata::Bank(b)
158    }
159}
160
161impl From<EventDescription> for HasUserdata {
162    fn from(e: EventDescription) -> Self {
163        HasUserdata::EventDescription(e)
164    }
165}
166
167impl From<EventInstance> for HasUserdata {
168    fn from(e: EventInstance) -> Self {
169        HasUserdata::EventInstance(e)
170    }
171}
172
173impl From<CommandReplay> for HasUserdata {
174    fn from(c: CommandReplay) -> Self {
175        HasUserdata::CommandReplay(c)
176    }
177}
178
179impl From<Reverb3D> for HasUserdata {
180    fn from(r: Reverb3D) -> Self {
181        HasUserdata::Reverb3D(r)
182    }
183}
184
185impl From<SoundGroup> for HasUserdata {
186    fn from(s: SoundGroup) -> Self {
187        HasUserdata::SoundGroup(s)
188    }
189}
190
191impl From<ChannelControl> for HasUserdata {
192    fn from(c: ChannelControl) -> Self {
193        HasUserdata::ChannelControl(c)
194    }
195}
196
197impl From<Dsp> for HasUserdata {
198    fn from(d: Dsp) -> Self {
199        HasUserdata::Dsp(d)
200    }
201}
202
203impl From<Sound> for HasUserdata {
204    fn from(s: Sound) -> Self {
205        HasUserdata::Sound(s)
206    }
207}
208
209impl From<Geometry> for HasUserdata {
210    fn from(g: Geometry) -> Self {
211        HasUserdata::Geometry(g)
212    }
213}
214
215impl From<DspConnection> for HasUserdata {
216    fn from(c: DspConnection) -> Self {
217        HasUserdata::DspConnection(c)
218    }
219}
220
221impl From<System> for HasUserdata {
222    fn from(s: System) -> Self {
223        HasUserdata::System(s)
224    }
225}