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