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