use std::any::Any;
use std::cell::{Cell, RefCell};
use std::marker::PhantomData;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
struct SlotKey {
index: u32,
generation: u32,
}
struct Slot {
generation: u32,
value: Option<Box<dyn Any>>,
version: u64,
}
struct Runtime {
slots: Vec<Slot>,
free: Vec<u32>,
}
impl Runtime {
const fn new() -> Self {
Self {
slots: Vec::new(),
free: Vec::new(),
}
}
fn insert(&mut self, value: Box<dyn Any>) -> SlotKey {
if let Some(idx) = self.free.pop() {
let slot = &mut self.slots[idx as usize];
slot.value = Some(value);
slot.version = 0;
SlotKey {
index: idx,
generation: slot.generation,
}
} else {
let idx = self.slots.len() as u32;
self.slots.push(Slot {
generation: 0,
value: Some(value),
version: 0,
});
SlotKey {
index: idx,
generation: 0,
}
}
}
fn slot(&self, key: SlotKey) -> Option<&Slot> {
self.slots
.get(key.index as usize)
.filter(|s| s.generation == key.generation && s.value.is_some())
}
fn slot_mut(&mut self, key: SlotKey) -> Option<&mut Slot> {
self.slots
.get_mut(key.index as usize)
.filter(|s| s.generation == key.generation && s.value.is_some())
}
}
thread_local! {
static RT: RefCell<Runtime> = const { RefCell::new(Runtime::new()) };
static EVENT_ACTIVE: Cell<bool> = const { Cell::new(false) };
static TOUCHED: Cell<bool> = const { Cell::new(false) };
}
fn notify_changed() {
if EVENT_ACTIVE.with(|c| c.get()) {
TOUCHED.with(|c| c.set(true));
} else {
crate::anim::request_repaint();
}
}
pub(crate) fn begin_event() {
EVENT_ACTIVE.with(|c| c.set(true));
TOUCHED.with(|c| c.set(false));
}
pub(crate) fn end_event() -> bool {
EVENT_ACTIVE.with(|c| c.set(false));
TOUCHED.with(|c| c.replace(false))
}
pub struct Signal<T> {
key: SlotKey,
_t: PhantomData<fn() -> T>,
}
impl<T> Clone for Signal<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for Signal<T> {}
pub fn signal<T: 'static>(value: T) -> Signal<T> {
let key = RT.with(|rt| rt.borrow_mut().insert(Box::new(value)));
Signal {
key,
_t: PhantomData,
}
}
impl<T: 'static> Signal<T> {
pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
RT.with(|rt| {
let rt = rt.borrow();
let slot = rt.slot(self.key).expect("signal 句柄已失效");
let v = slot
.value
.as_ref()
.unwrap()
.downcast_ref::<T>()
.expect("signal 类型不匹配");
f(v)
})
}
pub fn set(&self, value: T) {
RT.with(|rt| {
let mut rt = rt.borrow_mut();
if let Some(slot) = rt.slot_mut(self.key) {
slot.value = Some(Box::new(value));
slot.version = slot.version.wrapping_add(1);
}
});
notify_changed();
}
pub fn update(&self, f: impl FnOnce(&mut T)) {
RT.with(|rt| {
let mut rt = rt.borrow_mut();
if let Some(slot) = rt.slot_mut(self.key) {
if let Some(v) = slot.value.as_mut().and_then(|b| b.downcast_mut::<T>()) {
f(v);
slot.version = slot.version.wrapping_add(1);
}
}
});
notify_changed();
}
}
impl<T: Clone + 'static> Signal<T> {
pub fn get(&self) -> T {
self.with(|v| v.clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn get_set_roundtrip() {
let s = signal(3i32);
assert_eq!(s.get(), 3);
s.set(7);
assert_eq!(s.get(), 7);
}
#[test]
fn update_in_place() {
let s = signal(10i32);
s.update(|v| *v += 5);
assert_eq!(s.get(), 15);
}
#[test]
fn copy_handle_into_closures() {
let s = signal(0i32);
let inc = move || s.update(|v| *v += 1);
let read = move || s.get();
inc();
inc();
assert_eq!(read(), 2);
assert_eq!(s.get(), 2, "原句柄与闭包内句柄指向同一存储");
}
#[test]
fn distinct_signals_are_independent() {
let a = signal(1i32);
let b = signal(100i32);
a.set(2);
assert_eq!(a.get(), 2);
assert_eq!(b.get(), 100);
}
#[test]
fn with_borrows_without_clone() {
let s = signal(String::from("hello"));
let len = s.with(|v| v.len());
assert_eq!(len, 5);
s.update(|v| v.push_str(" world"));
assert_eq!(s.with(String::len), 11);
}
#[test]
fn set_in_event_marks_touched() {
let s = signal(0i32);
begin_event();
s.set(1);
assert!(end_event(), "事件期内写信号应标记 touched");
}
#[test]
fn set_outside_event_not_touched() {
let _ = end_event(); let s = signal(0i32);
s.set(9);
begin_event();
assert!(!end_event(), "事件期外的写不应记入下一次事件 touched");
}
#[test]
fn non_clone_type_supported_via_with() {
struct NoClone(i32);
let s = signal(NoClone(42));
assert_eq!(s.with(|v| v.0), 42);
s.update(|v| v.0 = 7);
assert_eq!(s.with(|v| v.0), 7);
}
}