1use std::{
17 ffi::c_char,
18 ops::{Deref, DerefMut},
19};
20
21#[cfg(feature = "python")]
22use nautilus_core::correctness::FAILED;
23use nautilus_core::{
24 UnixNanos,
25 ffi::{
26 cvec::CVec,
27 parsing::u8_as_bool,
28 string::{cstr_as_str, str_to_cstr},
29 },
30};
31#[cfg(feature = "python")]
32use pyo3::{ffi, prelude::*};
33
34use super::timer::TimeEventHandler_API;
35#[cfg(feature = "python")]
36use crate::timer::TimeEventCallback;
37use crate::{
38 clock::{Clock, TestClock},
39 live::clock::LiveClock,
40 timer::TimeEvent,
41};
42
43#[repr(C)]
52#[derive(Debug)]
53#[allow(non_camel_case_types)]
54pub struct TestClock_API(Box<TestClock>);
55
56impl Deref for TestClock_API {
57 type Target = TestClock;
58
59 fn deref(&self) -> &Self::Target {
60 &self.0
61 }
62}
63
64impl DerefMut for TestClock_API {
65 fn deref_mut(&mut self) -> &mut Self::Target {
66 &mut self.0
67 }
68}
69
70#[unsafe(no_mangle)]
71pub extern "C" fn test_clock_new() -> TestClock_API {
72 TestClock_API(Box::default())
73}
74
75#[unsafe(no_mangle)]
76pub extern "C" fn test_clock_drop(clock: TestClock_API) {
77 drop(clock); }
79
80#[cfg(feature = "python")]
90#[unsafe(no_mangle)]
91pub unsafe extern "C" fn test_clock_register_default_handler(
92 clock: &mut TestClock_API,
93 callback_ptr: *mut ffi::PyObject,
94) {
95 assert!(!callback_ptr.is_null());
96 assert!(unsafe { ffi::Py_None() } != callback_ptr);
97
98 let callback = Python::attach(|py| unsafe {
99 Bound::<PyAny>::from_borrowed_ptr(py, callback_ptr).unbind()
100 });
101 let callback = TimeEventCallback::from_python_legacy_capsule(callback);
102
103 clock.register_default_handler(callback);
104}
105
106#[unsafe(no_mangle)]
108pub extern "C" fn test_clock_cancel_default_handler(clock: &mut TestClock_API) {
109 clock.cancel_default_handler();
110}
111
112#[unsafe(no_mangle)]
114pub extern "C" fn test_clock_cancel_callbacks(clock: &mut TestClock_API) {
115 clock.cancel_callbacks();
116}
117
118#[unsafe(no_mangle)]
119pub extern "C" fn test_clock_set_time(clock: &TestClock_API, to_time_ns: u64) {
120 clock.set_time(to_time_ns.into());
121}
122
123#[unsafe(no_mangle)]
124pub extern "C" fn test_clock_timestamp(clock: &TestClock_API) -> f64 {
125 clock.get_time()
126}
127
128#[unsafe(no_mangle)]
129pub extern "C" fn test_clock_timestamp_ms(clock: &TestClock_API) -> u64 {
130 clock.get_time_ms()
131}
132
133#[unsafe(no_mangle)]
134pub extern "C" fn test_clock_timestamp_us(clock: &TestClock_API) -> u64 {
135 clock.get_time_us()
136}
137
138#[unsafe(no_mangle)]
139pub extern "C" fn test_clock_timestamp_ns(clock: &TestClock_API) -> u64 {
140 clock.get_time_ns().as_u64()
141}
142
143#[unsafe(no_mangle)]
144pub extern "C" fn test_clock_timer_names(clock: &TestClock_API) -> *const c_char {
145 str_to_cstr(&clock.timer_names().join("<,>"))
148}
149
150#[unsafe(no_mangle)]
151pub extern "C" fn test_clock_timer_count(clock: &mut TestClock_API) -> usize {
152 clock.timer_count()
153}
154
155#[cfg(feature = "python")]
165#[unsafe(no_mangle)]
166pub unsafe extern "C" fn test_clock_set_time_alert(
167 clock: &mut TestClock_API,
168 name_ptr: *const c_char,
169 alert_time_ns: UnixNanos,
170 callback_ptr: *mut ffi::PyObject,
171 allow_past: u8,
172) {
173 assert!(!callback_ptr.is_null());
174
175 let name = unsafe { cstr_as_str(name_ptr) };
176 let callback = if callback_ptr == unsafe { ffi::Py_None() } {
177 None
178 } else {
179 let callback = Python::attach(|py| unsafe {
180 Bound::<PyAny>::from_borrowed_ptr(py, callback_ptr).unbind()
181 });
182 Some(TimeEventCallback::from_python_legacy_capsule(callback))
183 };
184
185 clock
186 .set_time_alert_ns(name, alert_time_ns, callback, Some(allow_past != 0))
187 .expect(FAILED);
188}
189
190#[cfg(feature = "python")]
205#[unsafe(no_mangle)]
206pub unsafe extern "C" fn test_clock_set_timer(
207 clock: &mut TestClock_API,
208 name_ptr: *const c_char,
209 interval_ns: u64,
210 start_time_ns: UnixNanos,
211 stop_time_ns: UnixNanos,
212 callback_ptr: *mut ffi::PyObject,
213 allow_past: u8,
214 fire_immediately: u8,
215) {
216 assert!(!callback_ptr.is_null());
217
218 let name = unsafe { cstr_as_str(name_ptr) };
219 let start_time_ns = (start_time_ns != 0).then_some(start_time_ns);
221 let stop_time_ns = (stop_time_ns != 0).then_some(stop_time_ns);
222 let callback = if callback_ptr == unsafe { ffi::Py_None() } {
223 None
224 } else {
225 let callback = Python::attach(|py| unsafe {
226 Bound::<PyAny>::from_borrowed_ptr(py, callback_ptr).unbind()
227 });
228 Some(TimeEventCallback::from_python_legacy_capsule(callback))
229 };
230
231 clock
232 .set_timer_ns(
233 name,
234 interval_ns,
235 start_time_ns,
236 stop_time_ns,
237 callback,
238 Some(allow_past != 0),
239 Some(fire_immediately != 0),
240 )
241 .expect(FAILED);
242}
243
244#[unsafe(no_mangle)]
248pub unsafe extern "C" fn test_clock_advance_time(
249 clock: &mut TestClock_API,
250 to_time_ns: u64,
251 set_time: u8,
252) -> CVec {
253 let events: Vec<TimeEvent> = clock.advance_time(to_time_ns.into(), u8_as_bool(set_time));
254 let t: Vec<TimeEventHandler_API> = clock
255 .match_handlers(events)
256 .into_iter()
257 .map(Into::into)
258 .collect();
259 t.into()
260}
261
262#[unsafe(no_mangle)]
270pub extern "C" fn vec_time_event_handlers_drop(v: CVec) {
271 let CVec { ptr, len, cap } = v;
272
273 assert!(
274 len <= cap,
275 "vec_time_event_handlers_drop: len ({len}) > cap ({cap}) - memory corruption or wrong drop helper"
276 );
277 assert!(
278 len == 0 || !ptr.is_null(),
279 "vec_time_event_handlers_drop: null ptr with non-zero len ({len}) - memory corruption or wrong drop helper"
280 );
281
282 let data: Vec<TimeEventHandler_API> =
283 unsafe { Vec::from_raw_parts(ptr.cast::<TimeEventHandler_API>(), len, cap) };
284 drop(data); }
286
287#[unsafe(no_mangle)]
291pub unsafe extern "C" fn test_clock_next_time(
292 clock: &mut TestClock_API,
293 name_ptr: *const c_char,
294) -> UnixNanos {
295 let name = unsafe { cstr_as_str(name_ptr) };
296 clock.next_time_ns(name).unwrap_or_default()
297}
298
299#[unsafe(no_mangle)]
303pub unsafe extern "C" fn test_clock_cancel_timer(
304 clock: &mut TestClock_API,
305 name_ptr: *const c_char,
306) {
307 let name = unsafe { cstr_as_str(name_ptr) };
308 clock.cancel_timer(name);
309}
310
311#[unsafe(no_mangle)]
312pub extern "C" fn test_clock_cancel_timers(clock: &mut TestClock_API) {
313 clock.cancel_timers();
314}
315
316#[repr(C)]
326#[derive(Debug)]
327#[allow(non_camel_case_types)]
328pub struct LiveClock_API(Box<LiveClock>);
329
330impl Deref for LiveClock_API {
331 type Target = LiveClock;
332
333 fn deref(&self) -> &Self::Target {
334 &self.0
335 }
336}
337
338impl DerefMut for LiveClock_API {
339 fn deref_mut(&mut self) -> &mut Self::Target {
340 &mut self.0
341 }
342}
343
344#[unsafe(no_mangle)]
345pub extern "C" fn live_clock_new() -> LiveClock_API {
346 LiveClock_API(Box::new(LiveClock::new(None)))
348}
349
350#[unsafe(no_mangle)]
351pub extern "C" fn live_clock_drop(clock: LiveClock_API) {
352 drop(clock); }
354
355#[cfg(feature = "python")]
363#[unsafe(no_mangle)]
364pub unsafe extern "C" fn live_clock_register_default_handler(
365 clock: &mut LiveClock_API,
366 callback_ptr: *mut ffi::PyObject,
367) {
368 assert!(!callback_ptr.is_null());
369 assert!(unsafe { ffi::Py_None() } != callback_ptr);
370
371 let callback = Python::attach(|py| unsafe {
372 Bound::<PyAny>::from_borrowed_ptr(py, callback_ptr).unbind()
373 });
374 let callback = TimeEventCallback::from_python_legacy_capsule(callback);
375
376 clock.register_default_handler(callback);
377}
378
379#[unsafe(no_mangle)]
381pub extern "C" fn live_clock_cancel_default_handler(clock: &mut LiveClock_API) {
382 clock.cancel_default_handler();
383}
384
385#[unsafe(no_mangle)]
387pub extern "C" fn live_clock_cancel_callbacks(clock: &mut LiveClock_API) {
388 clock.cancel_callbacks();
389}
390
391#[unsafe(no_mangle)]
392pub extern "C" fn live_clock_timestamp(clock: &mut LiveClock_API) -> f64 {
393 clock.get_time()
394}
395
396#[unsafe(no_mangle)]
397pub extern "C" fn live_clock_timestamp_ms(clock: &mut LiveClock_API) -> u64 {
398 clock.get_time_ms()
399}
400
401#[unsafe(no_mangle)]
402pub extern "C" fn live_clock_timestamp_us(clock: &mut LiveClock_API) -> u64 {
403 clock.get_time_us()
404}
405
406#[unsafe(no_mangle)]
407pub extern "C" fn live_clock_timestamp_ns(clock: &mut LiveClock_API) -> u64 {
408 clock.get_time_ns().as_u64()
409}
410
411#[unsafe(no_mangle)]
412pub extern "C" fn live_clock_timer_names(clock: &LiveClock_API) -> *const c_char {
413 str_to_cstr(&clock.timer_names().join("<,>"))
416}
417
418#[unsafe(no_mangle)]
419pub extern "C" fn live_clock_timer_count(clock: &mut LiveClock_API) -> usize {
420 clock.timer_count()
421}
422
423#[cfg(feature = "python")]
435#[unsafe(no_mangle)]
436pub unsafe extern "C" fn live_clock_set_time_alert(
437 clock: &mut LiveClock_API,
438 name_ptr: *const c_char,
439 alert_time_ns: UnixNanos,
440 callback_ptr: *mut ffi::PyObject,
441 allow_past: u8,
442) {
443 assert!(!callback_ptr.is_null());
444
445 let name = unsafe { cstr_as_str(name_ptr) };
446 let callback = if callback_ptr == unsafe { ffi::Py_None() } {
447 None
448 } else {
449 let callback = Python::attach(|py| unsafe {
450 Bound::<PyAny>::from_borrowed_ptr(py, callback_ptr).unbind()
451 });
452 Some(TimeEventCallback::from_python_legacy_capsule(callback))
453 };
454
455 clock
456 .set_time_alert_ns(name, alert_time_ns, callback, Some(allow_past != 0))
457 .expect(FAILED);
458}
459
460#[cfg(feature = "python")]
477#[unsafe(no_mangle)]
478pub unsafe extern "C" fn live_clock_set_timer(
479 clock: &mut LiveClock_API,
480 name_ptr: *const c_char,
481 interval_ns: u64,
482 start_time_ns: UnixNanos,
483 stop_time_ns: UnixNanos,
484 callback_ptr: *mut ffi::PyObject,
485 allow_past: u8,
486 fire_immediately: u8,
487) {
488 assert!(!callback_ptr.is_null());
489
490 let name = unsafe { cstr_as_str(name_ptr) };
491 let start_time_ns = (start_time_ns != 0).then_some(start_time_ns);
493 let stop_time_ns = (stop_time_ns != 0).then_some(stop_time_ns);
494 let callback = if callback_ptr == unsafe { ffi::Py_None() } {
495 None
496 } else {
497 let callback = Python::attach(|py| unsafe {
498 Bound::<PyAny>::from_borrowed_ptr(py, callback_ptr).unbind()
499 });
500 Some(TimeEventCallback::from_python_legacy_capsule(callback))
501 };
502
503 clock
504 .set_timer_ns(
505 name,
506 interval_ns,
507 start_time_ns,
508 stop_time_ns,
509 callback,
510 Some(allow_past != 0),
511 Some(fire_immediately != 0),
512 )
513 .expect(FAILED);
514}
515
516#[unsafe(no_mangle)]
520pub unsafe extern "C" fn live_clock_next_time(
521 clock: &mut LiveClock_API,
522 name_ptr: *const c_char,
523) -> UnixNanos {
524 let name = unsafe { cstr_as_str(name_ptr) };
525 clock.next_time_ns(name).unwrap_or_default()
526}
527
528#[unsafe(no_mangle)]
532pub unsafe extern "C" fn live_clock_cancel_timer(
533 clock: &mut LiveClock_API,
534 name_ptr: *const c_char,
535) {
536 let name = unsafe { cstr_as_str(name_ptr) };
537 clock.cancel_timer(name);
538}
539
540#[unsafe(no_mangle)]
541pub extern "C" fn live_clock_cancel_timers(clock: &mut LiveClock_API) {
542 clock.cancel_timers();
543}
544
545#[cfg(all(test, feature = "python"))]
546mod tests {
547 use std::ffi::CString;
548
549 use nautilus_core::UUID4;
550 use pyo3::{
551 Bound, Py, PyAny, PyResult, Python,
552 types::{
553 PyAnyMethods, PyCFunction, PyDict, PyList, PyListMethods, PyTuple, PyTupleMethods,
554 PyTypeMethods,
555 },
556 };
557 use rstest::rstest;
558 use ustr::Ustr;
559
560 use super::*;
561
562 #[rstest]
563 fn test_clock_ffi_python_callbacks_use_legacy_capsules() {
564 Python::initialize();
565
566 Python::attach(|py| {
567 let seen = PyList::empty(py);
568 let callback = record_arg_type_callback(py, &seen);
569 let callback_ptr = callback.as_ptr();
570
571 let mut test_clock = test_clock_new();
572 unsafe { test_clock_register_default_handler(&mut test_clock, callback_ptr) };
573 test_clock.get_handler(time_event("test-default")).run();
574
575 let test_alert_name = CString::new("test-alert").unwrap();
576 unsafe {
577 test_clock_set_time_alert(
578 &mut test_clock,
579 test_alert_name.as_ptr(),
580 UnixNanos::from(1_000),
581 callback_ptr,
582 1,
583 );
584 }
585 test_clock.get_handler(time_event("test-alert")).run();
586
587 let test_timer_name = CString::new("test-timer").unwrap();
588 unsafe {
589 test_clock_set_timer(
590 &mut test_clock,
591 test_timer_name.as_ptr(),
592 1_000,
593 UnixNanos::from(1_000),
594 UnixNanos::from(0),
595 callback_ptr,
596 1,
597 0,
598 );
599 }
600 test_clock.get_handler(time_event("test-timer")).run();
601
602 let mut live_clock = live_clock_new();
603 unsafe { live_clock_register_default_handler(&mut live_clock, callback_ptr) };
604 live_clock.get_handler(time_event("live-default")).run();
605
606 let live_now = live_clock_timestamp_ns(&mut live_clock);
607 let live_alert_name = CString::new("live-alert").unwrap();
608 unsafe {
609 live_clock_set_time_alert(
610 &mut live_clock,
611 live_alert_name.as_ptr(),
612 UnixNanos::from(live_now + 10_000_000_000),
613 callback_ptr,
614 0,
615 );
616 }
617 live_clock.get_handler(time_event("live-alert")).run();
618
619 let live_timer_name = CString::new("live-timer").unwrap();
620 unsafe {
621 live_clock_set_timer(
622 &mut live_clock,
623 live_timer_name.as_ptr(),
624 10_000_000_000,
625 UnixNanos::from(0),
626 UnixNanos::from(0),
627 callback_ptr,
628 1,
629 0,
630 );
631 }
632 live_clock.get_handler(time_event("live-timer")).run();
633 live_clock.cancel_timers();
634
635 let seen_types = seen
636 .iter()
637 .map(|item| item.extract::<String>().unwrap())
638 .collect::<Vec<_>>();
639 assert_eq!(seen_types, vec!["PyCapsule".to_string(); 6]);
640 });
641 }
642
643 fn record_arg_type_callback(py: Python<'_>, seen: &Bound<'_, PyList>) -> Py<PyAny> {
644 let seen_obj = seen.clone().unbind().into_any();
645
646 new_sync_py_callback(
647 py,
648 move |args: &Bound<'_, PyTuple>, _kwargs: Option<&Bound<'_, PyDict>>| -> PyResult<()> {
649 let arg = args.get_item(0)?;
650 let type_name = arg.get_type().name()?.to_string();
651 seen_obj.call_method1(args.py(), "append", (type_name,))?;
652 Ok(())
653 },
654 )
655 .expect("callback should create")
656 .into_any()
657 .unbind()
658 }
659
660 fn new_sync_py_callback<F>(py: Python<'_>, closure: F) -> PyResult<Bound<'_, PyCFunction>>
661 where
662 F: Fn(&Bound<'_, PyTuple>, Option<&Bound<'_, PyDict>>) -> PyResult<()>
663 + Send
664 + Sync
665 + 'static,
666 {
667 PyCFunction::new_closure(py, None, None, closure)
668 }
669
670 fn time_event(name: &str) -> TimeEvent {
671 TimeEvent::new(
672 Ustr::from(name),
673 UUID4::from("00000000-0000-4000-8000-000000000011"),
674 UnixNanos::from(100),
675 UnixNanos::from(99),
676 )
677 }
678}