nautilus_common/runner.rs
1// -------------------------------------------------------------------------------------------------
2// Copyright (C) 2015-2026 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//! Global runtime machinery and thread-local storage.
17//!
18//! This module provides global access to shared runtime resources including clocks,
19//! message queues, and time event channels. It manages thread-local storage for
20//! system-wide components that need to be accessible across threads.
21
22use std::{cell::OnceCell, fmt::Debug, sync::Arc};
23
24use crate::{
25 messages::{data::DataCommand, execution::TradingCommand},
26 msgbus::{self, switchboard::MessagingSwitchboard},
27 timer::TimeEventHandler,
28};
29
30/// Trait for data command sending that can be implemented for both sync and async runners.
31pub trait DataCommandSender {
32 /// Executes a data command.
33 ///
34 /// - **Sync runners** send the command to a queue for synchronous execution.
35 /// - **Async runners** send the command to a channel for asynchronous execution.
36 fn execute(&self, command: DataCommand);
37}
38
39/// Synchronous implementation of DataCommandSender for backtest environments.
40#[derive(Debug)]
41pub struct SyncDataCommandSender;
42
43impl DataCommandSender for SyncDataCommandSender {
44 fn execute(&self, command: DataCommand) {
45 // TODO: Placeholder, we still need to queue and drain even for sync
46 let endpoint = MessagingSwitchboard::data_engine_execute();
47 msgbus::send_any(endpoint, &command);
48 }
49}
50
51/// Gets the global data command sender.
52///
53/// # Panics
54///
55/// Panics if the sender is uninitialized.
56#[must_use]
57pub fn get_data_cmd_sender() -> Arc<dyn DataCommandSender> {
58 DATA_CMD_SENDER.with(|sender| {
59 sender
60 .get()
61 .expect("Data command sender should be initialized by runner")
62 .clone()
63 })
64}
65
66/// Sets the global data command sender.
67///
68/// This should be called by the runner when it initializes.
69/// Can only be called once per thread.
70///
71/// # Panics
72///
73/// Panics if a sender has already been set.
74pub fn set_data_cmd_sender(sender: Arc<dyn DataCommandSender>) {
75 DATA_CMD_SENDER.with(|s| {
76 assert!(
77 s.set(sender).is_ok(),
78 "Data command sender can only be set once"
79 );
80 });
81}
82
83/// Trait for time event sending that can be implemented for both sync and async runners.
84pub trait TimeEventSender: Debug + Send + Sync {
85 /// Sends a time event handler.
86 fn send(&self, handler: TimeEventHandler);
87}
88
89/// Gets the global time event sender.
90///
91/// # Panics
92///
93/// Panics if the sender is uninitialized.
94#[must_use]
95pub fn get_time_event_sender() -> Arc<dyn TimeEventSender> {
96 TIME_EVENT_SENDER.with(|sender| {
97 sender
98 .get()
99 .expect("Time event sender should be initialized by runner")
100 .clone()
101 })
102}
103
104/// Attempts to get the global time event sender without panicking.
105///
106/// Returns `None` if the sender is not initialized (e.g., in test environments).
107#[must_use]
108pub fn try_get_time_event_sender() -> Option<Arc<dyn TimeEventSender>> {
109 TIME_EVENT_SENDER.with(|sender| sender.get().cloned())
110}
111
112/// Sets the global time event sender.
113///
114/// Can only be called once per thread.
115///
116/// # Panics
117///
118/// Panics if a sender has already been set.
119pub fn set_time_event_sender(sender: Arc<dyn TimeEventSender>) {
120 TIME_EVENT_SENDER.with(|s| {
121 assert!(
122 s.set(sender).is_ok(),
123 "Time event sender can only be set once"
124 );
125 });
126}
127
128/// Trait for trading command sending that can be implemented for both sync and async runners.
129pub trait TradingCommandSender {
130 /// Executes a trading command.
131 ///
132 /// - **Sync runners** send the command to a queue for synchronous execution.
133 /// - **Async runners** send the command to a channel for asynchronous execution.
134 fn execute(&self, command: TradingCommand);
135}
136
137/// Gets the global trading command sender.
138///
139/// # Panics
140///
141/// Panics if the sender is uninitialized.
142#[must_use]
143pub fn get_trading_cmd_sender() -> Arc<dyn TradingCommandSender> {
144 EXEC_CMD_SENDER.with(|sender| {
145 sender
146 .get()
147 .expect("Trading command sender should be initialized by runner")
148 .clone()
149 })
150}
151
152/// Sets the global trading command sender.
153///
154/// This should be called by the runner when it initializes.
155/// Can only be called once per thread.
156///
157/// # Panics
158///
159/// Panics if a sender has already been set.
160pub fn set_exec_cmd_sender(sender: Arc<dyn TradingCommandSender>) {
161 EXEC_CMD_SENDER.with(|s| {
162 assert!(
163 s.set(sender).is_ok(),
164 "Trading command sender can only be set once"
165 );
166 });
167}
168
169thread_local! {
170 static TIME_EVENT_SENDER: OnceCell<Arc<dyn TimeEventSender>> = const { OnceCell::new() };
171 static DATA_CMD_SENDER: OnceCell<Arc<dyn DataCommandSender>> = const { OnceCell::new() };
172 static EXEC_CMD_SENDER: OnceCell<Arc<dyn TradingCommandSender>> = const { OnceCell::new() };
173}