Skip to main content

nautilus_common/live/
sender.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//! Message senders for standalone clients and live runtime dispatch.
17
18use std::thread::{self, ThreadId};
19
20use super::dispatch::DispatchMessage;
21
22/// A message sender which preserves callback ancestry when bound to a live runtime.
23///
24/// Sends on the runtime owner thread capture its active root. Sends on other threads
25/// and sends without an active root are independent ingress. Conversion from a plain Tokio sender supports standalone
26/// clients whose receivers consume domain events directly, without callback tracking.
27#[derive(Debug)]
28pub struct DispatchSender<T> {
29    channel: DispatchChannel<T>,
30}
31
32/// A sender for data, execution, and system events.
33pub type EventSender<T> = DispatchSender<T>;
34
35impl<T> DispatchSender<T> {
36    /// Binds a dispatch channel to the calling runtime thread.
37    #[must_use]
38    pub fn new(sender: tokio::sync::mpsc::UnboundedSender<DispatchMessage<T>>) -> Self {
39        Self {
40            channel: DispatchChannel::Dispatch {
41                sender,
42                owner: thread::current().id(),
43            },
44        }
45    }
46
47    // panics-doc-ok
48    /// Sends a message, preserving the active owner-thread callback root.
49    ///
50    /// # Errors
51    ///
52    /// Returns the undelivered envelope if the receiver is closed.
53    ///
54    /// # Panics
55    ///
56    /// Panics if the owner exhausts its channel context IDs.
57    pub fn send(
58        &self,
59        message: T,
60    ) -> Result<(), tokio::sync::mpsc::error::SendError<DispatchMessage<T>>> {
61        match &self.channel {
62            DispatchChannel::Plain(sender) => sender
63                .send(message)
64                .map_err(|e| tokio::sync::mpsc::error::SendError(e.0.into())),
65            DispatchChannel::Dispatch { sender, owner } => {
66                sender.send(DispatchMessage::new(message, *owner))
67            }
68        }
69    }
70
71    /// Returns whether the receiver has closed.
72    #[must_use]
73    pub fn is_closed(&self) -> bool {
74        match &self.channel {
75            DispatchChannel::Plain(sender) => sender.is_closed(),
76            DispatchChannel::Dispatch { sender, .. } => sender.is_closed(),
77        }
78    }
79
80    /// Returns whether both senders target the same channel.
81    #[must_use]
82    pub fn same_channel(&self, other: &Self) -> bool {
83        match (&self.channel, &other.channel) {
84            (DispatchChannel::Plain(left), DispatchChannel::Plain(right)) => {
85                left.same_channel(right)
86            }
87            (
88                DispatchChannel::Dispatch { sender: left, .. },
89                DispatchChannel::Dispatch { sender: right, .. },
90            ) => left.same_channel(right),
91            _ => false,
92        }
93    }
94}
95
96impl<T> Clone for DispatchSender<T> {
97    fn clone(&self) -> Self {
98        let channel = match &self.channel {
99            DispatchChannel::Plain(sender) => DispatchChannel::Plain(sender.clone()),
100            DispatchChannel::Dispatch { sender, owner } => DispatchChannel::Dispatch {
101                sender: sender.clone(),
102                owner: *owner,
103            },
104        };
105
106        Self { channel }
107    }
108}
109
110impl<T> From<tokio::sync::mpsc::UnboundedSender<T>> for DispatchSender<T> {
111    fn from(sender: tokio::sync::mpsc::UnboundedSender<T>) -> Self {
112        Self {
113            channel: DispatchChannel::Plain(sender),
114        }
115    }
116}
117
118#[derive(Debug)]
119enum DispatchChannel<T> {
120    Plain(tokio::sync::mpsc::UnboundedSender<T>),
121    Dispatch {
122        sender: tokio::sync::mpsc::UnboundedSender<DispatchMessage<T>>,
123        owner: ThreadId,
124    },
125}