use std::cell::RefCell;
use std::collections::HashMap;
use std::hash::Hash;
use std::rc::Rc;
use crate::Context;
use crate::cell::CellHandle;
use crate::slot::SlotHandle;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EntryKind {
Cell,
Slot,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum MaterializationMode {
#[default]
Eager,
Lazy,
}
mod sealed {
pub trait Sealed {}
}
pub trait FamilyHandle<V>: sealed::Sealed + Copy + 'static {
const KIND: EntryKind;
fn materialize(ctx: &Context, compute: impl Fn(&Context) -> V + 'static) -> Self
where
V: PartialEq + Clone + 'static;
fn observe(self, ctx: &Context) -> V
where
V: Clone + 'static;
}
impl<V> sealed::Sealed for CellHandle<V> {}
impl<V: 'static> FamilyHandle<V> for CellHandle<V> {
const KIND: EntryKind = EntryKind::Cell;
fn materialize(ctx: &Context, compute: impl Fn(&Context) -> V + 'static) -> Self
where
V: PartialEq + Clone + 'static,
{
ctx.cell(compute(ctx))
}
fn observe(self, ctx: &Context) -> V
where
V: Clone + 'static,
{
ctx.get_cell(&self)
}
}
impl<V> sealed::Sealed for SlotHandle<V> {}
impl<V: 'static> FamilyHandle<V> for SlotHandle<V> {
const KIND: EntryKind = EntryKind::Slot;
fn materialize(ctx: &Context, compute: impl Fn(&Context) -> V + 'static) -> Self
where
V: PartialEq + Clone + 'static,
{
ctx.computed(compute)
}
fn observe(self, ctx: &Context) -> V
where
V: Clone + 'static,
{
ctx.get(&self)
}
}
struct FamilyInner<K, V, H> {
mode: MaterializationMode,
factory: Rc<dyn Fn(&K) -> V>,
materialized: RefCell<HashMap<K, H>>,
order: RefCell<Vec<K>>,
}
pub struct ReactiveFamily<K, V, H> {
inner: Rc<FamilyInner<K, V, H>>,
}
impl<K, V, H> Clone for ReactiveFamily<K, V, H> {
fn clone(&self) -> Self {
Self {
inner: Rc::clone(&self.inner),
}
}
}
impl<K, V, H> ReactiveFamily<K, V, H>
where
K: Eq + Hash + Clone + 'static,
V: PartialEq + Clone + 'static,
H: FamilyHandle<V>,
{
fn build(
ctx: &Context,
mode: MaterializationMode,
keys: impl IntoIterator<Item = K>,
factory: impl Fn(&K) -> V + 'static,
) -> Self {
let fam = Self {
inner: Rc::new(FamilyInner {
mode,
factory: Rc::new(factory),
materialized: RefCell::new(HashMap::new()),
order: RefCell::new(Vec::new()),
}),
};
for key in keys {
if H::KIND == EntryKind::Cell || mode == MaterializationMode::Eager {
fam.materialize_key(ctx, key);
}
}
fam
}
pub fn eager(
ctx: &Context,
keys: impl IntoIterator<Item = K>,
factory: impl Fn(&K) -> V + 'static,
) -> Self {
Self::build(ctx, MaterializationMode::Eager, keys, factory)
}
pub fn lazy(
ctx: &Context,
keys: impl IntoIterator<Item = K>,
factory: impl Fn(&K) -> V + 'static,
) -> Self {
Self::build(ctx, MaterializationMode::Lazy, keys, factory)
}
pub fn new(
ctx: &Context,
keys: impl IntoIterator<Item = K>,
factory: impl Fn(&K) -> V + 'static,
) -> Self {
Self::eager(ctx, keys, factory)
}
fn materialize_key(&self, ctx: &Context, key: K) -> H {
if let Some(handle) = self.inner.materialized.borrow().get(&key) {
return *handle; }
let factory = Rc::clone(&self.inner.factory);
let k = key.clone();
let handle = H::materialize(ctx, move |_ctx| factory(&k));
self.inner
.materialized
.borrow_mut()
.insert(key.clone(), handle);
self.inner.order.borrow_mut().push(key);
handle
}
pub fn get(&self, ctx: &Context, key: K) -> H {
self.materialize_key(ctx, key)
}
pub fn observe(&self, ctx: &Context, key: K) -> V {
self.get(ctx, key).observe(ctx)
}
pub fn is_present(&self, key: &K) -> bool {
self.inner.materialized.borrow().contains_key(key)
}
pub fn present_keys(&self) -> Vec<K> {
self.inner.order.borrow().clone()
}
pub fn present_count(&self) -> usize {
self.inner.order.borrow().len()
}
pub fn mode(&self) -> MaterializationMode {
self.inner.mode
}
pub fn entry_kind(&self) -> EntryKind {
H::KIND
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_mode_is_eager() {
assert_eq!(MaterializationMode::default(), MaterializationMode::Eager);
}
#[test]
fn eager_materializes_all_up_front() {
let ctx = Context::new();
let fam: ReactiveFamily<u32, u32, SlotHandle<u32>> =
ReactiveFamily::eager(&ctx, [0, 1, 2, 5, 9], |&k| k * 3);
assert_eq!(fam.present_count(), 5);
for k in [0u32, 1, 2, 5, 9] {
assert!(fam.is_present(&k));
}
}
#[test]
fn lazy_defers_slots_until_read() {
let ctx = Context::new();
let fam: ReactiveFamily<u32, u32, SlotHandle<u32>> =
ReactiveFamily::lazy(&ctx, [0, 1, 2, 5, 9], |&k| k * 3);
assert_eq!(fam.present_count(), 0);
assert!(!fam.is_present(&5));
assert_eq!(fam.observe(&ctx, 5), 15);
assert!(fam.is_present(&5));
assert_eq!(fam.present_keys(), vec![5]);
}
#[test]
fn eager_and_lazy_observe_identically() {
let ctx = Context::new();
let eager: ReactiveFamily<u32, u32, SlotHandle<u32>> =
ReactiveFamily::eager(&ctx, [0, 1, 2, 5, 9], |&k| k * 3);
let lazy: ReactiveFamily<u32, u32, SlotHandle<u32>> =
ReactiveFamily::lazy(&ctx, [0, 1, 2, 5, 9], |&k| k * 3);
for k in [0u32, 1, 2, 5, 9] {
assert_eq!(eager.observe(&ctx, k), lazy.observe(&ctx, k));
}
}
#[test]
fn present_set_is_monotone_across_reads() {
let ctx = Context::new();
let fam: ReactiveFamily<u32, u32, SlotHandle<u32>> =
ReactiveFamily::lazy(&ctx, [1, 2, 3, 4, 5], |&k| k * 2);
let mut sizes = Vec::new();
for k in [2u32, 4, 2, 5] {
fam.observe(&ctx, k);
sizes.push(fam.present_count());
}
assert_eq!(sizes, vec![1, 2, 2, 3]);
assert_eq!(fam.present_keys(), vec![2, 4, 5]);
}
#[test]
fn cell_family_materialized_in_every_mode() {
let ctx = Context::new();
for mode_lazy in [false, true] {
let keys = ["a", "b", "c"];
let fam: ReactiveFamily<&str, u32, CellHandle<u32>> = if mode_lazy {
ReactiveFamily::lazy(&ctx, keys, |_| 0)
} else {
ReactiveFamily::eager(&ctx, keys, |_| 0)
};
assert_eq!(fam.entry_kind(), EntryKind::Cell);
assert_eq!(fam.present_count(), 3);
}
}
#[test]
fn cell_family_entries_are_writable_inputs() {
let ctx = Context::new();
let fam: ReactiveFamily<u32, u32, CellHandle<u32>> =
ReactiveFamily::eager(&ctx, [7], |&k| k);
let h = fam.get(&ctx, 7);
assert_eq!(h.get(&ctx), 7);
h.set(&ctx, 100);
assert_eq!(fam.observe(&ctx, 7), 100);
}
}