craydate/
callbacks.rs

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/// The key used for each set of callbacks held in a `Callbacks` collection.
17///
18/// They key type would need to be passed to the C callback function in order to find the
19/// user-provided closure from the key.
20#[derive(Debug)]
21enum CallbackKey {
22  SoundSourceCompletion(usize),
23  MenuItem(usize),
24  SequenceFinished(usize),
25  HeadphoneChanged,
26}
27
28/// The arguments given to the C callback function for each type of function. These are used to find
29/// the user-provided closure.
30///
31/// The enum functions to indicate, in `CURRENT_CALLBACK`, which callback is currently being
32/// executed, or `None`.
33#[derive(Debug)]
34enum CallbackArguments {
35  /// Indicates that no callback is active.
36  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/// Holds ownership of the closure given when registering a system callback. Dropping this type
52/// would prevent the closure from ever being called. Typically held as a field as long as a
53/// callback is registered.
54#[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
68/// Provides an API to run a closure tied to a callback when the `SystemEventWatcher` reports a
69/// callback is ready to be run via `SystemEvent::Callback`. This type uses its type argument `T` to
70/// define the values that the caller will pass along to the closure when running it.
71pub 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  /// Construct a container for callbacks that will be passed `T` when they are run.
80  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  /// Attempt to run a callback, passing along a `T`.
110  ///
111  /// This should be called in response to a `SystemEvent::Callback` event occuring, which indicates
112  /// there is a callback available to be run.
113  ///
114  /// Returns true if the callback was found in this `Callbacks` collection and run, otherwise
115  /// returns false. A false return would mean the callback is in a different `Callbacks` collection
116  /// (or the callback was removed via dropping `RegisteredCallback` internally incorrectly).
117  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    // Waking the executors should cause them to poll() and receive back a `SystemEvent::Callback`,
221    // as set on the line above. They would run the callback via `Callbacks` then eventually yield
222    // back to us here.
223    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}