use alloc::{boxed::Box, rc::Rc};
use core::any::type_name;
use crate::{
Signal,
watcher::{BoxWatcherGuard, Context, OnDrop},
};
#[derive(Debug, Clone)]
pub struct Debug<C> {
source: C,
inner: Rc<DebugInner>,
}
struct DebugInner {
_guard: BoxWatcherGuard,
config: Config,
}
impl core::fmt::Debug for DebugInner {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct(type_name::<Self>())
.field("_guard", &"<opaque guard>")
.field("config", &self.config)
.finish()
}
}
impl<C> Debug<C>
where
C: Signal,
C::Output: core::fmt::Debug,
{
pub fn with_config(source: C, config: Config) -> Self {
let name = type_name::<C>();
let guard: BoxWatcherGuard = if config.should_log_changes() {
Box::new(source.watch(move |context: Context<_>| {
let value = context.value();
let metadata = context.metadata();
if metadata.is_empty() {
log::info!("`{name}` changed to {value:?}");
} else {
log::info!("`{name}` changed to {value:?} with metadata {metadata:?}");
}
}))
} else {
Box::new(())
};
Self {
source,
inner: Rc::new(DebugInner {
_guard: guard,
config,
}),
}
}
pub fn changes(source: C) -> Self {
Self::with_config(source, Config::changes())
}
pub fn verbose(source: C) -> Self {
Self::with_config(source, Config::verbose())
}
pub fn compute_only(source: C) -> Self {
Self::with_config(source, Config::compute_only())
}
pub fn watchers(source: C) -> Self {
Self::with_config(source, Config::watchers())
}
pub fn compute_and_changes(source: C) -> Self {
Self::with_config(source, Config::compute_and_changes())
}
}
#[derive(Debug, Clone, Copy)]
pub struct Config(u32);
impl Config {
const COMPUTE: u32 = 1 << 0;
const WATCH: u32 = 1 << 1;
const REMOVE_WATCHER: u32 = 1 << 2;
const CHANGE: u32 = 1 << 3;
#[must_use]
pub const fn changes() -> Self {
Self(Self::CHANGE)
}
#[must_use]
pub const fn verbose() -> Self {
Self(Self::COMPUTE | Self::WATCH | Self::REMOVE_WATCHER | Self::CHANGE)
}
#[must_use]
pub const fn compute_only() -> Self {
Self(Self::COMPUTE)
}
#[must_use]
pub const fn watchers() -> Self {
Self(Self::WATCH | Self::REMOVE_WATCHER)
}
#[must_use]
pub const fn compute_and_changes() -> Self {
Self(Self::COMPUTE | Self::CHANGE)
}
const fn contains_flag(self, flag: u32) -> bool {
(self.0 & flag) == flag
}
const fn should_log_changes(self) -> bool {
self.contains_flag(Self::CHANGE)
}
const fn should_log_compute(self) -> bool {
self.contains_flag(Self::COMPUTE)
}
const fn should_log_watch(self) -> bool {
self.contains_flag(Self::WATCH)
}
const fn should_log_remove_watcher(self) -> bool {
self.contains_flag(Self::REMOVE_WATCHER)
}
}
impl Default for Config {
fn default() -> Self {
Self::changes()
}
}
impl<C> Signal for Debug<C>
where
C: Signal,
C::Output: core::fmt::Debug,
{
type Output = C::Output;
type Guard = BoxWatcherGuard;
fn get(&self) -> Self::Output {
let name = type_name::<C>();
let value = self.source.get();
if self.inner.config.should_log_compute() {
log::debug!("`{name}` computed value {value:?}");
}
value
}
fn watch(&self, watcher: impl Fn(Context<C::Output>) + 'static) -> Self::Guard {
let guard = self.source.watch(watcher);
if self.inner.config.should_log_watch() {
log::debug!("Added watcher");
}
let guard: BoxWatcherGuard = if self.inner.config.should_log_remove_watcher() {
Box::new(OnDrop::attach(guard, || {
log::debug!("Removed watcher");
}))
} else {
Box::new(guard)
};
guard
}
}