Skip to main content

nautilus_common/ffi/
clock.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16use 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/// C compatible Foreign Function Interface (FFI) for an underlying [`TestClock`].
44///
45/// This struct wraps `TestClock` in a way that makes it compatible with C function
46/// calls, enabling interaction with `TestClock` in a C environment.
47///
48/// It implements the `Deref` trait, allowing instances of `TestClock_API` to be
49/// dereferenced to `TestClock`, providing access to `TestClock`'s methods without
50/// having to manually access the underlying `TestClock` instance.
51#[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); // Memory freed here
78}
79
80/// Registers the default callback handler for TestClock.
81///
82/// # Safety
83///
84/// Assumes `callback_ptr` is a valid `PyCallable` pointer.
85///
86/// # Panics
87///
88/// Panics if the `callback_ptr` is null or represents the Python `None` object.
89#[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(callback);
102
103    clock.register_default_handler(callback);
104}
105
106#[unsafe(no_mangle)]
107pub extern "C" fn test_clock_set_time(clock: &TestClock_API, to_time_ns: u64) {
108    clock.set_time(to_time_ns.into());
109}
110
111#[unsafe(no_mangle)]
112pub extern "C" fn test_clock_timestamp(clock: &TestClock_API) -> f64 {
113    clock.get_time()
114}
115
116#[unsafe(no_mangle)]
117pub extern "C" fn test_clock_timestamp_ms(clock: &TestClock_API) -> u64 {
118    clock.get_time_ms()
119}
120
121#[unsafe(no_mangle)]
122pub extern "C" fn test_clock_timestamp_us(clock: &TestClock_API) -> u64 {
123    clock.get_time_us()
124}
125
126#[unsafe(no_mangle)]
127pub extern "C" fn test_clock_timestamp_ns(clock: &TestClock_API) -> u64 {
128    clock.get_time_ns().as_u64()
129}
130
131#[unsafe(no_mangle)]
132pub extern "C" fn test_clock_timer_names(clock: &TestClock_API) -> *const c_char {
133    // For simplicity we join a string with a reasonably unique delimiter.
134    // This is a temporary solution pending the removal of Cython.
135    str_to_cstr(&clock.timer_names().join("<,>"))
136}
137
138#[unsafe(no_mangle)]
139pub extern "C" fn test_clock_timer_count(clock: &mut TestClock_API) -> usize {
140    clock.timer_count()
141}
142
143/// # Safety
144///
145/// This function assumes:
146/// - `name_ptr` is a valid C string pointer.
147/// - `callback_ptr` is a valid `PyCallable` pointer.
148///
149/// # Panics
150///
151/// Panics if `callback_ptr` is null or if setting the timer fails.
152#[cfg(feature = "python")]
153#[unsafe(no_mangle)]
154pub unsafe extern "C" fn test_clock_set_time_alert(
155    clock: &mut TestClock_API,
156    name_ptr: *const c_char,
157    alert_time_ns: UnixNanos,
158    callback_ptr: *mut ffi::PyObject,
159    allow_past: u8,
160) {
161    assert!(!callback_ptr.is_null());
162
163    let name = unsafe { cstr_as_str(name_ptr) };
164    let callback = if callback_ptr == unsafe { ffi::Py_None() } {
165        None
166    } else {
167        let callback = Python::attach(|py| unsafe {
168            Bound::<PyAny>::from_borrowed_ptr(py, callback_ptr).unbind()
169        });
170        Some(TimeEventCallback::from(callback))
171    };
172
173    clock
174        .set_time_alert_ns(name, alert_time_ns, callback, Some(allow_past != 0))
175        .expect(FAILED);
176}
177
178/// # Safety
179///
180/// This function assumes:
181/// - `name_ptr` is a valid C string pointer.
182/// - `callback_ptr` is a valid `PyCallable` pointer.
183///
184/// # Parameters
185///
186/// - `start_time_ns`: UNIX timestamp in nanoseconds. Use `0` to indicate "use current time".
187/// - `stop_time_ns`: UNIX timestamp in nanoseconds. Use `0` to indicate "no stop time".
188///
189/// # Panics
190///
191/// Panics if `callback_ptr` is null or represents the Python `None` object.
192#[cfg(feature = "python")]
193#[unsafe(no_mangle)]
194pub unsafe extern "C" fn test_clock_set_timer(
195    clock: &mut TestClock_API,
196    name_ptr: *const c_char,
197    interval_ns: u64,
198    start_time_ns: UnixNanos,
199    stop_time_ns: UnixNanos,
200    callback_ptr: *mut ffi::PyObject,
201    allow_past: u8,
202    fire_immediately: u8,
203) {
204    assert!(!callback_ptr.is_null());
205
206    let name = unsafe { cstr_as_str(name_ptr) };
207    // C API convention: 0 means None (use defaults)
208    let start_time_ns = (start_time_ns != 0).then_some(start_time_ns);
209    let stop_time_ns = (stop_time_ns != 0).then_some(stop_time_ns);
210    let callback = if callback_ptr == unsafe { ffi::Py_None() } {
211        None
212    } else {
213        let callback = Python::attach(|py| unsafe {
214            Bound::<PyAny>::from_borrowed_ptr(py, callback_ptr).unbind()
215        });
216        Some(TimeEventCallback::from(callback))
217    };
218
219    clock
220        .set_timer_ns(
221            name,
222            interval_ns,
223            start_time_ns,
224            stop_time_ns,
225            callback,
226            Some(allow_past != 0),
227            Some(fire_immediately != 0),
228        )
229        .expect(FAILED);
230}
231
232/// # Safety
233///
234/// Assumes `set_time` is a correct `uint8_t` of either 0 or 1.
235#[unsafe(no_mangle)]
236pub unsafe extern "C" fn test_clock_advance_time(
237    clock: &mut TestClock_API,
238    to_time_ns: u64,
239    set_time: u8,
240) -> CVec {
241    let events: Vec<TimeEvent> = clock.advance_time(to_time_ns.into(), u8_as_bool(set_time));
242    let t: Vec<TimeEventHandler_API> = clock
243        .match_handlers(events)
244        .into_iter()
245        .map(Into::into)
246        .collect();
247    t.into()
248}
249
250// TODO: This drop helper may leak Python callbacks when handlers own Python objects.
251//       We need to mirror the `ffi::timer` registry so reference counts are decremented properly.
252/// Drops a `CVec` of `TimeEventHandler_API` values.
253///
254/// # Panics
255///
256/// Panics if `CVec` invariants are violated (corrupted metadata).
257#[unsafe(no_mangle)]
258pub extern "C" fn vec_time_event_handlers_drop(v: CVec) {
259    let CVec { ptr, len, cap } = v;
260
261    assert!(
262        len <= cap,
263        "vec_time_event_handlers_drop: len ({len}) > cap ({cap}) - memory corruption or wrong drop helper"
264    );
265    assert!(
266        len == 0 || !ptr.is_null(),
267        "vec_time_event_handlers_drop: null ptr with non-zero len ({len}) - memory corruption or wrong drop helper"
268    );
269
270    let data: Vec<TimeEventHandler_API> =
271        unsafe { Vec::from_raw_parts(ptr.cast::<TimeEventHandler_API>(), len, cap) };
272    drop(data); // Memory freed here
273}
274
275/// # Safety
276///
277/// Assumes `name_ptr` is a valid C string pointer.
278#[unsafe(no_mangle)]
279pub unsafe extern "C" fn test_clock_next_time(
280    clock: &mut TestClock_API,
281    name_ptr: *const c_char,
282) -> UnixNanos {
283    let name = unsafe { cstr_as_str(name_ptr) };
284    clock.next_time_ns(name).unwrap_or_default()
285}
286
287/// # Safety
288///
289/// Assumes `name_ptr` is a valid C string pointer.
290#[unsafe(no_mangle)]
291pub unsafe extern "C" fn test_clock_cancel_timer(
292    clock: &mut TestClock_API,
293    name_ptr: *const c_char,
294) {
295    let name = unsafe { cstr_as_str(name_ptr) };
296    clock.cancel_timer(name);
297}
298
299#[unsafe(no_mangle)]
300pub extern "C" fn test_clock_cancel_timers(clock: &mut TestClock_API) {
301    clock.cancel_timers();
302}
303
304/// C compatible Foreign Function Interface (FFI) for an underlying [`LiveClock`].
305///
306/// This struct wraps `LiveClock` in a way that makes it compatible with C function
307/// calls, enabling interaction with `LiveClock` in a C environment.
308///
309/// It implements the `Deref` and `DerefMut` traits, allowing instances of `LiveClock_API` to be
310/// dereferenced to `LiveClock`, providing access to `LiveClock`'s methods without
311/// having to manually access the underlying `LiveClock` instance. This includes
312/// both mutable and immutable access.
313#[repr(C)]
314#[derive(Debug)]
315#[allow(non_camel_case_types)]
316pub struct LiveClock_API(Box<LiveClock>);
317
318impl Deref for LiveClock_API {
319    type Target = LiveClock;
320
321    fn deref(&self) -> &Self::Target {
322        &self.0
323    }
324}
325
326impl DerefMut for LiveClock_API {
327    fn deref_mut(&mut self) -> &mut Self::Target {
328        &mut self.0
329    }
330}
331
332#[unsafe(no_mangle)]
333pub extern "C" fn live_clock_new() -> LiveClock_API {
334    // Initialize a live clock without a time event sender
335    LiveClock_API(Box::new(LiveClock::new(None)))
336}
337
338#[unsafe(no_mangle)]
339pub extern "C" fn live_clock_drop(clock: LiveClock_API) {
340    drop(clock); // Memory freed here
341}
342
343/// # Safety
344///
345/// Assumes `callback_ptr` is a valid `PyCallable` pointer.
346///
347/// # Panics
348///
349/// Panics if `callback_ptr` is null or represents the Python `None` object.
350#[cfg(feature = "python")]
351#[unsafe(no_mangle)]
352pub unsafe extern "C" fn live_clock_register_default_handler(
353    clock: &mut LiveClock_API,
354    callback_ptr: *mut ffi::PyObject,
355) {
356    assert!(!callback_ptr.is_null());
357    assert!(unsafe { ffi::Py_None() } != callback_ptr);
358
359    let callback = Python::attach(|py| unsafe {
360        Bound::<PyAny>::from_borrowed_ptr(py, callback_ptr).unbind()
361    });
362    let callback = TimeEventCallback::from(callback);
363
364    clock.register_default_handler(callback);
365}
366
367#[unsafe(no_mangle)]
368pub extern "C" fn live_clock_timestamp(clock: &mut LiveClock_API) -> f64 {
369    clock.get_time()
370}
371
372#[unsafe(no_mangle)]
373pub extern "C" fn live_clock_timestamp_ms(clock: &mut LiveClock_API) -> u64 {
374    clock.get_time_ms()
375}
376
377#[unsafe(no_mangle)]
378pub extern "C" fn live_clock_timestamp_us(clock: &mut LiveClock_API) -> u64 {
379    clock.get_time_us()
380}
381
382#[unsafe(no_mangle)]
383pub extern "C" fn live_clock_timestamp_ns(clock: &mut LiveClock_API) -> u64 {
384    clock.get_time_ns().as_u64()
385}
386
387#[unsafe(no_mangle)]
388pub extern "C" fn live_clock_timer_names(clock: &LiveClock_API) -> *const c_char {
389    // For simplicity we join a string with a reasonably unique delimiter.
390    // This is a temporary solution pending the removal of Cython.
391    str_to_cstr(&clock.timer_names().join("<,>"))
392}
393
394#[unsafe(no_mangle)]
395pub extern "C" fn live_clock_timer_count(clock: &mut LiveClock_API) -> usize {
396    clock.timer_count()
397}
398
399/// # Safety
400///
401/// This function assumes:
402/// - `name_ptr` is a valid C string pointer.
403/// - `callback_ptr` is a valid `PyCallable` pointer.
404///
405/// # Panics
406///
407/// This function panics if:
408/// - `name` is not a valid string.
409/// - `callback_ptr` is NULL and no default callback has been assigned on the clock.
410#[cfg(feature = "python")]
411#[unsafe(no_mangle)]
412pub unsafe extern "C" fn live_clock_set_time_alert(
413    clock: &mut LiveClock_API,
414    name_ptr: *const c_char,
415    alert_time_ns: UnixNanos,
416    callback_ptr: *mut ffi::PyObject,
417    allow_past: u8,
418) {
419    assert!(!callback_ptr.is_null());
420
421    let name = unsafe { cstr_as_str(name_ptr) };
422    let callback = if callback_ptr == unsafe { ffi::Py_None() } {
423        None
424    } else {
425        let callback = Python::attach(|py| unsafe {
426            Bound::<PyAny>::from_borrowed_ptr(py, callback_ptr).unbind()
427        });
428        Some(TimeEventCallback::from(callback))
429    };
430
431    clock
432        .set_time_alert_ns(name, alert_time_ns, callback, Some(allow_past != 0))
433        .expect(FAILED);
434}
435
436/// # Safety
437///
438/// This function assumes:
439/// - `name_ptr` is a valid C string pointer.
440/// - `callback_ptr` is a valid `PyCallable` pointer.
441///
442/// # Parameters
443///
444/// - `start_time_ns`: UNIX timestamp in nanoseconds. Use `0` to indicate "use current time".
445/// - `stop_time_ns`: UNIX timestamp in nanoseconds. Use `0` to indicate "no stop time".
446///
447/// # Panics
448///
449/// This function panics if:
450/// - `name` is not a valid string.
451/// - `callback_ptr` is NULL and no default callback has been assigned on the clock.
452#[cfg(feature = "python")]
453#[unsafe(no_mangle)]
454pub unsafe extern "C" fn live_clock_set_timer(
455    clock: &mut LiveClock_API,
456    name_ptr: *const c_char,
457    interval_ns: u64,
458    start_time_ns: UnixNanos,
459    stop_time_ns: UnixNanos,
460    callback_ptr: *mut ffi::PyObject,
461    allow_past: u8,
462    fire_immediately: u8,
463) {
464    assert!(!callback_ptr.is_null());
465
466    let name = unsafe { cstr_as_str(name_ptr) };
467    // C API convention: 0 means None (use defaults)
468    let start_time_ns = (start_time_ns != 0).then_some(start_time_ns);
469    let stop_time_ns = (stop_time_ns != 0).then_some(stop_time_ns);
470    let callback = if callback_ptr == unsafe { ffi::Py_None() } {
471        None
472    } else {
473        let callback = Python::attach(|py| unsafe {
474            Bound::<PyAny>::from_borrowed_ptr(py, callback_ptr).unbind()
475        });
476        Some(TimeEventCallback::from(callback))
477    };
478
479    clock
480        .set_timer_ns(
481            name,
482            interval_ns,
483            start_time_ns,
484            stop_time_ns,
485            callback,
486            Some(allow_past != 0),
487            Some(fire_immediately != 0),
488        )
489        .expect(FAILED);
490}
491
492/// # Safety
493///
494/// Assumes `name_ptr` is a valid C string pointer.
495#[unsafe(no_mangle)]
496pub unsafe extern "C" fn live_clock_next_time(
497    clock: &mut LiveClock_API,
498    name_ptr: *const c_char,
499) -> UnixNanos {
500    let name = unsafe { cstr_as_str(name_ptr) };
501    clock.next_time_ns(name).unwrap_or_default()
502}
503
504/// # Safety
505///
506/// Assumes `name_ptr` is a valid C string pointer.
507#[unsafe(no_mangle)]
508pub unsafe extern "C" fn live_clock_cancel_timer(
509    clock: &mut LiveClock_API,
510    name_ptr: *const c_char,
511) {
512    let name = unsafe { cstr_as_str(name_ptr) };
513    clock.cancel_timer(name);
514}
515
516#[unsafe(no_mangle)]
517pub extern "C" fn live_clock_cancel_timers(clock: &mut LiveClock_API) {
518    clock.cancel_timers();
519}