use facet::Facet;
#[cfg(feature = "rusqlite")]
use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use std::fmt;
use std::sync::OnceLock;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Instant, SystemTime, UNIX_EPOCH};
#[derive(Facet, Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
#[facet(rename_all = "snake_case")]
pub enum Direction {
Incoming,
Outgoing,
}
#[derive(Facet, Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
#[facet(rename_all = "snake_case")]
pub enum ChannelDir {
Tx,
Rx,
}
#[derive(Facet, Clone, Debug, PartialEq, Eq)]
#[facet(transparent)]
pub struct Json(pub(crate) String);
impl Json {
pub fn new(text: impl Into<String>) -> Self {
Self(text.into())
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
pub fn into_string(self) -> String {
self.0
}
}
#[cfg(feature = "rusqlite")]
impl ToSql for Json {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
Ok(self.as_str().into())
}
}
#[cfg(feature = "rusqlite")]
impl FromSql for Json {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
Ok(Json::new(String::column_result(value)?))
}
}
fn ptime_anchor() -> &'static Instant {
static PTIME_ANCHOR: OnceLock<Instant> = OnceLock::new();
PTIME_ANCHOR.get_or_init(Instant::now)
}
#[derive(Facet, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[facet(transparent)]
pub struct PTime(u64);
impl PTime {
pub fn now() -> Self {
let elapsed_ms = ptime_anchor().elapsed().as_millis().min(u64::MAX as u128) as u64;
Self(elapsed_ms)
}
pub fn as_millis(&self) -> u64 {
self.0
}
}
#[cfg(feature = "rusqlite")]
impl ToSql for PTime {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
let as_i64 = i64::try_from(self.0).map_err(|_| {
rusqlite::Error::ToSqlConversionFailure(Box::new(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("PTime out of SQLite i64 range: {}", self.0),
)))
})?;
Ok(as_i64.into())
}
}
#[cfg(feature = "rusqlite")]
impl FromSql for PTime {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
let raw_i64 = i64::column_result(value)?;
let raw_u64 = u64::try_from(raw_i64).map_err(|_| FromSqlError::OutOfRange(raw_i64))?;
Ok(PTime(raw_u64))
}
}
#[derive(Facet, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[facet(transparent)]
pub struct EntityId(pub(crate) String);
#[derive(Facet, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[facet(transparent)]
pub struct ScopeId(pub(crate) String);
#[derive(Facet, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[facet(transparent)]
pub struct EventId(pub(crate) String);
#[derive(Facet, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[facet(transparent)]
pub struct ConnectionId(pub(crate) u64);
#[derive(Facet, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[facet(transparent)]
pub struct ProcessId(pub(crate) String);
#[derive(Facet, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[facet(transparent)]
pub struct SessionId(pub(crate) String);
impl EntityId {
pub fn new(id: impl Into<String>) -> Self {
Self(id.into())
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
impl ScopeId {
pub fn new(id: impl Into<String>) -> Self {
Self(id.into())
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
impl EventId {
pub fn new(id: impl Into<String>) -> Self {
Self(id.into())
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
impl ConnectionId {
pub fn new(id: u64) -> Self {
assert!(
id > 0 && id <= moire_trace_types::JS_SAFE_INT_MAX_U64,
"invariant violated: connection_id must be in 1..={}, got {id}",
moire_trace_types::JS_SAFE_INT_MAX_U64
);
Self(id)
}
pub fn next(self) -> Self {
Self::new(self.0.saturating_add(1))
}
}
impl fmt::Display for ConnectionId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "CONNECTION#{:x}", self.0)
}
}
impl ProcessId {
pub fn new(id: impl Into<String>) -> Self {
Self(id.into())
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
impl SessionId {
pub fn new(id: impl Into<String>) -> Self {
Self(id.into())
}
pub fn from_ordinal(value: u64) -> Self {
Self(format!("session:{value}"))
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
#[cfg(feature = "rusqlite")]
impl ToSql for ProcessId {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
Ok(self.as_str().into())
}
}
#[cfg(feature = "rusqlite")]
impl FromSql for ProcessId {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
Ok(ProcessId::new(String::column_result(value)?))
}
}
#[cfg(feature = "rusqlite")]
impl ToSql for ConnectionId {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
let value = i64::try_from(self.0).map_err(|_| {
rusqlite::Error::ToSqlConversionFailure(Box::new(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("ConnectionId out of SQLite i64 range: {}", self.0),
)))
})?;
Ok(value.into())
}
}
#[cfg(feature = "rusqlite")]
impl FromSql for ConnectionId {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
let raw_i64 = i64::column_result(value)?;
let raw_u64 = u64::try_from(raw_i64).map_err(|_| FromSqlError::OutOfRange(raw_i64))?;
Ok(ConnectionId::new(raw_u64))
}
}
#[cfg(feature = "rusqlite")]
impl ToSql for EntityId {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
Ok(self.as_str().into())
}
}
#[cfg(feature = "rusqlite")]
impl FromSql for EntityId {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
Ok(EntityId::new(String::column_result(value)?))
}
}
#[cfg(feature = "rusqlite")]
impl ToSql for ScopeId {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
Ok(self.as_str().into())
}
}
#[cfg(feature = "rusqlite")]
impl FromSql for ScopeId {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
Ok(ScopeId::new(String::column_result(value)?))
}
}
#[cfg(feature = "rusqlite")]
impl ToSql for EventId {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
Ok(self.as_str().into())
}
}
#[cfg(feature = "rusqlite")]
impl FromSql for EventId {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
Ok(EventId::new(String::column_result(value)?))
}
}
pub(crate) fn next_entity_id() -> EntityId {
EntityId(next_opaque_id())
}
pub(crate) fn next_scope_id() -> ScopeId {
ScopeId(next_opaque_id())
}
pub(crate) fn next_event_id() -> EventId {
EventId(next_opaque_id())
}
pub fn next_process_id() -> ProcessId {
ProcessId(next_opaque_id())
}
pub fn process_prefix_u16() -> u16 {
static PROCESS_PREFIX: OnceLock<u16> = OnceLock::new();
*PROCESS_PREFIX.get_or_init(|| {
let pid = std::process::id() as u64;
let seed = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
((seed ^ pid) & 0xFFFF) as u16
})
}
fn next_opaque_id() -> String {
static COUNTER: AtomicU64 = AtomicU64::new(1);
let prefix = process_prefix_u16();
let counter = COUNTER.fetch_add(1, Ordering::Relaxed) & 0x0000_FFFF_FFFF_FFFF;
let raw = ((prefix as u64) << 48) | counter;
MoireHex(raw).to_string()
}
struct MoireHex(u64);
impl fmt::Display for MoireHex {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
const DIGITS: &[u8; 16] = b"0123456789pesPES";
let mut out = [0u8; 16];
for (idx, shift) in (0..16).zip((0..64).step_by(4).rev()) {
let nibble = ((self.0 >> shift) & 0xF) as usize;
out[idx] = DIGITS[nibble];
}
f.write_str(unsafe { std::str::from_utf8_unchecked(&out) })
}
}