use serde::Serialize;
pub type NodeId = u32;
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct DecodeWarning {
pub node: Option<NodeId>,
pub kind: &'static str,
pub value: String,
pub message: String,
}
#[cfg_attr(not(feature = "devtools"), allow(dead_code))]
#[derive(Debug, Clone, PartialEq)]
pub struct RuntimeWarning {
pub node: Option<NodeId>,
pub kind: &'static str,
pub value: String,
pub message: String,
}
#[cfg(all(feature = "devtools", debug_assertions))]
mod imp {
use super::NodeId;
use super::{DecodeWarning, RuntimeWarning};
use std::cell::{Cell, RefCell};
use std::sync::Mutex;
use std::sync::atomic::{AtomicBool, Ordering};
const DECODE_CAP: usize = 64;
const RUNTIME_CAP: usize = 256;
thread_local! {
static DECODE: RefCell<Vec<DecodeWarning>> = const { RefCell::new(Vec::new()) };
static CURRENT_NODE: Cell<Option<NodeId>> = const { Cell::new(None) };
}
static ARMED: AtomicBool = AtomicBool::new(false);
static RUNTIME: Mutex<Vec<RuntimeWarning>> = Mutex::new(Vec::new());
pub fn decode_batch_start() {
DECODE.with(|d| d.borrow_mut().clear());
}
pub fn decode_watermark() -> usize {
DECODE.with(|d| d.borrow().len())
}
pub fn decode_report(kind: &'static str, value: &str, message: &str) {
crate::console_log::push(
crate::console_log::Source::Rust,
crate::console_log::Level::Warn,
&format!("[{kind}] {message}"),
);
DECODE.with(|d| {
let mut d = d.borrow_mut();
if d.len() < DECODE_CAP {
d.push(DecodeWarning {
node: None,
kind,
value: value.to_owned(),
message: message.to_owned(),
});
}
});
}
pub fn decode_attribute_since(mark: usize, node: Option<NodeId>) {
DECODE.with(|d| {
for w in d.borrow_mut().iter_mut().skip(mark) {
w.node = node;
}
});
}
pub fn take_decode_warnings() -> Vec<DecodeWarning> {
DECODE.with(|d| std::mem::take(&mut *d.borrow_mut()))
}
pub fn arm_runtime() {
ARMED.store(true, Ordering::Relaxed);
}
pub struct NodeScope(Option<NodeId>);
impl Drop for NodeScope {
fn drop(&mut self) {
CURRENT_NODE.with(|c| c.set(self.0));
}
}
pub fn node_scope(id: NodeId) -> NodeScope {
CURRENT_NODE.with(|c| NodeScope(c.replace(Some(id))))
}
pub fn report(kind: &'static str, value: &str, message: &str) {
if !ARMED.load(Ordering::Relaxed) {
return;
}
let node = CURRENT_NODE.with(|c| c.get());
crate::console_log::push(
crate::console_log::Source::Rust,
crate::console_log::Level::Warn,
&match node {
Some(n) => format!("[{kind}] {message} (node {n})"),
None => format!("[{kind}] {message}"),
},
);
let mut sink = RUNTIME.lock().unwrap_or_else(|e| e.into_inner());
if sink.len() < RUNTIME_CAP {
sink.push(RuntimeWarning {
node,
kind,
value: value.to_owned(),
message: message.to_owned(),
});
}
}
pub fn take_runtime_warnings() -> Vec<RuntimeWarning> {
std::mem::take(&mut *RUNTIME.lock().unwrap_or_else(|e| e.into_inner()))
}
}
#[cfg_attr(not(feature = "devtools"), allow(dead_code))]
#[cfg(not(all(feature = "devtools", debug_assertions)))]
mod imp {
use super::NodeId;
use super::{DecodeWarning, RuntimeWarning};
#[inline(always)]
pub fn decode_batch_start() {}
#[inline(always)]
pub fn decode_watermark() -> usize {
0
}
#[inline(always)]
pub fn decode_report(_kind: &'static str, _value: &str, _message: &str) {}
#[inline(always)]
pub fn decode_attribute_since(_mark: usize, _node: Option<NodeId>) {}
#[inline(always)]
pub fn take_decode_warnings() -> Vec<DecodeWarning> {
Vec::new()
}
#[inline(always)]
pub fn arm_runtime() {}
pub struct NodeScope;
#[inline(always)]
pub fn node_scope(_id: NodeId) -> NodeScope {
NodeScope
}
#[inline(always)]
pub fn report(_kind: &'static str, _value: &str, _message: &str) {}
#[inline(always)]
pub fn take_runtime_warnings() -> Vec<RuntimeWarning> {
Vec::new()
}
}
pub use imp::*;
#[cfg(test)]
pub fn test_lock() -> std::sync::MutexGuard<'static, ()> {
static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
LOCK.lock().unwrap_or_else(|e| e.into_inner())
}
#[cfg(all(test, feature = "devtools", debug_assertions))]
mod tests {
use super::*;
#[test]
fn decode_sink_stamps_and_drains() {
decode_batch_start();
decode_report("length", "aa16", "invalid length \"aa16\"");
let mark = decode_watermark();
decode_report("display", "flexx", "unrecognized display \"flexx\"");
decode_attribute_since(mark, Some(9));
let warns = take_decode_warnings();
assert_eq!(warns.len(), 2);
assert_eq!(warns[0].node, None);
assert_eq!(warns[1].node, Some(9));
assert!(take_decode_warnings().is_empty(), "drain empties the sink");
}
#[test]
fn node_scope_nests_and_restores() {
let _lock = test_lock();
arm_runtime();
let _ = take_runtime_warnings();
{
let _outer = node_scope(1);
report("color", "redd", "unrecognized color");
{
let _inner = node_scope(2);
report("color", "bluu", "unrecognized color");
}
report("color", "grean", "unrecognized color");
}
report("color", "unscoped", "unrecognized color");
let nodes: Vec<_> = take_runtime_warnings()
.into_iter()
.map(|w| w.node)
.collect();
assert_eq!(nodes, vec![Some(1), Some(2), Some(1), None]);
}
}