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