use std::cell::Cell;
use std::ops::{Deref, DerefMut};
use crate::state::{ComponentData, HookKey, KeepAlive, State};
pub(crate) struct RenderingState<'s> {
pub(crate) keep_alive: &'s mut Vec<KeepAlive>,
pub(crate) hooks: &'s mut Vec<HookKey>,
pub(crate) parent_dep: HookKey,
}
pub struct Signal<T> {
data: T,
written: bool,
read: Cell<bool>,
deps: Vec<HookKey>,
}
#[derive(Copy, Clone)]
pub struct SignalState {
written: bool,
read: bool,
}
impl<T: std::fmt::Debug> std::fmt::Debug for Signal<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
(**self).fmt(f)
}
}
impl<T> Signal<T> {
pub fn new(data: T) -> Self {
Self {
data,
written: false,
read: Cell::new(false),
deps: Vec::new(),
}
}
#[doc(hidden)]
pub fn pop_state(&mut self) -> SignalState {
let result = SignalState {
written: self.written,
read: self.read.get(),
};
self.clear();
result
}
#[doc(hidden)]
pub fn set_state(&mut self, state: SignalState) {
self.written = state.written;
self.read.set(state.read);
}
}
pub trait SignalMethods {
fn clear(&mut self);
fn register_dep(&mut self, dep: HookKey);
fn deps(&mut self) -> std::vec::Drain<'_, HookKey>;
fn changed(&self) -> bool;
}
impl<T> SignalMethods for Signal<T> {
fn clear(&mut self) {
self.written = false;
self.read.set(false);
}
fn register_dep(&mut self, dep: HookKey) {
if self.read.get() {
self.deps.push(dep);
}
}
fn changed(&self) -> bool {
self.written
}
fn deps(&mut self) -> std::vec::Drain<'_, HookKey> {
self.deps.drain(..)
}
}
impl<T> Deref for Signal<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.read.set(true);
&self.data
}
}
impl<T> DerefMut for Signal<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.written = true;
&mut self.data
}
}
pub(crate) trait ReactiveHook<C: ComponentData> {
fn update(&mut self, _ctx: &mut State<C>, _you: HookKey) -> UpdateResult;
fn drop_deps(&mut self) -> Option<std::vec::Drain<'_, HookKey>> {
None
}
}
pub(crate) enum UpdateResult {
Nothing,
RunHook(HookKey),
}
#[cfg(feature = "ergonomic_ops")]
mod ergonomin_ops {
use std::ops::{
AddAssign,
BitAndAssign,
BitOrAssign,
BitXorAssign,
DivAssign,
MulAssign,
RemAssign,
ShlAssign,
ShrAssign,
SubAssign,
};
use super::Signal;
impl<R, T: PartialEq<R>> PartialEq<R> for Signal<T> {
fn eq(&self, other: &R) -> bool {
**self == *other
}
}
impl<R, T: PartialOrd<R>> PartialOrd<R> for Signal<T> {
fn partial_cmp(&self, other: &R) -> Option<std::cmp::Ordering> {
(**self).partial_cmp(other)
}
}
macro_rules! inplace_op {
($trait:ident. $method:ident()) => {
impl<R, T: $trait<R>> $trait<R> for Signal<T> {
fn $method(&mut self, rhs: R) {
(**self).$method(rhs);
}
}
};
}
inplace_op!(AddAssign.add_assign());
inplace_op!(SubAssign.sub_assign());
inplace_op!(MulAssign.mul_assign());
inplace_op!(DivAssign.div_assign());
inplace_op!(RemAssign.rem_assign());
inplace_op!(BitAndAssign.bitand_assign());
inplace_op!(BitOrAssign.bitor_assign());
inplace_op!(BitXorAssign.bitxor_assign());
inplace_op!(ShlAssign.shl_assign());
inplace_op!(ShrAssign.shr_assign());
}
#[cfg(test)]
mod tests {
use super::{Signal, SignalMethods};
struct Holder<T>(Signal<T>);
#[test]
fn reading() {
let foo = &Holder(Signal::new(10));
assert_eq!(*foo.0, 10);
assert!(foo.0.read.get());
}
#[test]
fn modify() {
let foo = &mut Holder(Signal::new(10));
*foo.0 = 20;
assert!(foo.0.changed());
assert_eq!(*foo.0, 20);
}
#[test]
fn debug() {
let data = "Hello World";
let foo = &Holder(Signal::new(data));
assert_eq!(format!("{:?}", foo.0), format!("{data:?}"));
assert!(foo.0.read.get());
}
#[cfg(feature = "ergonomic_ops")]
mod ergonomic_ops {
use super::*;
#[test]
fn eq() {
let foo = &Holder(Signal::new(10));
assert_eq!(foo.0, 10);
assert_ne!(foo.0, 20);
assert!(foo.0.read.get());
}
#[test]
fn cmp() {
let foo = &Holder(Signal::new(10));
assert!(foo.0 > 5);
assert!(foo.0 < 20);
assert!(foo.0.read.get());
}
macro_rules! test_inplace {
($name:ident: $inital:literal $operation:tt $value:literal -> $expected:literal) => {
#[test]
fn $name() {
let foo = &mut Holder(Signal::new($inital));
foo.0 $operation $value;
assert!(foo.0.changed());
assert_eq!(foo.0, $expected);
}
};
}
test_inplace!(inplace_add: 10 += 5 -> 15);
test_inplace!(inplace_sub: 10 -= 5 -> 5);
test_inplace!(inplace_mul: 10 *= 4 -> 40);
test_inplace!(inplace_div: 10 /= 5 -> 2);
test_inplace!(inplace_mod: 12 %= 10 -> 2);
test_inplace!(inplace_and: 0b1100 &= 0b1010 -> 0b1000);
test_inplace!(inplace_or: 0b0100 |= 0b0010 -> 0b0110);
test_inplace!(inplace_xor: 0b1100 ^= 0b1000 -> 0b0100);
test_inplace!(inplace_shl: 1 <<= 1 -> 2);
test_inplace!(inplace_shr: 4 >>= 1 -> 2);
}
}