use std::{borrow::Cow, fmt, sync::Arc};
use erased_serde::Serialize as ErasedSerialize;
use serde::Serialize;
use smallbox::{smallbox, SmallBox};
use elfo_utils::time::SystemTime;
use super::{extract_name::extract_name, sequence_no::SequenceNo};
use crate::{actor::ActorMeta, envelope, scope, thread::ThreadId, tracing::TraceId, Message};
#[doc(hidden)]
#[instability::unstable]
pub struct Dump {
pub meta: Arc<ActorMeta>,
pub sequence_no: SequenceNo,
pub timestamp: SystemTime,
pub trace_id: TraceId,
pub thread_id: ThreadId,
pub direction: Direction,
pub message_name: MessageName,
pub message_protocol: &'static str,
pub message_kind: MessageKind,
pub message: ErasedMessage,
}
#[doc(hidden)]
#[instability::unstable]
pub type ErasedMessage = SmallBox<dyn ErasedSerialize + Send, [usize; 24]>;
assert_impl_all!(Dump: Send);
assert_eq_size!(Dump, [u8; 320]);
impl Dump {
#[instability::unstable]
pub fn builder() -> DumpBuilder {
DumpBuilder {
timestamp: None,
direction: Direction::Out,
message_name: None,
message_protocol: "",
message_kind: MessageKind::Regular,
}
}
pub(crate) fn message(
message: &impl Message,
kind: &envelope::MessageKind,
direction: Direction,
) -> Self {
Self::builder()
.direction(direction)
.message_name(message.name())
.message_protocol(message.protocol())
.message_kind(MessageKind::from_message_kind(kind))
.do_finish(message._erase())
}
}
#[instability::unstable]
pub struct DumpBuilder {
timestamp: Option<SystemTime>,
direction: Direction,
message_name: Option<MessageName>,
message_protocol: &'static str,
message_kind: MessageKind,
}
impl DumpBuilder {
#[instability::unstable]
pub fn timestamp(&mut self, timestamp: impl Into<SystemTime>) -> &mut Self {
self.timestamp = Some(timestamp.into());
self
}
#[instability::unstable]
pub fn direction(&mut self, direction: Direction) -> &mut Self {
self.direction = direction;
self
}
#[instability::unstable]
pub fn message_name(&mut self, name: impl Into<MessageName>) -> &mut Self {
self.message_name = Some(name.into());
self
}
#[instability::unstable]
pub fn message_protocol(&mut self, protocol: &'static str) -> &mut Self {
self.message_protocol = protocol;
self
}
#[instability::unstable]
pub fn message_kind(&mut self, kind: MessageKind) -> &mut Self {
self.message_kind = kind;
self
}
#[instability::unstable]
pub fn finish<M>(&mut self, message: M) -> Dump
where
M: Serialize + Send + 'static,
{
if self.message_name.is_none() {
self.message_name = Some(extract_name(&message));
}
self.do_finish(smallbox!(message))
}
fn do_finish(&mut self, message: ErasedMessage) -> Dump {
let (meta, trace_id, sequence_no) = scope::with(|scope| {
(
scope.meta().clone(),
scope.trace_id(),
scope.dumping().next_sequence_no(),
)
});
Dump {
meta,
sequence_no,
timestamp: self.timestamp.unwrap_or_else(SystemTime::now),
trace_id,
thread_id: crate::thread::id(),
direction: self.direction,
message_name: self.message_name.take().unwrap_or_default(),
message_protocol: self.message_protocol,
message_kind: self.message_kind,
message,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[instability::unstable]
pub enum Direction {
In,
Out,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
#[instability::unstable]
pub struct MessageName(&'static str, Option<&'static str>);
impl<'a> PartialEq<&'a str> for MessageName {
fn eq(&self, s: &&'a str) -> bool {
if let Some(variant) = self.1 {
s.split_once("::")
.is_some_and(|(n, v)| n == self.0 && v == variant)
} else {
self.0 == *s
}
}
}
impl PartialEq<MessageName> for &'_ str {
fn eq(&self, s: &MessageName) -> bool {
s == self
}
}
impl fmt::Display for MessageName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.0)?;
if let Some(variant) = self.1 {
f.write_str("::")?;
f.write_str(variant)?;
}
Ok(())
}
}
impl From<&'static str> for MessageName {
#[inline]
fn from(struct_name: &'static str) -> Self {
Self(struct_name, None)
}
}
impl From<(&'static str, &'static str)> for MessageName {
#[inline]
fn from((enum_name, variant): (&'static str, &'static str)) -> Self {
Self(enum_name, Some(variant))
}
}
impl From<MessageName> for Cow<'static, str> {
#[inline]
fn from(name: MessageName) -> Self {
Self::from(&name)
}
}
impl From<&MessageName> for Cow<'static, str> {
#[inline]
fn from(name: &MessageName) -> Self {
match name.1 {
Some(variant) => {
let mut string = String::with_capacity(name.0.len() + 2 + variant.len());
string.push_str(name.0);
string.push_str("::");
string.push_str(variant);
Self::Owned(string)
}
None => Self::Borrowed(name.0),
}
}
}
impl MessageName {
#[doc(hidden)]
#[instability::unstable]
pub fn to_str<'a>(&self, buffer: &'a mut String) -> &'a str {
if let Some(variant) = self.1 {
buffer.clear();
buffer.push_str(self.0);
buffer.push_str("::");
buffer.push_str(variant);
&buffer[..]
} else {
self.0
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[instability::unstable]
pub enum MessageKind {
Regular,
Request(u64),
Response(u64),
}
impl MessageKind {
pub(crate) fn from_message_kind(kind: &crate::envelope::MessageKind) -> Self {
use slotmap::Key;
use crate::envelope::MessageKind as MK;
match kind {
MK::Regular { .. } => Self::Regular,
MK::RequestAny(token) | MK::RequestAll(token) => {
Self::Request(token.request_id().to_ffi())
}
MK::Response { request_id, .. } => Self::Response(request_id.data().as_ffi()),
}
}
}
#[test]
fn message_name() {
assert_eq!(MessageName::from("A"), "A");
assert_ne!(MessageName::from("A"), "AB");
assert_eq!(MessageName::from(("A", "B")), "A::B");
assert_ne!(MessageName::from(("A", "B")), "AB::B");
assert_eq!("A", MessageName::from("A"), "A");
assert_eq!(Cow::from(MessageName::from("A")), "A");
assert_eq!(Cow::from(MessageName::from(("A", "B"))), "A::B");
let mut buf = String::new();
assert_eq!(MessageName::from("A").to_str(&mut buf), "A");
assert_eq!(MessageName::from(("A", "B")).to_str(&mut buf), "A::B");
assert_eq!(MessageName::from("A").to_string(), "A");
assert_eq!(MessageName::from(("A", "B")).to_string(), "A::B");
}