nautilus_common/ffi/
clock.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Posei Systems Pty Ltd. All rights reserved.
3//  https://poseitrader.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    fire_immediately: u8,
191) {
192    assert!(!callback_ptr.is_null());
193
194    let name = unsafe { cstr_as_str(name_ptr) };
195    let start_time_ns = (start_time_ns != 0).then_some(start_time_ns);
196    let stop_time_ns = (stop_time_ns != 0).then_some(stop_time_ns);
197    let callback = if callback_ptr == unsafe { ffi::Py_None() } {
198        None
199    } else {
200        let callback =
201            Python::with_gil(|py| unsafe { PyObject::from_borrowed_ptr(py, callback_ptr) });
202        Some(TimeEventCallback::from(callback))
203    };
204
205    clock
206        .set_timer_ns(
207            name,
208            interval_ns,
209            start_time_ns,
210            stop_time_ns,
211            callback,
212            Some(allow_past != 0),
213            Some(fire_immediately != 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    // Initialize a live clock without a time event sender
307    LiveClock_API(Box::new(LiveClock::new(None)))
308}
309
310#[unsafe(no_mangle)]
311pub extern "C" fn live_clock_drop(clock: LiveClock_API) {
312    drop(clock); // Memory freed here
313}
314
315/// # Safety
316///
317/// Assumes `callback_ptr` is a valid `PyCallable` pointer.
318///
319/// # Panics
320///
321/// Panics if `callback_ptr` is null or represents the Python `None` object.
322#[cfg(feature = "python")]
323#[unsafe(no_mangle)]
324pub unsafe extern "C" fn live_clock_register_default_handler(
325    clock: &mut LiveClock_API,
326    callback_ptr: *mut ffi::PyObject,
327) {
328    assert!(!callback_ptr.is_null());
329    assert!(unsafe { ffi::Py_None() } != callback_ptr);
330
331    let callback = Python::with_gil(|py| unsafe { PyObject::from_borrowed_ptr(py, callback_ptr) });
332    let callback = TimeEventCallback::from(callback);
333
334    clock.register_default_handler(callback);
335}
336
337#[unsafe(no_mangle)]
338pub extern "C" fn live_clock_timestamp(clock: &mut LiveClock_API) -> f64 {
339    clock.get_time()
340}
341
342#[unsafe(no_mangle)]
343pub extern "C" fn live_clock_timestamp_ms(clock: &mut LiveClock_API) -> u64 {
344    clock.get_time_ms()
345}
346
347#[unsafe(no_mangle)]
348pub extern "C" fn live_clock_timestamp_us(clock: &mut LiveClock_API) -> u64 {
349    clock.get_time_us()
350}
351
352#[unsafe(no_mangle)]
353pub extern "C" fn live_clock_timestamp_ns(clock: &mut LiveClock_API) -> u64 {
354    clock.get_time_ns().as_u64()
355}
356
357#[unsafe(no_mangle)]
358pub extern "C" fn live_clock_timer_names(clock: &LiveClock_API) -> *const c_char {
359    // For simplicity we join a string with a reasonably unique delimiter.
360    // This is a temporary solution pending the removal of Cython.
361    str_to_cstr(&clock.timer_names().join("<,>"))
362}
363
364#[unsafe(no_mangle)]
365pub extern "C" fn live_clock_timer_count(clock: &mut LiveClock_API) -> usize {
366    clock.timer_count()
367}
368
369/// # Safety
370///
371/// This function assumes:
372/// - `name_ptr` is a valid C string pointer.
373/// - `callback_ptr` is a valid `PyCallable` pointer.
374///
375/// # Panics
376///
377/// This function panics if:
378/// - `name` is not a valid string.
379/// - `callback_ptr` is NULL and no default callback has been assigned on the clock.
380#[cfg(feature = "python")]
381#[unsafe(no_mangle)]
382pub unsafe extern "C" fn live_clock_set_time_alert(
383    clock: &mut LiveClock_API,
384    name_ptr: *const c_char,
385    alert_time_ns: UnixNanos,
386    callback_ptr: *mut ffi::PyObject,
387    allow_past: u8,
388) {
389    assert!(!callback_ptr.is_null());
390
391    let name = unsafe { cstr_as_str(name_ptr) };
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_time_alert_ns(name, alert_time_ns, callback, Some(allow_past != 0))
402        .expect(FAILED);
403}
404
405/// # Safety
406///
407/// This function assumes:
408/// - `name_ptr` is a valid C string pointer.
409/// - `callback_ptr` is a valid `PyCallable` pointer.
410///
411/// # Panics
412///
413/// This function panics if:
414/// - `name` is not a valid string.
415/// - `callback_ptr` is NULL and no default callback has been assigned on the clock.
416#[cfg(feature = "python")]
417#[unsafe(no_mangle)]
418pub unsafe extern "C" fn live_clock_set_timer(
419    clock: &mut LiveClock_API,
420    name_ptr: *const c_char,
421    interval_ns: u64,
422    start_time_ns: UnixNanos,
423    stop_time_ns: UnixNanos,
424    callback_ptr: *mut ffi::PyObject,
425    allow_past: u8,
426    fire_immediately: u8,
427) {
428    assert!(!callback_ptr.is_null());
429
430    let name = unsafe { cstr_as_str(name_ptr) };
431    let start_time_ns = (start_time_ns != 0).then_some(start_time_ns);
432    let stop_time_ns = (stop_time_ns != 0).then_some(stop_time_ns);
433    let callback = if callback_ptr == unsafe { ffi::Py_None() } {
434        None
435    } else {
436        let callback =
437            Python::with_gil(|py| unsafe { PyObject::from_borrowed_ptr(py, callback_ptr) });
438        Some(TimeEventCallback::from(callback))
439    };
440
441    clock
442        .set_timer_ns(
443            name,
444            interval_ns,
445            start_time_ns,
446            stop_time_ns,
447            callback,
448            Some(allow_past != 0),
449            Some(fire_immediately != 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}