panda/callbacks/
export.rs

1/// Declare and export a plugin-to-plugin (PPP) callback, which allows other plugins
2/// to add callbacks for this plugin to run.
3///
4/// ## Example
5///
6/// For example, if you were writing a Rust plugin that would use a callback that 
7/// runs every other basic block, you could declare and use two callbacks like so:
8///
9/// ```
10/// use panda::{Callback, export_ppp_callback};
11/// use panda::prelude::*;
12/// 
13/// export_ppp_callback! {
14///     pub(crate) fn on_every_even_block(cpu: &mut CPUState);
15///     pub(crate) fn on_every_odd_block(cpu: &mut CPUState);
16/// }
17/// 
18/// #[panda::init]
19/// fn init() {
20///     let mut i = 0;
21///     Callback::new().before_block_exec(move |cpu, _| {
22///         if i % 2 == 0 {
23///             on_every_even_block::trigger(cpu);
24///         } else {
25///             on_every_odd_block::trigger(cpu);
26///         }
27///
28///         i += 1;
29///     });
30/// }
31/// ```
32///
33/// (For further usage see `panda-rs/examples/ppp_callback_export.rs`)
34///
35/// The return type of each callback can be any which implements [`CallbackReturn`], a 
36/// trait which describes how to fold all the return values into a single return value
37/// to be returned by `<callback_name>::trigger(...)`. For example a callback that returns
38/// a `bool` will return `true` if any of the callbacks return `true`, and will only return
39/// false if every registered callback returns false.
40///
41/// If you wish to alter this behavior for existing types, use the [newtype pattern], 
42/// which will allow you to provide your own implementation by implementing the trait.
43///
44/// [newtype pattern]: https://doc.rust-lang.org/rust-by-example/generics/new_types.html
45///
46/// **Note:** All callback arguments and return values are expected to be FFI safe. If rustc 
47/// emits a warning for a given type, it is very likely it is not compatible with the 
48/// C ABI. In order to have your own custom types be FFI-safe, they should be marked 
49/// either `#[repr(transparent)]` or `#[repr(C)]` or should be treated as opaque types 
50/// (and thus should only be created and accessed within the same plugin and passed as
51/// references).
52#[macro_export]
53macro_rules! export_ppp_callback {
54    {
55        $(
56            $vis:vis fn $cb_name:ident (
57                $(
58                    $arg:ident : $arg_ty:ty
59                ),* $(,)?
60            ) $(-> $ret_ty:ty)?;
61        )*
62    } => {$(
63        $vis mod $cb_name {
64            use super::*;
65
66            use ::std::ffi::c_void;
67
68            #[derive(PartialEq)]
69            struct PppContextInternal(*mut c_void);
70
71            unsafe impl Sync for PppContextInternal {}
72            unsafe impl Send for PppContextInternal {}
73
74            $vis type CallbackType = extern "C" fn($( $arg_ty ),*) $(-> $ret_ty)?;
75            $vis type CallbackTypeWithContext = extern "C" fn(*mut c_void, $( $arg_ty ),*) $(-> $ret_ty)?;
76
77            extern "C" fn trampoline(context: *mut c_void, $($arg : $arg_ty),*) $(-> $ret_ty)? {
78                let cb: CallbackType = unsafe {
79                    ::core::mem::transmute(context)
80                };
81
82                cb($($arg),*)
83            }
84
85            #[export_name = concat!("ppp_add_cb_", stringify!($cb_name))]
86            $vis extern "C" fn add_callback(callback: CallbackType) {
87                unsafe {
88                    add_callback_with_context(trampoline, ::core::mem::transmute(callback))
89                }
90            }
91
92            #[export_name = concat!("ppp_add_cb_", stringify!($cb_name), "_with_context")]
93            $vis extern "C" fn add_callback_with_context(
94                callback: CallbackTypeWithContext,
95                context: *mut c_void,
96            ) {
97                CALLBACKS
98                    .lock()
99                    .unwrap()
100                    .push((callback, PppContextInternal(context)));
101            }
102
103            #[export_name = concat!("ppp_remove_cb_", stringify!($cb_name))]
104            $vis extern "C" fn remove_callback(callback: CallbackType) -> bool {
105                unsafe {
106                    remove_callback_with_context(trampoline, ::core::mem::transmute(callback))
107                }
108            }
109
110            #[export_name = concat!("ppp_remove_cb_", stringify!($cb_name), "_with_context")]
111            $vis extern "C" fn remove_callback_with_context(
112                callback: CallbackTypeWithContext,
113                context: *mut c_void,
114            ) -> bool {
115                let context = PppContextInternal(context);
116                let mut callbacks = CALLBACKS.lock().unwrap();
117                let old_len = callbacks.len();
118
119                callbacks.retain(
120                    |(cb, cb_ctxt)| (*cb as usize, cb_ctxt) != (callback as _, &context)
121                );
122
123                callbacks.len() != old_len
124            }
125
126            $crate::lazy_static::lazy_static! {
127                static ref CALLBACKS: ::std::sync::Mutex<
128                    Vec<(CallbackTypeWithContext, PppContextInternal)>
129                > = ::std::sync::Mutex::new(Vec::new());
130            }
131
132            $vis fn trigger($($arg : $arg_ty),*) $(-> $ret_ty)? {
133                CALLBACKS.lock()
134                    .unwrap()
135                    .iter_mut()
136                    .map(|(callback, PppContextInternal(context))| callback(
137                        *context,
138                        $($arg),*
139                    ))
140                    .fold(
141                        $crate::__callback_fold_default!($($ret_ty)?),
142                        $crate::__callback_fold_fn!($($ret_ty)?)
143                    )
144            }
145        }
146    )*};
147}
148
149#[doc(hidden)]
150#[macro_export]
151macro_rules! __callback_fold_default {
152    () => {
153        ()
154    };
155    ($ty:ty) => {
156        <$ty as $crate::CallbackReturn>::callback_fold_default()
157    };
158}
159
160#[doc(hidden)]
161#[macro_export]
162macro_rules! __callback_fold_fn {
163    () => {
164        (|(), _| ())
165    };
166    ($ty:ty) => {
167        <$ty as $crate::CallbackReturn>::fold_callback_return
168    };
169}
170
171/// A type which can be returned from a callback and folded into a single value
172///
173/// As an example, here's the provided implementation for `bool`:
174///
175/// ```no_run
176/// /// Returns true if any of the callbacks returned true without short circuiting
177/// impl CallbackReturn for bool {
178///     type FoldType = bool;
179/// 
180///     fn fold_callback_return(folded: Self::FoldType, ret: Self) -> Self::FoldType {
181///         folded | ret
182///     }
183/// }
184/// ```
185///
186/// The way this is used is by taking the `FoldType` and creating a default instance. For
187/// a `bool` this will be `false`. Then, for each callback return value it will take the 
188/// previous instance (starting with `false`) and do `previous | current_callback_return`.
189///
190/// The result will mean that if callbacks `a`, `b`, and `c` are registered, the resulting
191/// value returned from `<callback>::trigger(...)` is `((false | a) | b) | c`. (Parenthesis
192/// added to demonstrate folding order)
193pub trait CallbackReturn {
194    type FoldType: Default;
195
196    /// Function for folding each callback return value into a single value
197    fn fold_callback_return(folded: Self::FoldType, ret: Self) -> Self::FoldType;
198
199    /// Get the default value for folding the callback returns into a single value
200    fn callback_fold_default() -> Self::FoldType {
201        Self::FoldType::default()
202    }
203}
204
205/// Returns true if any of the callbacks returned true without short circuiting
206impl CallbackReturn for bool {
207    type FoldType = bool;
208
209    fn fold_callback_return(folded: Self::FoldType, ret: Self) -> Self::FoldType {
210        folded | ret
211    }
212}
213
214macro_rules! impl_for_ints {
215    ($($ty:ty)*) => {
216        $(
217            /// Returns the first non-zero value without short-circuiting
218            impl CallbackReturn for $ty {
219                type FoldType = $ty;
220
221                fn fold_callback_return(folded: Self::FoldType, ret: Self) -> Self::FoldType {
222                    if folded != 0 {
223                        folded
224                    } else {
225                        ret
226                    }
227                }
228            }
229        )*
230    };
231}
232
233impl_for_ints!(u8 u16 u32 u64 usize i8 i16 i32 i64 isize);