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