classicube_helpers/
tick.rs

1use std::{
2    cell::{Cell, RefCell},
3    rc::{Rc, Weak},
4};
5
6use classicube_sys::{ScheduledTask, ScheduledTask_Add};
7
8use crate::callback_handler::CallbackHandler;
9
10thread_local!(
11    static CALLBACK_REGISTERED: Cell<bool> = const { Cell::new(false) };
12);
13
14thread_local!(
15    static TICK_CALLBACK_HANDLERS: RefCell<Vec<Weak<RefCell<CallbackHandler<TickEvent>>>>> =
16        const { RefCell::new(Vec::new()) };
17);
18
19#[derive(Debug)]
20pub struct TickEvent {
21    pub task: *mut ScheduledTask,
22}
23
24pub struct TickEventHandler {
25    callback_handler: Rc<RefCell<CallbackHandler<TickEvent>>>,
26}
27
28impl TickEventHandler {
29    #[must_use]
30    pub fn new() -> Self {
31        Self {
32            callback_handler: Rc::new(RefCell::new(CallbackHandler::new())),
33        }
34    }
35
36    pub fn on<F>(&mut self, callback: F)
37    where
38        F: FnMut(&TickEvent),
39        F: 'static,
40    {
41        self.callback_handler.borrow_mut().on(callback);
42
43        unsafe {
44            self.register_listener();
45        }
46    }
47
48    fn check_register_detour() {
49        if !CALLBACK_REGISTERED.get() {
50            CALLBACK_REGISTERED.set(true);
51
52            unsafe {
53                // modify accumulator so that we match frame rate
54                ScheduledTask_Add(0.0, Some(Self::hook));
55            }
56        }
57    }
58
59    unsafe fn register_listener(&mut self) {
60        Self::check_register_detour();
61
62        let weak = Rc::downgrade(&self.callback_handler);
63
64        TICK_CALLBACK_HANDLERS.with(|callback_handlers| {
65            for callback_handler in &*callback_handlers.borrow() {
66                if callback_handler.ptr_eq(&weak) {
67                    // we already have a handler registered
68                    return;
69                }
70            }
71
72            callback_handlers.borrow_mut().push(weak);
73        });
74    }
75
76    unsafe fn unregister_listener(&mut self) {
77        _ = TICK_CALLBACK_HANDLERS.try_with(|callback_handlers| {
78            let mut callback_handlers = callback_handlers.borrow_mut();
79
80            let my_weak = Rc::downgrade(&self.callback_handler);
81
82            let mut i = 0;
83            while i != callback_handlers.len() {
84                // if it's our weak, remove it
85                if callback_handlers[i].ptr_eq(&my_weak) {
86                    callback_handlers.remove(i);
87                } else {
88                    i += 1;
89                }
90            }
91        });
92    }
93
94    extern "C" fn hook(task: *mut ScheduledTask) {
95        let task = unsafe { &mut *task };
96        // run once per frame
97        task.accumulator = -0.0001;
98
99        TICK_CALLBACK_HANDLERS.with(|callback_handlers| {
100            let callback_handlers = callback_handlers.borrow_mut();
101            for weak_callback_handler in &*callback_handlers {
102                if let Some(callback_handler) = weak_callback_handler.upgrade() {
103                    callback_handler
104                        .borrow_mut()
105                        .handle_event(&TickEvent { task });
106                }
107            }
108        });
109    }
110}
111
112impl Drop for TickEventHandler {
113    fn drop(&mut self) {
114        unsafe {
115            self.unregister_listener();
116        }
117    }
118}
119
120impl Default for TickEventHandler {
121    fn default() -> Self {
122        Self::new()
123    }
124}