diff --git a/nautilus_core/Cargo.lock b/nautilus_core/Cargo.lock index 34af5ed1d957..db9c15cc6406 100644 --- a/nautilus_core/Cargo.lock +++ b/nautilus_core/Cargo.lock @@ -2447,6 +2447,7 @@ dependencies = [ "pyo3", "rstest", "tempfile", + "ustr", ] [[package]] diff --git a/nautilus_core/backtest/Cargo.toml b/nautilus_core/backtest/Cargo.toml index bd6e83e57053..1ea8e22a31a5 100644 --- a/nautilus_core/backtest/Cargo.toml +++ b/nautilus_core/backtest/Cargo.toml @@ -15,6 +15,7 @@ nautilus-common = { path = "../common" } nautilus-core = { path = "../core" } nautilus-model = { path = "../model" } pyo3 = { workspace = true, optional = true } +ustr = { workspace = true } [dev-dependencies] tempfile = { workspace = true } diff --git a/nautilus_core/backtest/src/engine.rs b/nautilus_core/backtest/src/engine.rs index 1919438d7b14..32088d9df8b8 100644 --- a/nautilus_core/backtest/src/engine.rs +++ b/nautilus_core/backtest/src/engine.rs @@ -113,6 +113,7 @@ mod tests { use nautilus_core::uuid::UUID4; use pyo3::{types::PyList, Py, Python}; use rstest::*; + use ustr::Ustr; use super::*; @@ -126,9 +127,12 @@ mod tests { let mut accumulator = TimeEventAccumulator::new(); - let time_event1 = TimeEvent::new("TEST_EVENT_1", UUID4::new(), 100, 100).unwrap(); - let time_event2 = TimeEvent::new("TEST_EVENT_2", UUID4::new(), 300, 300).unwrap(); - let time_event3 = TimeEvent::new("TEST_EVENT_3", UUID4::new(), 200, 200).unwrap(); + let time_event1 = + TimeEvent::new(Ustr::from("TEST_EVENT_1"), UUID4::new(), 100, 100).unwrap(); + let time_event2 = + TimeEvent::new(Ustr::from("TEST_EVENT_2"), UUID4::new(), 300, 300).unwrap(); + let time_event3 = + TimeEvent::new(Ustr::from("TEST_EVENT_3"), UUID4::new(), 200, 200).unwrap(); // Note: as_ptr returns a borrowed pointer. It is valid as long // as the object is in scope. In this case `callback_ptr` is valid diff --git a/nautilus_core/common/src/clock.rs b/nautilus_core/common/src/clock.rs index 780ec77c6840..4d057769c19c 100644 --- a/nautilus_core/common/src/clock.rs +++ b/nautilus_core/common/src/clock.rs @@ -19,6 +19,7 @@ use nautilus_core::{ correctness::check_valid_string, time::{AtomicTime, ClockMode, UnixNanos}, }; +use ustr::Ustr; use crate::{ handlers::EventHandler, @@ -44,7 +45,7 @@ pub trait Clock { /// callback gets used to handle generated events. fn set_time_alert_ns( &mut self, - name: String, + name: &str, alert_time_ns: UnixNanos, callback: Option, ); @@ -54,7 +55,7 @@ pub trait Clock { /// used to handle generated event. fn set_timer_ns( &mut self, - name: String, + name: &str, interval_ns: u64, start_time_ns: UnixNanos, stop_time_ns: Option, @@ -72,9 +73,9 @@ pub trait Clock { )] pub struct TestClock { time: AtomicTime, - timers: HashMap, + timers: HashMap, default_callback: Option, - callbacks: HashMap, + callbacks: HashMap, } impl TestClock { @@ -93,7 +94,7 @@ impl TestClock { } #[must_use] - pub fn get_timers(&self) -> &HashMap { + pub fn get_timers(&self) -> &HashMap { &self.timers } @@ -125,15 +126,11 @@ impl TestClock { events .into_iter() .map(|event| { - let handler = self - .callbacks - .get(event.name.as_str()) - .cloned() - .unwrap_or_else(|| { - // If callback_py is None, use the default_callback_py - // TODO: clone for now - self.default_callback.clone().unwrap() - }); + let handler = self.callbacks.get(&event.name).cloned().unwrap_or_else(|| { + // If callback_py is None, use the default_callback_py + // TODO: clone for now + self.default_callback.clone().unwrap() + }); TimeEventHandler { event, callback_ptr: handler.as_ptr(), @@ -179,58 +176,55 @@ impl Clock for TestClock { fn set_time_alert_ns( &mut self, - name: String, + name: &str, alert_time_ns: UnixNanos, callback: Option, ) { - check_valid_string(&name, "`Timer` name").unwrap(); + check_valid_string(name, "`Timer` name").unwrap(); assert!( callback.is_some() | self.default_callback.is_some(), "All Python callbacks were `None`" ); + let name_ustr = Ustr::from(name); match callback { - Some(callback_py) => self.callbacks.insert(name.clone(), callback_py), + Some(callback_py) => self.callbacks.insert(name_ustr, callback_py), None => None, }; // TODO: should the atomic clock be shared // currently share timestamp nanoseconds let time_ns = self.time.get_time_ns(); - let timer = TestTimer::new( - name.clone(), - alert_time_ns - time_ns, - time_ns, - Some(alert_time_ns), - ); - self.timers.insert(name, timer); + let timer = TestTimer::new(name, alert_time_ns - time_ns, time_ns, Some(alert_time_ns)); + self.timers.insert(name_ustr, timer); } fn set_timer_ns( &mut self, - name: String, + name: &str, interval_ns: u64, start_time_ns: UnixNanos, stop_time_ns: Option, callback: Option, ) { - check_valid_string(&name, "`Timer` name").unwrap(); + check_valid_string(name, "`Timer` name").unwrap(); assert!( callback.is_some() | self.default_callback.is_some(), "All Python callbacks were `None`" ); + let name_ustr = Ustr::from(name); match callback { - Some(callback_py) => self.callbacks.insert(name.clone(), callback_py), + Some(callback_py) => self.callbacks.insert(name_ustr, callback_py), None => None, }; - let timer = TestTimer::new(name.clone(), interval_ns, start_time_ns, stop_time_ns); - self.timers.insert(name, timer); + let timer = TestTimer::new(name, interval_ns, start_time_ns, stop_time_ns); + self.timers.insert(name_ustr, timer); } fn next_time_ns(&self, name: &str) -> UnixNanos { - let timer = self.timers.get(name); + let timer = self.timers.get(&Ustr::from(name)); match timer { None => 0, Some(timer) => timer.next_time_ns, @@ -238,7 +232,7 @@ impl Clock for TestClock { } fn cancel_timer(&mut self, name: &str) { - let timer = self.timers.remove(name); + let timer = self.timers.remove(&Ustr::from(name)); match timer { None => {} Some(mut timer) => timer.cancel(), @@ -259,9 +253,9 @@ impl Clock for TestClock { )] pub struct LiveClock { internal: AtomicTime, - timers: HashMap, + timers: HashMap, default_callback: Option, - callbacks: HashMap, + callbacks: HashMap, } impl LiveClock { @@ -312,57 +306,54 @@ impl Clock for LiveClock { fn set_time_alert_ns( &mut self, - name: String, + name: &str, mut alert_time_ns: UnixNanos, callback: Option, ) { - check_valid_string(&name, "`Timer` name").unwrap(); + check_valid_string(name, "`Timer` name").unwrap(); assert!( callback.is_some() | self.default_callback.is_some(), "All Python callbacks were `None`" ); + let name_ustr = Ustr::from(name); match callback { - Some(callback_py) => self.callbacks.insert(name.clone(), callback_py), + Some(callback_py) => self.callbacks.insert(name_ustr, callback_py), None => None, }; let ts_now = self.get_time_ns(); alert_time_ns = std::cmp::max(alert_time_ns, ts_now); - let timer = TestTimer::new( - name.clone(), - alert_time_ns - ts_now, - ts_now, - Some(alert_time_ns), - ); - self.timers.insert(name, timer); + let timer = TestTimer::new(name, alert_time_ns - ts_now, ts_now, Some(alert_time_ns)); + self.timers.insert(name_ustr, timer); } fn set_timer_ns( &mut self, - name: String, + name: &str, interval_ns: u64, start_time_ns: UnixNanos, stop_time_ns: Option, callback: Option, ) { - check_valid_string(&name, "`Timer` name").unwrap(); + check_valid_string(name, "`Timer` name").unwrap(); assert!( callback.is_some() | self.default_callback.is_some(), "All Python callbacks were `None`" ); + let name_ustr = Ustr::from(name); match callback { - Some(callback) => self.callbacks.insert(name.clone(), callback), + Some(callback) => self.callbacks.insert(name_ustr, callback), None => None, }; - let timer = TestTimer::new(name.clone(), interval_ns, start_time_ns, stop_time_ns); - self.timers.insert(name, timer); + let timer = TestTimer::new(name, interval_ns, start_time_ns, stop_time_ns); + self.timers.insert(name_ustr, timer); } fn next_time_ns(&self, name: &str) -> UnixNanos { - let timer = self.timers.get(name); + let timer = self.timers.get(&Ustr::from(name)); match timer { None => 0, Some(timer) => timer.next_time_ns, @@ -370,7 +361,7 @@ impl Clock for LiveClock { } fn cancel_timer(&mut self, name: &str) { - let timer = self.timers.remove(name); + let timer = self.timers.remove(&Ustr::from(name)); match timer { None => {} Some(mut timer) => timer.cancel(), @@ -421,9 +412,10 @@ mod tests { let handler = EventHandler::new(Some(py_append), None); test_clock.register_default_handler(handler); - test_clock.set_timer_ns(String::from("TEST_TIME1"), 10, 0, None, None); + let timer_name = "TEST_TIME1"; + test_clock.set_timer_ns(timer_name, 10, 0, None, None); - assert_eq!(test_clock.timer_names(), ["TEST_TIME1"]); + assert_eq!(test_clock.timer_names(), [timer_name]); assert_eq!(test_clock.timer_count(), 1); }); } @@ -438,8 +430,9 @@ mod tests { let handler = EventHandler::new(Some(py_append), None); test_clock.register_default_handler(handler); - test_clock.set_timer_ns(String::from("TEST_TIME1"), 10, 0, None, None); - test_clock.cancel_timer(String::from("TEST_TIME1").as_str()); + let timer_name = "TEST_TIME1"; + test_clock.set_timer_ns(timer_name, 10, 0, None, None); + test_clock.cancel_timer(timer_name); assert!(test_clock.timer_names().is_empty()); assert_eq!(test_clock.timer_count(), 0); @@ -456,7 +449,8 @@ mod tests { let handler = EventHandler::new(Some(py_append), None); test_clock.register_default_handler(handler); - test_clock.set_timer_ns(String::from("TEST_TIME1"), 10, 0, None, None); + let timer_name = "TEST_TIME1"; + test_clock.set_timer_ns(timer_name, 10, 0, None, None); test_clock.cancel_timers(); assert!(test_clock.timer_names().is_empty()); @@ -474,10 +468,11 @@ mod tests { let handler = EventHandler::new(Some(py_append), None); test_clock.register_default_handler(handler); - test_clock.set_timer_ns(String::from("TEST_TIME1"), 1, 1, Some(3), None); + let timer_name = "TEST_TIME1"; + test_clock.set_timer_ns(timer_name, 1, 1, Some(3), None); test_clock.advance_time(2, true); - assert_eq!(test_clock.timer_names(), ["TEST_TIME1"]); + assert_eq!(test_clock.timer_names(), [timer_name]); assert_eq!(test_clock.timer_count(), 1); }); } @@ -492,7 +487,7 @@ mod tests { let handler = EventHandler::new(Some(py_append), None); test_clock.register_default_handler(handler); - test_clock.set_timer_ns(String::from("TEST_TIME1"), 2, 0, Some(3), None); + test_clock.set_timer_ns("TEST_TIME1", 2, 0, Some(3), None); test_clock.advance_time(3, true); assert_eq!(test_clock.timer_names().len(), 1); @@ -511,7 +506,7 @@ mod tests { let handler = EventHandler::new(Some(py_append), None); test_clock.register_default_handler(handler); - test_clock.set_timer_ns(String::from("TEST_TIME1"), 2, 0, Some(3), None); + test_clock.set_timer_ns("TEST_TIME1", 2, 0, Some(3), None); test_clock.advance_time(3, false); assert_eq!(test_clock.timer_names().len(), 1); diff --git a/nautilus_core/common/src/ffi/clock.rs b/nautilus_core/common/src/ffi/clock.rs index d510837c9360..b6c334859fd5 100644 --- a/nautilus_core/common/src/ffi/clock.rs +++ b/nautilus_core/common/src/ffi/clock.rs @@ -150,7 +150,7 @@ pub unsafe extern "C" fn test_clock_set_time_alert_ns( }); let handler = EventHandler::new(callback_py.clone(), None); - clock.set_time_alert_ns(name, alert_time_ns, callback_py.map(|_| handler)); + clock.set_time_alert_ns(&name, alert_time_ns, callback_py.map(|_| handler)); } /// # Safety @@ -181,7 +181,7 @@ pub unsafe extern "C" fn test_clock_set_timer_ns( let handler = EventHandler::new(callback_py.clone(), None); clock.set_timer_ns( - name, + &name, interval_ns, start_time_ns, stop_time_ns, diff --git a/nautilus_core/common/src/ffi/timer.rs b/nautilus_core/common/src/ffi/timer.rs index 53946247b166..04fb1f6b02d2 100644 --- a/nautilus_core/common/src/ffi/timer.rs +++ b/nautilus_core/common/src/ffi/timer.rs @@ -16,7 +16,7 @@ use std::ffi::c_char; use nautilus_core::{ - ffi::string::{cstr_to_string, str_to_cstr}, + ffi::string::{cstr_to_ustr, str_to_cstr}, uuid::UUID4, }; @@ -32,7 +32,7 @@ pub unsafe extern "C" fn time_event_new( ts_event: u64, ts_init: u64, ) -> TimeEvent { - TimeEvent::new(&cstr_to_string(name_ptr), event_id, ts_event, ts_init).unwrap() + TimeEvent::new(cstr_to_ustr(name_ptr), event_id, ts_event, ts_init).unwrap() } /// Returns a [`TimeEvent`] as a C string pointer. diff --git a/nautilus_core/common/src/python/timer.rs b/nautilus_core/common/src/python/timer.rs index b01f456389f1..5f850e60c6d5 100644 --- a/nautilus_core/common/src/python/timer.rs +++ b/nautilus_core/common/src/python/timer.rs @@ -35,7 +35,7 @@ impl TimeEvent { ts_event: UnixNanos, ts_init: UnixNanos, ) -> PyResult { - Self::new(name, event_id, ts_event, ts_init).map_err(to_pyvalue_err) + Self::new(Ustr::from(name), event_id, ts_event, ts_init).map_err(to_pyvalue_err) } fn __setstate__(&mut self, py: Python, state: PyObject) -> PyResult<()> { @@ -67,7 +67,7 @@ impl TimeEvent { #[staticmethod] fn _safe_constructor() -> PyResult { - Ok(Self::new("NULL", UUID4::new(), 0, 0).unwrap()) // Safe default + Ok(Self::new(Ustr::from("NULL"), UUID4::new(), 0, 0).unwrap()) // Safe default } fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py { diff --git a/nautilus_core/common/src/timer.rs b/nautilus_core/common/src/timer.rs index c83cd1d65e9b..4d6a38cf0a09 100644 --- a/nautilus_core/common/src/timer.rs +++ b/nautilus_core/common/src/timer.rs @@ -48,15 +48,13 @@ pub struct TimeEvent { impl TimeEvent { pub fn new( - name: &str, + name: Ustr, event_id: UUID4, ts_event: UnixNanos, ts_init: UnixNanos, ) -> Result { - check_valid_string(name, "`TimeEvent` name")?; - Ok(Self { - name: Ustr::from(name), + name, event_id, ts_event, ts_init, @@ -112,7 +110,7 @@ impl Ord for TimeEventHandler { pub trait Timer { fn new( - name: String, + name: Ustr, interval_ns: TimedeltaNanos, start_time_ns: UnixNanos, stop_time_ns: Option, @@ -122,9 +120,9 @@ pub trait Timer { fn cancel(&mut self); } -#[derive(Clone)] +#[derive(Clone, Copy)] pub struct TestTimer { - pub name: String, + pub name: Ustr, pub interval_ns: u64, pub start_time_ns: UnixNanos, pub stop_time_ns: Option, @@ -135,15 +133,15 @@ pub struct TestTimer { impl TestTimer { #[must_use] pub fn new( - name: String, + name: &str, interval_ns: u64, start_time_ns: UnixNanos, stop_time_ns: Option, ) -> Self { - check_valid_string(&name, "`TestTimer` name").unwrap(); + check_valid_string(name, "`TestTimer` name").unwrap(); Self { - name, + name: Ustr::from(name), interval_ns, start_time_ns, stop_time_ns, @@ -186,7 +184,7 @@ impl Iterator for TestTimer { } else { let item = ( TimeEvent { - name: Ustr::from(&self.name), + name: self.name, event_id: UUID4::new(), ts_event: self.next_time_ns, ts_init: self.next_time_ns, @@ -219,8 +217,7 @@ mod tests { #[rstest] fn test_pop_event() { - let name = String::from("test_timer"); - let mut timer = TestTimer::new(name, 0, 1, None); + let mut timer = TestTimer::new("test_timer", 0, 1, None); assert!(timer.next().is_some()); assert!(timer.next().is_some()); @@ -230,8 +227,7 @@ mod tests { #[rstest] fn test_advance_within_next_time_ns() { - let name = String::from("test_timer"); - let mut timer = TestTimer::new(name, 5, 0, None); + let mut timer = TestTimer::new("test_timer", 5, 0, None); let _: Vec = timer.advance(1).collect(); let _: Vec = timer.advance(2).collect(); let _: Vec = timer.advance(3).collect(); @@ -242,32 +238,28 @@ mod tests { #[rstest] fn test_advance_up_to_next_time_ns() { - let name = String::from("test_timer"); - let mut timer = TestTimer::new(name, 1, 0, None); + let mut timer = TestTimer::new("test_timer", 1, 0, None); assert_eq!(timer.advance(1).count(), 1); assert!(!timer.is_expired); } #[rstest] fn test_advance_up_to_next_time_ns_with_stop_time() { - let name = String::from("test_timer"); - let mut timer = TestTimer::new(name, 1, 0, Some(2)); + let mut timer = TestTimer::new("test_timer", 1, 0, Some(2)); assert_eq!(timer.advance(2).count(), 2); assert!(timer.is_expired); } #[rstest] fn test_advance_beyond_next_time_ns() { - let name = String::from("test_timer"); - let mut timer = TestTimer::new(name, 1, 0, Some(5)); + let mut timer = TestTimer::new("test_timer", 1, 0, Some(5)); assert_eq!(timer.advance(5).count(), 5); assert!(timer.is_expired); } #[rstest] fn test_advance_beyond_stop_time() { - let name = String::from("test_timer"); - let mut timer = TestTimer::new(name, 1, 0, Some(5)); + let mut timer = TestTimer::new("test_timer", 1, 0, Some(5)); assert_eq!(timer.advance(10).count(), 5); assert!(timer.is_expired); }