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