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}