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