use std::cell::{Cell, RefCell};
use std::rc::Rc;
use crate::window::WindowId;
#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
pub struct Dirty(u8);
impl Dirty {
pub const EMPTY: Self = Self(0);
pub const VIEW: Self = Self(1);
pub const LAYOUT: Self = Self(1 << 1);
pub const PAINT: Self = Self(1 << 2);
pub const PRESENT: Self = Self(1 << 3);
pub const fn empty() -> Self {
Self(0)
}
pub const fn bits(self) -> u8 {
self.0
}
pub const fn contains(self, other: Self) -> bool {
self.0 & other.0 == other.0
}
pub const fn is_empty(self) -> bool {
self.0 == 0
}
pub fn insert(&mut self, other: Self) {
self.0 |= other.0;
}
pub fn remove(&mut self, other: Self) {
self.0 &= !other.0;
}
}
impl std::ops::BitOr for Dirty {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
Self(self.0 | rhs.0)
}
}
impl std::ops::BitOrAssign for Dirty {
fn bitor_assign(&mut self, rhs: Self) {
self.insert(rhs);
}
}
impl std::ops::BitAnd for Dirty {
type Output = Self;
fn bitand(self, rhs: Self) -> Self {
Self(self.0 & rhs.0)
}
}
#[derive(Clone, Default)]
pub struct Runtime {
inner: Rc<RuntimeInner>,
}
#[derive(Default)]
struct RuntimeInner {
windows: RefCell<Vec<(WindowId, Dirty)>>,
in_view: Cell<Option<WindowId>>,
requests: RequestQueue,
theme: RefCell<crate::theme::Theme>,
}
#[derive(Default, Clone)]
pub struct RequestQueue {
items: Rc<RefCell<Vec<Box<dyn std::any::Any>>>>,
}
impl RequestQueue {
pub fn new() -> Self {
Self::default()
}
pub fn push<T: 'static>(&self, v: T) {
self.items.borrow_mut().push(Box::new(v));
}
pub fn take<T: 'static>(&self) -> Vec<T> {
let mut items = self.items.borrow_mut();
let mut out = Vec::new();
let mut rest = Vec::with_capacity(items.len());
for item in items.drain(..) {
match item.downcast::<T>() {
Ok(v) => out.push(*v),
Err(other) => rest.push(other),
}
}
*items = rest;
out
}
pub fn len(&self) -> usize {
self.items.borrow().len()
}
pub fn is_empty(&self) -> bool {
self.items.borrow().is_empty()
}
}
impl std::fmt::Debug for RequestQueue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RequestQueue")
.field("pending", &self.len())
.finish()
}
}
impl Runtime {
pub fn new() -> Self {
Self::default()
}
pub fn register_window(&self, id: WindowId) {
let mut w = self.inner.windows.borrow_mut();
if !w.iter().any(|(x, _)| *x == id) {
w.push((id, Dirty::empty()));
}
}
pub fn unregister_window(&self, id: WindowId) {
self.inner.windows.borrow_mut().retain(|(x, _)| *x != id);
}
pub fn windows(&self) -> Vec<WindowId> {
self.inner.windows.borrow().iter().map(|(x, _)| *x).collect()
}
pub fn requests(&self) -> &RequestQueue {
&self.inner.requests
}
pub(crate) fn mark_all(&self, d: Dirty) {
for (_, flags) in self.inner.windows.borrow_mut().iter_mut() {
flags.insert(d);
}
}
pub fn theme(&self) -> crate::theme::Theme {
*self.inner.theme.borrow()
}
pub fn set_theme(&self, t: crate::theme::Theme) {
if *self.inner.theme.borrow() == t {
return;
}
*self.inner.theme.borrow_mut() = t;
self.mark_all(Dirty::VIEW | Dirty::PAINT | Dirty::PRESENT);
}
pub fn mark(&self, id: WindowId, d: Dirty) {
if let Some((_, flags)) = self.inner.windows.borrow_mut().iter_mut().find(|(x, _)| *x == id) {
flags.insert(d);
}
}
pub fn take_dirty(&self, id: WindowId) -> Dirty {
match self
.inner
.windows
.borrow_mut()
.iter_mut()
.find(|(x, _)| *x == id)
{
Some((_, flags)) => std::mem::take(flags),
None => Dirty::empty(),
}
}
pub fn peek_dirty(&self, id: WindowId) -> Dirty {
self.inner
.windows
.borrow()
.iter()
.find(|(x, _)| *x == id)
.map(|(_, f)| *f)
.unwrap_or(Dirty::empty())
}
#[allow(dead_code)]
pub(crate) fn begin_view(&self, id: WindowId) {
debug_assert!(
self.inner.in_view.get().is_none(),
"Runtime::begin_view 嵌套调用(应为每个窗口串行执行 view())"
);
self.inner.in_view.set(Some(id));
}
#[allow(dead_code)]
pub(crate) fn end_view(&self) {
self.inner.in_view.set(None);
}
pub fn is_in_view(&self) -> bool {
self.inner.in_view.get().is_some()
}
}
pub struct Signal<T: 'static> {
inner: Rc<SignalInner<T>>,
rt: Runtime,
}
struct SignalInner<T> {
value: RefCell<T>,
}
impl<T: 'static> Signal<T> {
pub fn new(rt: &Runtime, value: T) -> Self {
Self {
inner: Rc::new(SignalInner {
value: RefCell::new(value),
}),
rt: rt.clone(),
}
}
pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
f(&self.inner.value.borrow())
}
pub fn set(&self, value: T) {
self.assert_not_in_view();
*self.inner.value.borrow_mut() = value;
self.rt.mark_all(Dirty::VIEW);
}
pub fn update<F: FnOnce(&mut T)>(&self, f: F) {
self.assert_not_in_view();
f(&mut self.inner.value.borrow_mut());
self.rt.mark_all(Dirty::VIEW);
}
#[track_caller]
fn assert_not_in_view(&self) {
if cfg!(debug_assertions) && self.rt.is_in_view() {
panic!(
"Signal::set/update 不能在 view() 内调用(会自我触发循环):\
请把状态变更放到事件闭包或 on_tick/on_external 里"
);
}
}
}
impl<T: Clone + 'static> Signal<T> {
pub fn get(&self) -> T {
self.inner.value.borrow().clone()
}
}
impl<T: Default + 'static> Signal<T> {
pub fn take(&self) -> T {
self.assert_not_in_view();
let out = std::mem::take(&mut *self.inner.value.borrow_mut());
self.rt.mark_all(Dirty::VIEW);
out
}
}
impl<T: 'static> Clone for Signal<T> {
fn clone(&self) -> Self {
Self {
inner: Rc::clone(&self.inner),
rt: self.rt.clone(),
}
}
}
impl<T: std::fmt::Debug + 'static> std::fmt::Debug for Signal<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("Signal").field(&*self.inner.value.borrow()).finish()
}
}
pub fn act<T: 'static>(vm: &Rc<T>, f: fn(&T)) -> impl Fn() + 'static {
let me = Rc::clone(vm);
move || f(&me)
}
pub fn act1<T: 'static, A: Clone + 'static>(vm: &Rc<T>, f: fn(&T, A), arg: A) -> impl Fn() + 'static {
let me = Rc::clone(vm);
move || f(&me, arg.clone())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn set_marks_all_windows_and_take_clears() {
let rt = Runtime::new();
let a = WindowId::new(1);
let b = WindowId::new(2);
rt.register_window(a);
rt.register_window(b);
let s = Signal::new(&rt, 0);
s.set(1);
assert!(rt.take_dirty(a).contains(Dirty::VIEW));
assert!(rt.take_dirty(b).contains(Dirty::VIEW));
assert!(rt.take_dirty(a).is_empty());
assert!(rt.peek_dirty(b).is_empty());
}
#[test]
fn update_writes_and_marks() {
let rt = Runtime::new();
let w = WindowId::new(1);
rt.register_window(w);
let s = Signal::new(&rt, 1);
s.update(|v| *v += 41);
assert_eq!(s.get(), 42);
assert!(rt.take_dirty(w).contains(Dirty::VIEW));
}
#[test]
fn batching_is_free_one_flag_for_n_sets() {
let rt = Runtime::new();
let w = WindowId::new(1);
rt.register_window(w);
let a = Signal::new(&rt, 0);
let b = Signal::new(&rt, 0);
a.set(1);
b.set(2);
a.update(|v| *v += 1);
assert_eq!(rt.take_dirty(w), Dirty::VIEW);
}
#[test]
fn signal_is_clone_even_if_t_is_not() {
struct NotClone(u32);
let rt = Runtime::new();
let s = Signal::new(&rt, NotClone(1));
let s2 = s.clone();
s2.set(NotClone(2));
assert_eq!(s.with(|v| v.0), 2);
}
#[test]
fn with_avoids_clone() {
let rt = Runtime::new();
let s = Signal::new(&rt, vec![1, 2, 3]);
assert_eq!(s.with(|v| v.len()), 3);
}
#[test]
fn take_resets_to_default() {
let rt = Runtime::new();
let w = WindowId::new(1);
rt.register_window(w);
let s: Signal<String> = Signal::new(&rt, "hi".into());
assert_eq!(s.take(), "hi");
assert_eq!(s.with(|v| v.clone()), "");
assert!(rt.take_dirty(w).contains(Dirty::VIEW));
}
#[test]
fn unregistered_window_gets_nothing_and_does_not_panic() {
let rt = Runtime::new();
let w = WindowId::new(7);
rt.register_window(w);
let s = Signal::new(&rt, 0);
s.set(1);
rt.unregister_window(w);
assert!(rt.take_dirty(w).is_empty());
rt.mark_all(Dirty::PAINT); assert!(rt.take_dirty(w).is_empty());
}
#[test]
fn register_is_idempotent() {
let rt = Runtime::new();
let w = WindowId::new(1);
rt.register_window(w);
rt.register_window(w);
assert_eq!(rt.windows(), vec![w]);
}
#[cfg(debug_assertions)]
#[test]
#[should_panic(expected = "view()")]
fn set_inside_view_panics() {
let rt = Runtime::new();
let w = WindowId::new(1);
rt.register_window(w);
let s = Signal::new(&rt, 0);
rt.begin_view(w); s.set(1); }
#[test]
fn get_inside_view_is_fine_and_no_dirty_is_marked() {
let rt = Runtime::new();
let w = WindowId::new(1);
rt.register_window(w);
let s = Signal::new(&rt, 7);
rt.begin_view(w);
assert_eq!(s.get(), 7);
rt.end_view();
assert!(rt.take_dirty(w).is_empty());
}
#[test]
fn act_calls_the_method() {
use std::cell::Cell;
struct Vm {
hits: Cell<u32>,
}
impl Vm {
fn inc(&self) {
self.hits.set(self.hits.get() + 1);
}
}
let vm = Rc::new(Vm { hits: Cell::new(0) });
let f = act(&vm, Vm::inc);
f();
f();
assert_eq!(vm.hits.get(), 2);
}
#[test]
fn act1_passes_the_argument() {
use std::cell::Cell;
struct Vm {
last: Cell<u32>,
}
impl Vm {
fn pick(&self, id: u32) {
self.last.set(id);
}
}
let vm = Rc::new(Vm { last: Cell::new(0) });
let f = act1(&vm, Vm::pick, 42);
f();
assert_eq!(vm.last.get(), 42);
}
}