|
| 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 | + |
| 16 | +use nautilus_common::{clock::TestClock, timer::TimeEventHandlerV2}; |
| 17 | +use nautilus_core::UnixNanos; |
| 18 | + |
| 19 | +/// Provides a means of accumulating and draining time event handlers. |
| 20 | +pub struct TimeEventAccumulator { |
| 21 | + event_handlers: Vec<TimeEventHandlerV2>, |
| 22 | +} |
| 23 | + |
| 24 | +impl TimeEventAccumulator { |
| 25 | + /// Creates a new [`TimeEventAccumulator`] instance. |
| 26 | + #[must_use] |
| 27 | + pub const fn new() -> Self { |
| 28 | + Self { |
| 29 | + event_handlers: Vec::new(), |
| 30 | + } |
| 31 | + } |
| 32 | + |
| 33 | + /// Advance the given clock to the `to_time_ns`. |
| 34 | + pub fn advance_clock(&mut self, clock: &mut TestClock, to_time_ns: UnixNanos, set_time: bool) { |
| 35 | + let events = clock.advance_time(to_time_ns, set_time); |
| 36 | + let handlers = clock.match_handlers(events); |
| 37 | + self.event_handlers.extend(handlers); |
| 38 | + } |
| 39 | + |
| 40 | + /// Drain the accumulated time event handlers in sorted order (by the events `ts_event`). |
| 41 | + pub fn drain(&mut self) -> Vec<TimeEventHandlerV2> { |
| 42 | + // stable sort is not necessary since there is no relation between |
| 43 | + // events of the same clock. Only time based ordering is needed. |
| 44 | + self.event_handlers |
| 45 | + .sort_unstable_by_key(|v| v.event.ts_event); |
| 46 | + self.event_handlers.drain(..).collect() |
| 47 | + } |
| 48 | +} |
| 49 | + |
| 50 | +impl Default for TimeEventAccumulator { |
| 51 | + /// Creates a new default [`TimeEventAccumulator`] instance. |
| 52 | + fn default() -> Self { |
| 53 | + Self::new() |
| 54 | + } |
| 55 | +} |
| 56 | + |
| 57 | +//////////////////////////////////////////////////////////////////////////////// |
| 58 | +// Tests |
| 59 | +//////////////////////////////////////////////////////////////////////////////// |
| 60 | +#[cfg(all(test, feature = "python"))] |
| 61 | +mod tests { |
| 62 | + use nautilus_common::timer::{TimeEvent, TimeEventCallback}; |
| 63 | + use nautilus_core::UUID4; |
| 64 | + use pyo3::{Py, Python, prelude::*, types::PyList}; |
| 65 | + use rstest::*; |
| 66 | + use ustr::Ustr; |
| 67 | + |
| 68 | + use super::*; |
| 69 | + |
| 70 | + #[rstest] |
| 71 | + fn test_accumulator_drain_sorted() { |
| 72 | + pyo3::prepare_freethreaded_python(); |
| 73 | + |
| 74 | + Python::with_gil(|py| { |
| 75 | + let py_list = PyList::empty(py); |
| 76 | + let py_append = Py::from(py_list.getattr("append").unwrap()); |
| 77 | + |
| 78 | + let mut accumulator = TimeEventAccumulator::new(); |
| 79 | + |
| 80 | + let time_event1 = TimeEvent::new( |
| 81 | + Ustr::from("TEST_EVENT_1"), |
| 82 | + UUID4::new(), |
| 83 | + 100.into(), |
| 84 | + 100.into(), |
| 85 | + ); |
| 86 | + let time_event2 = TimeEvent::new( |
| 87 | + Ustr::from("TEST_EVENT_2"), |
| 88 | + UUID4::new(), |
| 89 | + 300.into(), |
| 90 | + 300.into(), |
| 91 | + ); |
| 92 | + let time_event3 = TimeEvent::new( |
| 93 | + Ustr::from("TEST_EVENT_3"), |
| 94 | + UUID4::new(), |
| 95 | + 200.into(), |
| 96 | + 200.into(), |
| 97 | + ); |
| 98 | + |
| 99 | + // Note: as_ptr returns a borrowed pointer. It is valid as long |
| 100 | + // as the object is in scope. In this case `callback_ptr` is valid |
| 101 | + // as long as `py_append` is in scope. |
| 102 | + let callback = TimeEventCallback::from(py_append.into_any()); |
| 103 | + |
| 104 | + let handler1 = TimeEventHandlerV2::new(time_event1.clone(), callback.clone()); |
| 105 | + let handler2 = TimeEventHandlerV2::new(time_event2.clone(), callback.clone()); |
| 106 | + let handler3 = TimeEventHandlerV2::new(time_event3.clone(), callback); |
| 107 | + |
| 108 | + accumulator.event_handlers.push(handler1); |
| 109 | + accumulator.event_handlers.push(handler2); |
| 110 | + accumulator.event_handlers.push(handler3); |
| 111 | + |
| 112 | + let drained_handlers = accumulator.drain(); |
| 113 | + |
| 114 | + assert_eq!(drained_handlers.len(), 3); |
| 115 | + assert_eq!(drained_handlers[0].event.ts_event, time_event1.ts_event); |
| 116 | + assert_eq!(drained_handlers[1].event.ts_event, time_event3.ts_event); |
| 117 | + assert_eq!(drained_handlers[2].event.ts_event, time_event2.ts_event); |
| 118 | + }); |
| 119 | + } |
| 120 | +} |
0 commit comments