use std::mem::ManuallyDrop;
use std::sync::Arc;
use crate::{Channel, ChannelBuilder, Context, Encode, PartialMetadata, RawChannel};
#[doc(hidden)]
pub struct ChannelPlaceholder {}
impl ChannelPlaceholder {
pub fn new(channel: Arc<RawChannel>) -> *mut Self {
Arc::into_raw(channel) as *mut Self
}
pub unsafe fn log<T: Encode>(channel_ptr: *mut Self, msg: &T, metadata: PartialMetadata) {
let channel_arc = unsafe { Arc::from_raw(channel_ptr as *mut RawChannel) };
let channel = ManuallyDrop::new(Channel::<T>::from_raw_channel(channel_arc));
channel.log_with_meta(msg, metadata);
}
}
#[doc(hidden)]
#[cold]
pub fn create_channel<T: Encode>(
topic: &str,
_: &T,
context: &Arc<Context>,
) -> *mut ChannelPlaceholder {
let channel = ChannelBuilder::new(topic)
.schema(T::get_schema())
.message_encoding(T::get_message_encoding())
.context(context)
.build_raw()
.unwrap_or_else(|e| {
unreachable!("Failed to create channel {e}")
});
ChannelPlaceholder::new(channel)
}
#[macro_export]
macro_rules! log {
($topic:literal, $msg:expr $(,)? ) => {{ $crate::log_with_meta!($topic, $msg, $crate::PartialMetadata::default()) }};
($topic:literal, $msg:expr, log_time = $log_time:expr $(,)? ) => {{
$crate::log_with_meta!(
$topic,
$msg,
$crate::PartialMetadata::with_log_time($log_time)
)
}};
}
#[doc(hidden)]
#[macro_export]
macro_rules! log_with_meta {
($topic:literal, $msg:expr, $metadata:expr) => {{
static CHANNEL: std::sync::atomic::AtomicPtr<$crate::log_macro::ChannelPlaceholder> =
std::sync::atomic::AtomicPtr::new(std::ptr::null_mut());
let mut channel_ptr = CHANNEL.load(std::sync::atomic::Ordering::Acquire);
if channel_ptr.is_null() {
channel_ptr =
$crate::log_macro::create_channel($topic, &$msg, &$crate::Context::get_default());
CHANNEL.store(channel_ptr, std::sync::atomic::Ordering::Release);
}
unsafe { $crate::log_macro::ChannelPlaceholder::log(channel_ptr, &$msg, $metadata) };
}};
}
#[cfg(test)]
mod tests {
use bytes::BufMut;
use tracing_test::traced_test;
use crate::messages::{Color, LaserScan, Log, Timestamp};
use crate::nanoseconds_since_epoch;
use crate::{Context, testutil::RecordingSink};
use crate::{FoxgloveError, Schema};
use super::*;
use crate::testutil::GlobalContextTest;
fn serialize<T: Encode>(msg: &T) -> Vec<u8> {
let mut buf = Vec::new();
msg.encode(&mut buf).unwrap();
buf
}
#[test]
fn test_log_macro() {
let _cleanup = GlobalContextTest::new();
let now = nanoseconds_since_epoch();
let sink = Arc::new(RecordingSink::new());
Context::get_default().add_sink(sink.clone());
let mut log_messages = Vec::new();
for line in 1..=3 {
let msg = Log {
timestamp: None,
level: 1,
message: "Hello, world!".to_string(),
name: "".to_string(),
file: "".to_string(),
line,
};
log_messages.push(msg);
}
let timestamp = Timestamp::now();
log!("foo", log_messages[0], log_time = 123);
log!("foo", log_messages[1]);
log!("foo", log_messages[2], log_time = timestamp);
log!("foo", Color::default());
let messages = sink.take_messages();
assert_eq!(messages.len(), 4);
assert_eq!(messages[0].msg, serialize(&log_messages[0]));
assert_eq!(messages[0].metadata.log_time, 123);
assert_eq!(messages[1].msg, serialize(&log_messages[1]));
assert!(messages[1].metadata.log_time >= now);
assert_eq!(messages[2].msg, serialize(&log_messages[2]));
assert_eq!(messages[2].metadata.log_time, timestamp.total_nanos());
assert_eq!(messages[3].msg, serialize(&Color::default()));
assert!(messages[3].metadata.log_time >= now);
assert_eq!(messages[0].channel_id, messages[1].channel_id);
assert_eq!(messages[0].channel_id, messages[2].channel_id);
assert_ne!(messages[0].channel_id, messages[3].channel_id);
}
#[test]
fn test_log_in_loop() {
let _cleanup = GlobalContextTest::new();
let sink = Arc::new(RecordingSink::new());
Context::get_default().add_sink(sink.clone());
let mut log_messages = Vec::new();
for line in 1..=2 {
let msg = Log {
timestamp: None,
level: 1,
message: "Hello, world!".to_string(),
name: "".to_string(),
file: "".to_string(),
line,
};
log!("bar", msg, log_time = 123);
log_messages.push(msg);
}
let messages = sink.take_messages();
assert_eq!(messages.len(), 2);
assert_eq!(messages[0].msg, serialize(&log_messages[0]));
assert_eq!(messages[0].metadata.log_time, 123);
assert_eq!(messages[1].msg, serialize(&log_messages[1]));
assert_eq!(messages[1].metadata.log_time, 123);
}
#[test]
#[traced_test]
fn test_log_existing_channel_different_schema_warns() {
let _cleanup = GlobalContextTest::new();
let sink = Arc::new(RecordingSink::new());
Context::get_default().add_sink(sink.clone());
let _channel = ChannelBuilder::new("foo").build::<LaserScan>();
log!(
"foo",
Log {
timestamp: None,
level: 1,
message: "Hello, world!".to_string(),
name: "".to_string(),
file: "".to_string(),
line: 1,
}
);
assert!(logs_contain(
"Channel with topic foo already exists in this context"
));
}
#[test]
#[traced_test]
fn test_log_existing_channel_different_encoding_warns() {
let _cleanup = GlobalContextTest::new();
let sink = Arc::new(RecordingSink::new());
Context::get_default().add_sink(sink.clone());
struct Foo {
x: u32,
}
impl Encode for Foo {
type Error = FoxgloveError;
fn encode(&self, buf: &mut impl BufMut) -> Result<(), Self::Error> {
buf.put_u32(self.x);
Ok(())
}
fn get_schema() -> Option<Schema> {
None
}
fn get_message_encoding() -> String {
"foo".to_string()
}
}
struct Bar {
x: u32,
}
impl Encode for Bar {
type Error = FoxgloveError;
fn encode(&self, buf: &mut impl BufMut) -> Result<(), Self::Error> {
buf.put_u32(self.x);
Ok(())
}
fn get_schema() -> Option<Schema> {
None
}
fn get_message_encoding() -> String {
"bar".to_string()
}
}
let _channel = ChannelBuilder::new("foo").build::<Foo>();
log!("foo", Bar { x: 1 });
assert!(logs_contain(
"Channel with topic foo already exists in this context"
));
}
#[test]
#[traced_test]
fn test_log_macro_inside_generic_function() {
let _cleanup = GlobalContextTest::new();
let sink = Arc::new(RecordingSink::new());
Context::get_default().add_sink(sink.clone());
struct Foo {
x: u32,
}
impl Encode for Foo {
type Error = FoxgloveError;
fn encode(&self, buf: &mut impl BufMut) -> Result<(), Self::Error> {
buf.put_u32(self.x);
Ok(())
}
fn get_schema() -> Option<Schema> {
None
}
fn get_message_encoding() -> String {
"foo".to_string()
}
}
struct Bar {
x: u32,
}
impl Encode for Bar {
type Error = FoxgloveError;
fn encode(&self, buf: &mut impl BufMut) -> Result<(), Self::Error> {
buf.put_u32(self.x);
Ok(())
}
fn get_schema() -> Option<Schema> {
None
}
fn get_message_encoding() -> String {
"bar".to_string()
}
}
fn generic_func<T: Encode>(x: T) {
log!("foo", x);
}
generic_func(Foo { x: 1 });
generic_func(Bar { x: 1 });
let messages = sink.take_messages();
assert_eq!(messages.len(), 2);
assert_eq!(messages[0].msg, serialize(&Foo { x: 1 }));
assert_eq!(messages[1].msg, serialize(&Bar { x: 1 }));
}
}