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}