nautilus_common/ffi/
clock.rs

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