#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
#[cfg(feature = "reflect")]
pub use bevy_reflect::Reflect;
use bincode::de::{BorrowDecoder, Decoder};
use bincode::enc::Encoder;
use bincode::enc::write::Writer;
use bincode::error::{DecodeError, EncodeError};
use bincode::{BorrowDecode, Decode as dDecode, Decode, Encode, Encode as dEncode};
use compact_str::CompactString;
use cu29_clock::{PartialCuTimeRange, Tov};
use serde::{Deserialize, Serialize};
use alloc::boxed::Box;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
#[cfg(feature = "std")]
use core::cell::Cell;
#[cfg(not(feature = "std"))]
use core::error::Error as CoreError;
use core::fmt::{Debug, Display, Formatter};
#[cfg(feature = "std")]
use std::error::Error;
#[cfg(not(feature = "std"))]
use spin::Mutex as SpinMutex;
#[cfg(feature = "std")]
type DynError = dyn std::error::Error + Send + Sync + 'static;
#[cfg(not(feature = "std"))]
type DynError = dyn core::error::Error + Send + Sync + 'static;
#[derive(Debug)]
struct StringError(String);
impl Display for StringError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.0)
}
}
#[cfg(feature = "std")]
impl std::error::Error for StringError {}
#[cfg(not(feature = "std"))]
impl core::error::Error for StringError {}
pub struct CuError {
message: String,
cause: Option<Box<DynError>>,
}
impl Debug for CuError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.debug_struct("CuError")
.field("message", &self.message)
.field("cause", &self.cause.as_ref().map(|e| e.to_string()))
.finish()
}
}
impl Clone for CuError {
fn clone(&self) -> Self {
CuError {
message: self.message.clone(),
cause: self
.cause
.as_ref()
.map(|e| Box::new(StringError(e.to_string())) as Box<DynError>),
}
}
}
impl Serialize for CuError {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeStruct;
let mut state = serializer.serialize_struct("CuError", 2)?;
state.serialize_field("message", &self.message)?;
state.serialize_field("cause", &self.cause.as_ref().map(|e| e.to_string()))?;
state.end()
}
}
impl<'de> Deserialize<'de> for CuError {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct CuErrorHelper {
message: String,
cause: Option<String>,
}
let helper = CuErrorHelper::deserialize(deserializer)?;
Ok(CuError {
message: helper.message,
cause: helper
.cause
.map(|s| Box::new(StringError(s)) as Box<DynError>),
})
}
}
impl Display for CuError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
let context_str = match &self.cause {
Some(c) => c.to_string(),
None => "None".to_string(),
};
write!(f, "{}\n context:{}", self.message, context_str)?;
Ok(())
}
}
#[cfg(not(feature = "std"))]
impl CoreError for CuError {
fn source(&self) -> Option<&(dyn CoreError + 'static)> {
self.cause
.as_deref()
.map(|e| e as &(dyn CoreError + 'static))
}
}
#[cfg(feature = "std")]
impl Error for CuError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.cause.as_deref().map(|e| e as &(dyn Error + 'static))
}
}
impl From<&str> for CuError {
fn from(s: &str) -> CuError {
CuError {
message: s.to_string(),
cause: None,
}
}
}
impl From<String> for CuError {
fn from(s: String) -> CuError {
CuError {
message: s,
cause: None,
}
}
}
impl CuError {
pub fn new(message_index: usize) -> CuError {
CuError {
message: format!("[interned:{}]", message_index),
cause: None,
}
}
#[cfg(feature = "std")]
pub fn new_with_cause<E>(message: &str, cause: E) -> CuError
where
E: std::error::Error + Send + Sync + 'static,
{
CuError {
message: message.to_string(),
cause: Some(Box::new(cause)),
}
}
#[cfg(not(feature = "std"))]
pub fn new_with_cause<E>(message: &str, cause: E) -> CuError
where
E: core::error::Error + Send + Sync + 'static,
{
CuError {
message: message.to_string(),
cause: Some(Box::new(cause)),
}
}
pub fn add_cause(mut self, context: &str) -> CuError {
self.cause = Some(Box::new(StringError(context.to_string())));
self
}
#[cfg(feature = "std")]
pub fn with_cause<E>(mut self, cause: E) -> CuError
where
E: std::error::Error + Send + Sync + 'static,
{
self.cause = Some(Box::new(cause));
self
}
#[cfg(not(feature = "std"))]
pub fn with_cause<E>(mut self, cause: E) -> CuError
where
E: core::error::Error + Send + Sync + 'static,
{
self.cause = Some(Box::new(cause));
self
}
pub fn cause(&self) -> Option<&(dyn core::error::Error + Send + Sync + 'static)> {
self.cause.as_deref()
}
pub fn message(&self) -> &str {
&self.message
}
}
#[cfg(feature = "std")]
pub fn with_cause<E>(message: &str, cause: E) -> CuError
where
E: std::error::Error + Send + Sync + 'static,
{
CuError::new_with_cause(message, cause)
}
#[cfg(not(feature = "std"))]
pub fn with_cause<E>(message: &str, cause: E) -> CuError
where
E: core::error::Error + Send + Sync + 'static,
{
CuError::new_with_cause(message, cause)
}
pub type CuResult<T> = Result<T, CuError>;
#[cfg(feature = "std")]
thread_local! {
static OBSERVED_ENCODE_BYTES: Cell<Option<usize>> = const { Cell::new(None) };
}
#[cfg(not(feature = "std"))]
static OBSERVED_ENCODE_BYTES: SpinMutex<Option<usize>> = SpinMutex::new(None);
pub fn begin_observed_encode() {
#[cfg(feature = "std")]
OBSERVED_ENCODE_BYTES.with(|bytes| {
debug_assert!(
bytes.get().is_none(),
"observed encode measurement must not be nested"
);
bytes.set(Some(0));
});
#[cfg(not(feature = "std"))]
{
let mut bytes = OBSERVED_ENCODE_BYTES.lock();
debug_assert!(
bytes.is_none(),
"observed encode measurement must not be nested"
);
*bytes = Some(0);
}
}
pub fn finish_observed_encode() -> usize {
#[cfg(feature = "std")]
{
OBSERVED_ENCODE_BYTES.with(|bytes| bytes.replace(None).unwrap_or(0))
}
#[cfg(not(feature = "std"))]
{
OBSERVED_ENCODE_BYTES.lock().take().unwrap_or(0)
}
}
pub fn abort_observed_encode() {
#[cfg(feature = "std")]
OBSERVED_ENCODE_BYTES.with(|bytes| bytes.set(None));
#[cfg(not(feature = "std"))]
{
*OBSERVED_ENCODE_BYTES.lock() = None;
}
}
pub fn observed_encode_bytes() -> usize {
#[cfg(feature = "std")]
{
OBSERVED_ENCODE_BYTES.with(|bytes| bytes.get().unwrap_or(0))
}
#[cfg(not(feature = "std"))]
{
OBSERVED_ENCODE_BYTES.lock().as_ref().copied().unwrap_or(0)
}
}
pub fn record_observed_encode_bytes(bytes: usize) {
#[cfg(feature = "std")]
OBSERVED_ENCODE_BYTES.with(|total| {
if let Some(current) = total.get() {
total.set(Some(current.saturating_add(bytes)));
}
});
#[cfg(not(feature = "std"))]
{
let mut total = OBSERVED_ENCODE_BYTES.lock();
if let Some(current) = *total {
*total = Some(current.saturating_add(bytes));
}
}
}
pub struct ObservedWriter<W> {
inner: W,
}
impl<W> ObservedWriter<W> {
pub const fn new(inner: W) -> Self {
Self { inner }
}
pub fn into_inner(self) -> W {
self.inner
}
pub fn inner(&self) -> &W {
&self.inner
}
pub fn inner_mut(&mut self) -> &mut W {
&mut self.inner
}
}
impl<W: Writer> Writer for ObservedWriter<W> {
#[inline(always)]
fn write(&mut self, bytes: &[u8]) -> Result<(), EncodeError> {
self.inner.write(bytes)?;
record_observed_encode_bytes(bytes.len());
Ok(())
}
}
pub trait WriteStream<E: Encode>: Debug + Send + Sync {
fn log(&mut self, obj: &E) -> CuResult<()>;
fn flush(&mut self) -> CuResult<()> {
Ok(())
}
fn last_log_bytes(&self) -> Option<usize> {
None
}
}
#[derive(dEncode, dDecode, Copy, Clone, Debug, PartialEq)]
pub enum UnifiedLogType {
Empty, StructuredLogLine, CopperList, FrozenTasks, LastEntry, RuntimeLifecycle, }
pub trait Metadata: Default + Debug + Clone + Encode + Decode<()> + Serialize {}
impl Metadata for () {}
#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, Serialize, Deserialize)]
#[cfg_attr(feature = "reflect", derive(Reflect))]
pub struct CuMsgOrigin {
pub subsystem_code: u16,
pub instance_id: u32,
pub cl_id: u64,
}
pub trait CuMsgMetadataTrait {
fn process_time(&self) -> PartialCuTimeRange;
fn status_txt(&self) -> &CuCompactString;
fn origin(&self) -> Option<&CuMsgOrigin> {
None
}
}
pub trait ErasedCuStampedData {
fn payload(&self) -> Option<&dyn erased_serde::Serialize>;
#[cfg(feature = "reflect")]
fn payload_reflect(&self) -> Option<&dyn Reflect>;
fn tov(&self) -> Tov;
fn metadata(&self) -> &dyn CuMsgMetadataTrait;
}
pub trait ErasedCuStampedDataSet {
fn cumsgs(&self) -> Vec<&dyn ErasedCuStampedData>;
}
pub trait CuPayloadRawBytes {
fn payload_raw_bytes(&self) -> Vec<Option<u64>>;
}
pub trait MatchingTasks {
fn get_all_task_ids() -> &'static [&'static str];
}
pub trait PayloadSchemas {
fn get_payload_schemas() -> Vec<(&'static str, String)> {
Vec::new()
}
}
pub trait CopperListTuple:
bincode::Encode
+ bincode::Decode<()>
+ Debug
+ Serialize
+ ErasedCuStampedDataSet
+ MatchingTasks
+ Default
{
}
impl<T> CopperListTuple for T where
T: bincode::Encode
+ bincode::Decode<()>
+ Debug
+ Serialize
+ ErasedCuStampedDataSet
+ MatchingTasks
+ Default
{
}
pub const COMPACT_STRING_CAPACITY: usize = size_of::<String>();
#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct CuCompactString(pub CompactString);
impl Encode for CuCompactString {
fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
let CuCompactString(compact_string) = self;
let bytes = &compact_string.as_bytes();
bytes.encode(encoder)
}
}
impl Debug for CuCompactString {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
if self.0.is_empty() {
return write!(f, "CuCompactString(Empty)");
}
write!(f, "CuCompactString({})", self.0)
}
}
impl<Context> Decode<Context> for CuCompactString {
fn decode<D: Decoder>(decoder: &mut D) -> Result<Self, DecodeError> {
let bytes = <Vec<u8> as Decode<D::Context>>::decode(decoder)?; let compact_string =
CompactString::from_utf8(bytes).map_err(|e| DecodeError::Utf8 { inner: e })?;
Ok(CuCompactString(compact_string))
}
}
impl<'de, Context> BorrowDecode<'de, Context> for CuCompactString {
fn borrow_decode<D: BorrowDecoder<'de>>(decoder: &mut D) -> Result<Self, DecodeError> {
CuCompactString::decode(decoder)
}
}
#[cfg(feature = "defmt")]
impl defmt::Format for CuError {
fn format(&self, f: defmt::Formatter) {
match &self.cause {
Some(c) => {
let cause_str = c.to_string();
defmt::write!(
f,
"CuError {{ message: {}, cause: {} }}",
defmt::Display2Format(&self.message),
defmt::Display2Format(&cause_str),
)
}
None => defmt::write!(
f,
"CuError {{ message: {}, cause: None }}",
defmt::Display2Format(&self.message),
),
}
}
}
#[cfg(feature = "defmt")]
impl defmt::Format for CuCompactString {
fn format(&self, f: defmt::Formatter) {
if self.0.is_empty() {
defmt::write!(f, "CuCompactString(Empty)");
} else {
defmt::write!(f, "CuCompactString({})", defmt::Display2Format(&self.0));
}
}
}
#[cfg(test)]
mod tests {
use crate::CuCompactString;
use bincode::{config, decode_from_slice, encode_to_vec};
use compact_str::CompactString;
#[test]
fn test_cucompactstr_encode_decode_empty() {
let cstr = CuCompactString(CompactString::from(""));
let config = config::standard();
let encoded = encode_to_vec(&cstr, config).expect("Encoding failed");
assert_eq!(encoded.len(), 1); let (decoded, _): (CuCompactString, usize) =
decode_from_slice(&encoded, config).expect("Decoding failed");
assert_eq!(cstr.0, decoded.0);
}
#[test]
fn test_cucompactstr_encode_decode_small() {
let cstr = CuCompactString(CompactString::from("test"));
let config = config::standard();
let encoded = encode_to_vec(&cstr, config).expect("Encoding failed");
assert_eq!(encoded.len(), 5); let (decoded, _): (CuCompactString, usize) =
decode_from_slice(&encoded, config).expect("Decoding failed");
assert_eq!(cstr.0, decoded.0);
}
}
#[cfg(all(test, feature = "std"))]
mod std_tests {
use crate::{CuError, with_cause};
#[test]
fn test_cuerror_from_str() {
let err = CuError::from("test error");
assert_eq!(err.message(), "test error");
assert!(err.cause().is_none());
}
#[test]
fn test_cuerror_from_string() {
let err = CuError::from(String::from("test error"));
assert_eq!(err.message(), "test error");
assert!(err.cause().is_none());
}
#[test]
fn test_cuerror_new_index() {
let err = CuError::new(42);
assert_eq!(err.message(), "[interned:42]");
assert!(err.cause().is_none());
}
#[test]
fn test_cuerror_new_with_cause() {
let io_err = std::io::Error::other("io error");
let err = CuError::new_with_cause("wrapped error", io_err);
assert_eq!(err.message(), "wrapped error");
assert!(err.cause().is_some());
assert!(err.cause().unwrap().to_string().contains("io error"));
}
#[test]
fn test_cuerror_add_cause() {
let err = CuError::from("base error").add_cause("additional context");
assert_eq!(err.message(), "base error");
assert!(err.cause().is_some());
assert_eq!(err.cause().unwrap().to_string(), "additional context");
}
#[test]
fn test_cuerror_with_cause_method() {
let io_err = std::io::Error::other("io error");
let err = CuError::from("base error").with_cause(io_err);
assert_eq!(err.message(), "base error");
assert!(err.cause().is_some());
}
#[test]
fn test_cuerror_with_cause_free_function() {
let io_err = std::io::Error::other("io error");
let err = with_cause("wrapped", io_err);
assert_eq!(err.message(), "wrapped");
assert!(err.cause().is_some());
}
#[test]
fn test_cuerror_clone() {
let io_err = std::io::Error::other("io error");
let err = CuError::new_with_cause("test", io_err);
let cloned = err.clone();
assert_eq!(err.message(), cloned.message());
assert_eq!(
err.cause().map(|c| c.to_string()),
cloned.cause().map(|c| c.to_string())
);
}
#[test]
fn test_cuerror_serialize_deserialize_json() {
let io_err = std::io::Error::other("io error");
let err = CuError::new_with_cause("test", io_err);
let serialized = serde_json::to_string(&err).unwrap();
let deserialized: CuError = serde_json::from_str(&serialized).unwrap();
assert_eq!(err.message(), deserialized.message());
assert!(deserialized.cause().is_some());
}
#[test]
fn test_cuerror_serialize_deserialize_no_cause() {
let err = CuError::from("simple error");
let serialized = serde_json::to_string(&err).unwrap();
let deserialized: CuError = serde_json::from_str(&serialized).unwrap();
assert_eq!(err.message(), deserialized.message());
assert!(deserialized.cause().is_none());
}
#[test]
fn test_cuerror_display() {
let err = CuError::from("test error").add_cause("some context");
let display = format!("{}", err);
assert!(display.contains("test error"));
assert!(display.contains("some context"));
}
#[test]
fn test_cuerror_debug() {
let err = CuError::from("test error").add_cause("some context");
let debug = format!("{:?}", err);
assert!(debug.contains("test error"));
assert!(debug.contains("some context"));
}
}