use std::{
ffi::c_char,
ops::{Deref, DerefMut},
};
#[cfg(feature = "python")]
use nautilus_core::correctness::FAILED;
use nautilus_core::{
UnixNanos,
ffi::{
cvec::CVec,
parsing::u8_as_bool,
string::{cstr_as_str, str_to_cstr},
},
};
#[cfg(feature = "python")]
use pyo3::{ffi, prelude::*};
use super::timer::TimeEventHandler_API;
#[cfg(feature = "python")]
use crate::timer::TimeEventCallback;
use crate::{
clock::{Clock, TestClock},
live::clock::LiveClock,
timer::TimeEvent,
};
#[repr(C)]
#[derive(Debug)]
#[allow(non_camel_case_types)]
pub struct TestClock_API(Box<TestClock>);
impl Deref for TestClock_API {
type Target = TestClock;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for TestClock_API {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[unsafe(no_mangle)]
pub extern "C" fn test_clock_new() -> TestClock_API {
TestClock_API(Box::default())
}
#[unsafe(no_mangle)]
pub extern "C" fn test_clock_drop(clock: TestClock_API) {
drop(clock); }
#[cfg(feature = "python")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn test_clock_register_default_handler(
clock: &mut TestClock_API,
callback_ptr: *mut ffi::PyObject,
) {
assert!(!callback_ptr.is_null());
assert!(unsafe { ffi::Py_None() } != callback_ptr);
let callback = Python::attach(|py| unsafe {
Bound::<PyAny>::from_borrowed_ptr(py, callback_ptr).unbind()
});
let callback = TimeEventCallback::from_python_legacy_capsule(callback);
clock.register_default_handler(callback);
}
#[unsafe(no_mangle)]
pub extern "C" fn test_clock_cancel_default_handler(clock: &mut TestClock_API) {
clock.cancel_default_handler();
}
#[unsafe(no_mangle)]
pub extern "C" fn test_clock_cancel_callbacks(clock: &mut TestClock_API) {
clock.cancel_callbacks();
}
#[unsafe(no_mangle)]
pub extern "C" fn test_clock_set_time(clock: &TestClock_API, to_time_ns: u64) {
clock.set_time(to_time_ns.into());
}
#[unsafe(no_mangle)]
pub extern "C" fn test_clock_timestamp(clock: &TestClock_API) -> f64 {
clock.get_time()
}
#[unsafe(no_mangle)]
pub extern "C" fn test_clock_timestamp_ms(clock: &TestClock_API) -> u64 {
clock.get_time_ms()
}
#[unsafe(no_mangle)]
pub extern "C" fn test_clock_timestamp_us(clock: &TestClock_API) -> u64 {
clock.get_time_us()
}
#[unsafe(no_mangle)]
pub extern "C" fn test_clock_timestamp_ns(clock: &TestClock_API) -> u64 {
clock.get_time_ns().as_u64()
}
#[unsafe(no_mangle)]
pub extern "C" fn test_clock_timer_names(clock: &TestClock_API) -> *const c_char {
str_to_cstr(&clock.timer_names().join("<,>"))
}
#[unsafe(no_mangle)]
pub extern "C" fn test_clock_timer_count(clock: &mut TestClock_API) -> usize {
clock.timer_count()
}
#[cfg(feature = "python")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn test_clock_set_time_alert(
clock: &mut TestClock_API,
name_ptr: *const c_char,
alert_time_ns: UnixNanos,
callback_ptr: *mut ffi::PyObject,
allow_past: u8,
) {
assert!(!callback_ptr.is_null());
let name = unsafe { cstr_as_str(name_ptr) };
let callback = if callback_ptr == unsafe { ffi::Py_None() } {
None
} else {
let callback = Python::attach(|py| unsafe {
Bound::<PyAny>::from_borrowed_ptr(py, callback_ptr).unbind()
});
Some(TimeEventCallback::from_python_legacy_capsule(callback))
};
clock
.set_time_alert_ns(name, alert_time_ns, callback, Some(allow_past != 0))
.expect(FAILED);
}
#[cfg(feature = "python")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn test_clock_set_timer(
clock: &mut TestClock_API,
name_ptr: *const c_char,
interval_ns: u64,
start_time_ns: UnixNanos,
stop_time_ns: UnixNanos,
callback_ptr: *mut ffi::PyObject,
allow_past: u8,
fire_immediately: u8,
) {
assert!(!callback_ptr.is_null());
let name = unsafe { cstr_as_str(name_ptr) };
let start_time_ns = (start_time_ns != 0).then_some(start_time_ns);
let stop_time_ns = (stop_time_ns != 0).then_some(stop_time_ns);
let callback = if callback_ptr == unsafe { ffi::Py_None() } {
None
} else {
let callback = Python::attach(|py| unsafe {
Bound::<PyAny>::from_borrowed_ptr(py, callback_ptr).unbind()
});
Some(TimeEventCallback::from_python_legacy_capsule(callback))
};
clock
.set_timer_ns(
name,
interval_ns,
start_time_ns,
stop_time_ns,
callback,
Some(allow_past != 0),
Some(fire_immediately != 0),
)
.expect(FAILED);
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn test_clock_advance_time(
clock: &mut TestClock_API,
to_time_ns: u64,
set_time: u8,
) -> CVec {
let events: Vec<TimeEvent> = clock.advance_time(to_time_ns.into(), u8_as_bool(set_time));
let t: Vec<TimeEventHandler_API> = clock
.match_handlers(events)
.into_iter()
.map(Into::into)
.collect();
t.into()
}
#[unsafe(no_mangle)]
pub extern "C" fn vec_time_event_handlers_drop(v: CVec) {
let CVec { ptr, len, cap } = v;
assert!(
len <= cap,
"vec_time_event_handlers_drop: len ({len}) > cap ({cap}) - memory corruption or wrong drop helper"
);
assert!(
len == 0 || !ptr.is_null(),
"vec_time_event_handlers_drop: null ptr with non-zero len ({len}) - memory corruption or wrong drop helper"
);
let data: Vec<TimeEventHandler_API> =
unsafe { Vec::from_raw_parts(ptr.cast::<TimeEventHandler_API>(), len, cap) };
drop(data); }
#[unsafe(no_mangle)]
pub unsafe extern "C" fn test_clock_next_time(
clock: &mut TestClock_API,
name_ptr: *const c_char,
) -> UnixNanos {
let name = unsafe { cstr_as_str(name_ptr) };
clock.next_time_ns(name).unwrap_or_default()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn test_clock_cancel_timer(
clock: &mut TestClock_API,
name_ptr: *const c_char,
) {
let name = unsafe { cstr_as_str(name_ptr) };
clock.cancel_timer(name);
}
#[unsafe(no_mangle)]
pub extern "C" fn test_clock_cancel_timers(clock: &mut TestClock_API) {
clock.cancel_timers();
}
#[repr(C)]
#[derive(Debug)]
#[allow(non_camel_case_types)]
pub struct LiveClock_API(Box<LiveClock>);
impl Deref for LiveClock_API {
type Target = LiveClock;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for LiveClock_API {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[unsafe(no_mangle)]
pub extern "C" fn live_clock_new() -> LiveClock_API {
LiveClock_API(Box::new(LiveClock::new(None)))
}
#[unsafe(no_mangle)]
pub extern "C" fn live_clock_drop(clock: LiveClock_API) {
drop(clock); }
#[cfg(feature = "python")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn live_clock_register_default_handler(
clock: &mut LiveClock_API,
callback_ptr: *mut ffi::PyObject,
) {
assert!(!callback_ptr.is_null());
assert!(unsafe { ffi::Py_None() } != callback_ptr);
let callback = Python::attach(|py| unsafe {
Bound::<PyAny>::from_borrowed_ptr(py, callback_ptr).unbind()
});
let callback = TimeEventCallback::from_python_legacy_capsule(callback);
clock.register_default_handler(callback);
}
#[unsafe(no_mangle)]
pub extern "C" fn live_clock_cancel_default_handler(clock: &mut LiveClock_API) {
clock.cancel_default_handler();
}
#[unsafe(no_mangle)]
pub extern "C" fn live_clock_cancel_callbacks(clock: &mut LiveClock_API) {
clock.cancel_callbacks();
}
#[unsafe(no_mangle)]
pub extern "C" fn live_clock_timestamp(clock: &mut LiveClock_API) -> f64 {
clock.get_time()
}
#[unsafe(no_mangle)]
pub extern "C" fn live_clock_timestamp_ms(clock: &mut LiveClock_API) -> u64 {
clock.get_time_ms()
}
#[unsafe(no_mangle)]
pub extern "C" fn live_clock_timestamp_us(clock: &mut LiveClock_API) -> u64 {
clock.get_time_us()
}
#[unsafe(no_mangle)]
pub extern "C" fn live_clock_timestamp_ns(clock: &mut LiveClock_API) -> u64 {
clock.get_time_ns().as_u64()
}
#[unsafe(no_mangle)]
pub extern "C" fn live_clock_timer_names(clock: &LiveClock_API) -> *const c_char {
str_to_cstr(&clock.timer_names().join("<,>"))
}
#[unsafe(no_mangle)]
pub extern "C" fn live_clock_timer_count(clock: &mut LiveClock_API) -> usize {
clock.timer_count()
}
#[cfg(feature = "python")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn live_clock_set_time_alert(
clock: &mut LiveClock_API,
name_ptr: *const c_char,
alert_time_ns: UnixNanos,
callback_ptr: *mut ffi::PyObject,
allow_past: u8,
) {
assert!(!callback_ptr.is_null());
let name = unsafe { cstr_as_str(name_ptr) };
let callback = if callback_ptr == unsafe { ffi::Py_None() } {
None
} else {
let callback = Python::attach(|py| unsafe {
Bound::<PyAny>::from_borrowed_ptr(py, callback_ptr).unbind()
});
Some(TimeEventCallback::from_python_legacy_capsule(callback))
};
clock
.set_time_alert_ns(name, alert_time_ns, callback, Some(allow_past != 0))
.expect(FAILED);
}
#[cfg(feature = "python")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn live_clock_set_timer(
clock: &mut LiveClock_API,
name_ptr: *const c_char,
interval_ns: u64,
start_time_ns: UnixNanos,
stop_time_ns: UnixNanos,
callback_ptr: *mut ffi::PyObject,
allow_past: u8,
fire_immediately: u8,
) {
assert!(!callback_ptr.is_null());
let name = unsafe { cstr_as_str(name_ptr) };
let start_time_ns = (start_time_ns != 0).then_some(start_time_ns);
let stop_time_ns = (stop_time_ns != 0).then_some(stop_time_ns);
let callback = if callback_ptr == unsafe { ffi::Py_None() } {
None
} else {
let callback = Python::attach(|py| unsafe {
Bound::<PyAny>::from_borrowed_ptr(py, callback_ptr).unbind()
});
Some(TimeEventCallback::from_python_legacy_capsule(callback))
};
clock
.set_timer_ns(
name,
interval_ns,
start_time_ns,
stop_time_ns,
callback,
Some(allow_past != 0),
Some(fire_immediately != 0),
)
.expect(FAILED);
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn live_clock_next_time(
clock: &mut LiveClock_API,
name_ptr: *const c_char,
) -> UnixNanos {
let name = unsafe { cstr_as_str(name_ptr) };
clock.next_time_ns(name).unwrap_or_default()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn live_clock_cancel_timer(
clock: &mut LiveClock_API,
name_ptr: *const c_char,
) {
let name = unsafe { cstr_as_str(name_ptr) };
clock.cancel_timer(name);
}
#[unsafe(no_mangle)]
pub extern "C" fn live_clock_cancel_timers(clock: &mut LiveClock_API) {
clock.cancel_timers();
}
#[cfg(all(test, feature = "python"))]
mod tests {
use std::ffi::CString;
use nautilus_core::UUID4;
use pyo3::{
Bound, Py, PyAny, PyResult, Python,
types::{
PyAnyMethods, PyCFunction, PyDict, PyList, PyListMethods, PyTuple, PyTupleMethods,
PyTypeMethods,
},
};
use rstest::rstest;
use ustr::Ustr;
use super::*;
#[rstest]
fn test_clock_ffi_python_callbacks_use_legacy_capsules() {
Python::initialize();
Python::attach(|py| {
let seen = PyList::empty(py);
let callback = record_arg_type_callback(py, &seen);
let callback_ptr = callback.as_ptr();
let mut test_clock = test_clock_new();
unsafe { test_clock_register_default_handler(&mut test_clock, callback_ptr) };
test_clock.get_handler(time_event("test-default")).run();
let test_alert_name = CString::new("test-alert").unwrap();
unsafe {
test_clock_set_time_alert(
&mut test_clock,
test_alert_name.as_ptr(),
UnixNanos::from(1_000),
callback_ptr,
1,
);
}
test_clock.get_handler(time_event("test-alert")).run();
let test_timer_name = CString::new("test-timer").unwrap();
unsafe {
test_clock_set_timer(
&mut test_clock,
test_timer_name.as_ptr(),
1_000,
UnixNanos::from(1_000),
UnixNanos::from(0),
callback_ptr,
1,
0,
);
}
test_clock.get_handler(time_event("test-timer")).run();
let mut live_clock = live_clock_new();
unsafe { live_clock_register_default_handler(&mut live_clock, callback_ptr) };
live_clock.get_handler(time_event("live-default")).run();
let live_now = live_clock_timestamp_ns(&mut live_clock);
let live_alert_name = CString::new("live-alert").unwrap();
unsafe {
live_clock_set_time_alert(
&mut live_clock,
live_alert_name.as_ptr(),
UnixNanos::from(live_now + 10_000_000_000),
callback_ptr,
0,
);
}
live_clock.get_handler(time_event("live-alert")).run();
let live_timer_name = CString::new("live-timer").unwrap();
unsafe {
live_clock_set_timer(
&mut live_clock,
live_timer_name.as_ptr(),
10_000_000_000,
UnixNanos::from(0),
UnixNanos::from(0),
callback_ptr,
1,
0,
);
}
live_clock.get_handler(time_event("live-timer")).run();
live_clock.cancel_timers();
let seen_types = seen
.iter()
.map(|item| item.extract::<String>().unwrap())
.collect::<Vec<_>>();
assert_eq!(seen_types, vec!["PyCapsule".to_string(); 6]);
});
}
fn record_arg_type_callback(py: Python<'_>, seen: &Bound<'_, PyList>) -> Py<PyAny> {
let seen_obj = seen.clone().unbind().into_any();
PyCFunction::new_closure(
py,
None,
None,
move |args: &Bound<'_, PyTuple>, _kwargs: Option<&Bound<'_, PyDict>>| -> PyResult<()> {
let arg = args.get_item(0)?;
let type_name = arg.get_type().name()?.to_string();
seen_obj.call_method1(args.py(), "append", (type_name,))?;
Ok(())
},
)
.expect("callback should create")
.into_any()
.unbind()
}
fn time_event(name: &str) -> TimeEvent {
TimeEvent::new(
Ustr::from(name),
UUID4::from("00000000-0000-4000-8000-000000000011"),
UnixNanos::from(100),
UnixNanos::from(99),
)
}
}