use std::ffi::c_char;
#[cfg(feature = "python")]
use std::sync::{Mutex, OnceLock};
#[cfg(feature = "python")]
use ahash::AHashMap;
#[cfg(feature = "python")]
use nautilus_core::MUTEX_POISONED;
#[cfg(feature = "python")]
use nautilus_core::python::clone_py_object;
use nautilus_core::{
UUID4,
ffi::string::{cstr_to_ustr, str_to_cstr},
};
#[cfg(feature = "python")]
use pyo3::prelude::*;
use ustr::ustr;
use crate::timer::{TimeEvent, TimeEventCallback, TimeEventHandler};
#[repr(C)]
#[derive(Debug)]
#[allow(non_camel_case_types)]
pub struct TimeEventHandler_API {
pub event: TimeEvent,
pub callback_ptr: *mut c_char,
}
#[cfg(feature = "python")]
type CallbackEntry = (Py<PyAny>, usize);
#[cfg(feature = "python")]
fn registry() -> &'static Mutex<AHashMap<usize, CallbackEntry>> {
static REG: OnceLock<Mutex<AHashMap<usize, CallbackEntry>>> = OnceLock::new();
REG.get_or_init(|| Mutex::new(AHashMap::new()))
}
#[cfg(feature = "python")]
fn registry_lock() -> std::sync::MutexGuard<'static, AHashMap<usize, CallbackEntry>> {
match registry().lock() {
Ok(g) => g,
Err(poisoned) => poisoned.into_inner(),
}
}
#[cfg(feature = "python")]
pub fn registry_size() -> usize {
registry_lock().len()
}
#[cfg(feature = "python")]
pub fn cleanup_callback_registry() {
let callbacks: Vec<Py<PyAny>> = {
let mut map = registry_lock();
map.drain().map(|(_, (obj, _))| obj).collect()
};
Python::attach(|_| {
for cb in callbacks {
drop(cb);
}
});
}
#[cfg(feature = "python")]
impl From<TimeEventHandler> for TimeEventHandler_API {
fn from(value: TimeEventHandler) -> Self {
match value.callback {
TimeEventCallback::Python(callback_arc) => {
let raw_ptr = callback_arc.as_ptr().cast::<c_char>();
let key = raw_ptr as usize;
let mut map = registry_lock();
match map.entry(key) {
std::collections::hash_map::Entry::Occupied(mut e) => {
e.get_mut().1 += 1;
}
std::collections::hash_map::Entry::Vacant(e) => {
e.insert((clone_py_object(&callback_arc), 1));
}
}
Self {
event: value.event,
callback_ptr: raw_ptr,
}
}
TimeEventCallback::Rust(_) | TimeEventCallback::RustLocal(_) => {
panic!("Legacy time event handler is not supported for Rust callbacks")
}
}
}
}
#[cfg(feature = "python")]
impl Drop for TimeEventHandler_API {
fn drop(&mut self) {
if self.callback_ptr.is_null() {
return;
}
let key = self.callback_ptr as usize;
let mut map = registry().lock().expect(MUTEX_POISONED);
if let Some(entry) = map.get_mut(&key) {
if entry.1 > 1 {
entry.1 -= 1;
return;
}
let (arc, _) = map.remove(&key).unwrap();
Python::attach(|_| drop(arc));
}
}
}
impl Clone for TimeEventHandler_API {
fn clone(&self) -> Self {
#[cfg(feature = "python")]
{
if !self.callback_ptr.is_null() {
let key = self.callback_ptr as usize;
let mut map = registry_lock();
if let Some(entry) = map.get_mut(&key) {
entry.1 += 1;
}
}
}
Self {
event: self.event.clone(),
callback_ptr: self.callback_ptr,
}
}
}
#[cfg(not(feature = "python"))]
impl Drop for TimeEventHandler_API {
fn drop(&mut self) {}
}
impl TimeEventHandler_API {
#[must_use]
pub fn null() -> Self {
Self {
event: TimeEvent::new(ustr(""), UUID4::default(), 0.into(), 0.into()),
callback_ptr: std::ptr::null_mut(),
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn time_event_handler_drop(handler: TimeEventHandler_API) {
drop(handler);
}
#[cfg(all(test, feature = "python"))]
mod tests {
use nautilus_core::UUID4;
use pyo3::{Py, Python, types::PyList};
use rstest::rstest;
use ustr::Ustr;
use super::*;
use crate::timer::{TimeEvent, TimeEventCallback};
#[rstest]
fn registry_clears_after_handler_drop() {
Python::initialize();
Python::attach(|py| {
let py_list = PyList::empty(py);
let callback = TimeEventCallback::from(Py::from(py_list.getattr("append").unwrap()));
let handler = TimeEventHandler::new(
TimeEvent::new(Ustr::from("TEST"), UUID4::new(), 1.into(), 1.into()),
callback,
);
{
let _api: TimeEventHandler_API = handler.into();
assert_eq!(registry_size(), 1);
}
assert_eq!(registry_size(), 0);
});
}
}
#[cfg(not(feature = "python"))]
impl From<TimeEventHandler> for TimeEventHandler_API {
fn from(value: TimeEventHandler) -> Self {
match value.callback {
TimeEventCallback::Rust(_) | TimeEventCallback::RustLocal(_) => Self {
event: value.event,
callback_ptr: std::ptr::null_mut(),
},
#[cfg(feature = "python")]
TimeEventCallback::Python(_) => {
unreachable!("Python callback not supported without python feature")
}
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn time_event_new(
name_ptr: *const c_char,
event_id: UUID4,
ts_event: u64,
ts_init: u64,
) -> TimeEvent {
TimeEvent::new(
unsafe { cstr_to_ustr(name_ptr) },
event_id,
ts_event.into(),
ts_init.into(),
)
}
#[unsafe(no_mangle)]
pub extern "C" fn time_event_to_cstr(event: &TimeEvent) -> *const c_char {
str_to_cstr(&event.to_string())
}
#[unsafe(no_mangle)]
pub const extern "C" fn dummy(v: TimeEventHandler_API) -> TimeEventHandler_API {
v
}