1use alloc::boxed::Box;
2use alloc::collections::BTreeMap;
3use alloc::rc::{Rc, Weak};
4use alloc::vec::Vec;
5use core::cell::RefCell;
6use core::ffi::c_void;
7
8use crate::capi_state::CApiState;
9use crate::ctypes::*;
10use crate::executor::Executor;
11use crate::sound::headphone_state::HeadphoneState;
12use crate::system_event::SystemEvent;
13
14static mut CURRENT_CALLBACK: CallbackArguments = CallbackArguments::None;
15
16#[derive(Debug)]
21enum CallbackKey {
22 SoundSourceCompletion(usize),
23 MenuItem(usize),
24 SequenceFinished(usize),
25 HeadphoneChanged,
26}
27
28#[derive(Debug)]
34enum CallbackArguments {
35 None,
37 SoundSourceCompletion(usize),
38 MenuItem(usize),
39 SequenceFinished(usize),
40 HeadphoneChanged(HeadphoneState),
41}
42impl CallbackArguments {
43 fn is_none(&self) -> bool {
44 match self {
45 CallbackArguments::None => true,
46 _ => false,
47 }
48 }
49}
50
51#[must_use]
55#[derive(Debug)]
56pub(crate) struct RegisteredCallback {
57 cb_type: Option<CallbackKey>,
58 weak_removed: Weak<RefCell<Vec<CallbackKey>>>,
59}
60impl Drop for RegisteredCallback {
61 fn drop(&mut self) {
62 if let Some(removed) = self.weak_removed.upgrade() {
63 removed.borrow_mut().push(self.cb_type.take().unwrap())
64 }
65 }
66}
67
68pub struct Callbacks<T> {
72 sound_source_completion_callbacks: BTreeMap<usize, Box<dyn Fn(T)>>,
73 menu_item_callbacks: BTreeMap<usize, Box<dyn Fn(T)>>,
74 sequence_finished_callbacks: BTreeMap<usize, Box<dyn Fn(T)>>,
75 headphone_changed_callback: Option<Box<dyn Fn(HeadphoneState, T)>>,
76 removed: Rc<RefCell<Vec<CallbackKey>>>,
77}
78impl<T> Callbacks<T> {
79 pub fn new() -> Self {
81 Callbacks {
82 sound_source_completion_callbacks: BTreeMap::new(),
83 menu_item_callbacks: BTreeMap::new(),
84 sequence_finished_callbacks: BTreeMap::new(),
85 headphone_changed_callback: None,
86 removed: Rc::new(RefCell::new(Vec::new())),
87 }
88 }
89
90 fn gc(&mut self) {
91 for r in core::mem::take(&mut self.removed).borrow().iter() {
92 match r {
93 CallbackKey::SoundSourceCompletion(key) => {
94 self.sound_source_completion_callbacks.remove(key);
95 }
96 CallbackKey::MenuItem(key) => {
97 self.menu_item_callbacks.remove(key);
98 }
99 CallbackKey::SequenceFinished(key) => {
100 self.sequence_finished_callbacks.remove(key);
101 }
102 CallbackKey::HeadphoneChanged => {
103 self.headphone_changed_callback = None;
104 }
105 };
106 }
107 }
108
109 pub fn run(&mut self, t: T) -> bool {
118 self.gc();
119
120 match unsafe { &CURRENT_CALLBACK } {
121 CallbackArguments::None => false,
122 CallbackArguments::SoundSourceCompletion(key) => {
123 let cb = self.sound_source_completion_callbacks.get(key);
124 cb.and_then(|f| Some(f(t))).is_some()
125 }
126 CallbackArguments::MenuItem(key) => {
127 let cb = self.menu_item_callbacks.get(key);
128 cb.and_then(|f| Some(f(t))).is_some()
129 }
130 CallbackArguments::SequenceFinished(key) => {
131 let cb = self.sequence_finished_callbacks.get(key);
132 cb.and_then(|f| Some(f(t))).is_some()
133 }
134 CallbackArguments::HeadphoneChanged(state) => {
135 let cb = self.headphone_changed_callback.as_ref();
136 cb.and_then(|f| Some(f(*state, t))).is_some()
137 }
138 }
139 }
140}
141
142impl<T> Callbacks<T> {
143 #[must_use]
144 pub(crate) fn add_sound_source_completion(
145 &mut self,
146 key: usize,
147 cb: impl Fn(T) + 'static,
148 ) -> (unsafe extern "C" fn(*mut CSoundSource), RegisteredCallback) {
149 let r = self.sound_source_completion_callbacks.insert(key, Box::new(cb));
150 assert!(r.is_none());
151 (
152 CCallbacks::on_sound_source_completion_callback,
153 RegisteredCallback {
154 cb_type: Some(CallbackKey::SoundSourceCompletion(key)),
155 weak_removed: Rc::downgrade(&self.removed),
156 },
157 )
158 }
159
160 #[must_use]
161 pub(crate) fn add_menu_item(
162 &mut self,
163 key: usize,
164 cb: impl Fn(T) + 'static,
165 ) -> (unsafe extern "C" fn(*mut c_void), RegisteredCallback) {
166 let r = self.menu_item_callbacks.insert(key, Box::new(cb));
167 assert!(r.is_none());
168 (
169 CCallbacks::on_menu_item_callback,
170 RegisteredCallback {
171 cb_type: Some(CallbackKey::MenuItem(key)),
172 weak_removed: Rc::downgrade(&self.removed),
173 },
174 )
175 }
176
177 #[must_use]
178 pub(crate) fn add_sequence_finished(
179 &mut self,
180 key: usize,
181 cb: impl Fn(T) + 'static,
182 ) -> (
183 unsafe extern "C" fn(*mut CSoundSequence, *mut c_void),
184 RegisteredCallback,
185 ) {
186 let r = self.sound_source_completion_callbacks.insert(key, Box::new(cb));
187 assert!(r.is_none());
188 (
189 CCallbacks::on_sequence_finished_callback,
190 RegisteredCallback {
191 cb_type: Some(CallbackKey::SequenceFinished(key)),
192 weak_removed: Rc::downgrade(&self.removed),
193 },
194 )
195 }
196
197 #[must_use]
198 pub(crate) fn add_headphone_change(
199 &mut self,
200 cb: impl Fn(HeadphoneState, T) + 'static,
201 ) -> (unsafe extern "C" fn(i32, i32), RegisteredCallback) {
202 assert!(self.headphone_changed_callback.is_none());
203 self.headphone_changed_callback = Some(Box::new(cb));
204 (
205 CCallbacks::on_headphone_change_callback,
206 RegisteredCallback {
207 cb_type: Some(CallbackKey::HeadphoneChanged),
208 weak_removed: Rc::downgrade(&self.removed),
209 },
210 )
211 }
212}
213
214struct CCallbacks;
215impl CCallbacks {
216 fn run_callback(callback_args: CallbackArguments) {
217 assert!(unsafe { CURRENT_CALLBACK.is_none() });
218 unsafe { CURRENT_CALLBACK = callback_args };
219 CApiState::get().add_system_event(SystemEvent::Callback);
220 Executor::wake_system_wakers(CApiState::get().executor);
224 unsafe { CURRENT_CALLBACK = CallbackArguments::None };
225 }
226
227 pub extern "C" fn on_sound_source_completion_callback(key: *mut CSoundSource) {
228 Self::run_callback(CallbackArguments::SoundSourceCompletion(key as usize))
229 }
230
231 pub extern "C" fn on_menu_item_callback(key: *mut c_void) {
232 Self::run_callback(CallbackArguments::MenuItem(key as usize))
233 }
234
235 pub extern "C" fn on_sequence_finished_callback(seq: *mut CSoundSequence, _data: *mut c_void) {
236 Self::run_callback(CallbackArguments::SequenceFinished(seq as usize))
237 }
238
239 pub extern "C" fn on_headphone_change_callback(headphones: i32, mic: i32) {
240 Self::run_callback(CallbackArguments::HeadphoneChanged(HeadphoneState::new(
241 headphones != 0,
242 mic != 0,
243 )))
244 }
245}