use std::collections::HashMap;
use std::hash::Hash;
use std::sync::{Arc, Mutex};
use crate::reactive_family::{EntryKind, MaterializationMode};
use crate::{AsyncCellHandle, AsyncContext, AsyncSlotHandle};
mod sealed {
pub trait Sealed {}
}
pub trait AsyncFamilyHandle<V>: sealed::Sealed + Copy + Send + Sync + 'static {
const KIND: EntryKind;
fn materialize(ctx: &AsyncContext, compute: Arc<dyn Fn() -> V + Send + Sync>) -> Self
where
V: PartialEq + Clone + Send + Sync + 'static;
fn observe(self, ctx: &AsyncContext) -> Option<V>
where
V: Clone + Send + Sync + 'static;
}
impl<V> sealed::Sealed for AsyncCellHandle<V> {}
impl<V: Send + Sync + 'static> AsyncFamilyHandle<V> for AsyncCellHandle<V> {
const KIND: EntryKind = EntryKind::Cell;
fn materialize(ctx: &AsyncContext, compute: Arc<dyn Fn() -> V + Send + Sync>) -> Self
where
V: PartialEq + Clone + Send + Sync + 'static,
{
ctx.cell(compute())
}
fn observe(self, ctx: &AsyncContext) -> Option<V>
where
V: Clone + Send + Sync + 'static,
{
Some(ctx.get_cell(&self))
}
}
impl<V> sealed::Sealed for AsyncSlotHandle<V> {}
impl<V: Send + Sync + 'static> AsyncFamilyHandle<V> for AsyncSlotHandle<V> {
const KIND: EntryKind = EntryKind::Slot;
fn materialize(ctx: &AsyncContext, compute: Arc<dyn Fn() -> V + Send + Sync>) -> Self
where
V: PartialEq + Clone + Send + Sync + 'static,
{
ctx.computed_async(move |_actx| {
let v = compute();
async move { v }
})
}
fn observe(self, ctx: &AsyncContext) -> Option<V>
where
V: Clone + Send + Sync + 'static,
{
ctx.get(&self)
}
}
struct FamilyState<K, H> {
materialized: HashMap<K, H>,
order: Vec<K>,
}
struct FamilyInner<K, V, H> {
mode: MaterializationMode,
factory: Arc<dyn Fn(&K) -> V + Send + Sync>,
state: Mutex<FamilyState<K, H>>,
}
pub struct AsyncReactiveFamily<K, V, H> {
inner: Arc<FamilyInner<K, V, H>>,
}
impl<K, V, H> Clone for AsyncReactiveFamily<K, V, H> {
fn clone(&self) -> Self {
Self {
inner: Arc::clone(&self.inner),
}
}
}
impl<K, V, H> AsyncReactiveFamily<K, V, H>
where
K: Eq + Hash + Clone + Send + Sync + 'static,
V: PartialEq + Clone + Send + Sync + 'static,
H: AsyncFamilyHandle<V>,
{
fn build(
ctx: &AsyncContext,
mode: MaterializationMode,
keys: impl IntoIterator<Item = K>,
factory: impl Fn(&K) -> V + Send + Sync + 'static,
) -> Self {
let fam = Self {
inner: Arc::new(FamilyInner {
mode,
factory: Arc::new(factory),
state: Mutex::new(FamilyState {
materialized: HashMap::new(),
order: Vec::new(),
}),
}),
};
for key in keys {
if H::KIND == EntryKind::Cell || mode == MaterializationMode::Eager {
fam.materialize_key(ctx, key);
}
}
fam
}
pub fn eager(
ctx: &AsyncContext,
keys: impl IntoIterator<Item = K>,
factory: impl Fn(&K) -> V + Send + Sync + 'static,
) -> Self {
Self::build(ctx, MaterializationMode::Eager, keys, factory)
}
pub fn lazy(
ctx: &AsyncContext,
keys: impl IntoIterator<Item = K>,
factory: impl Fn(&K) -> V + Send + Sync + 'static,
) -> Self {
Self::build(ctx, MaterializationMode::Lazy, keys, factory)
}
pub fn new(
ctx: &AsyncContext,
keys: impl IntoIterator<Item = K>,
factory: impl Fn(&K) -> V + Send + Sync + 'static,
) -> Self {
Self::eager(ctx, keys, factory)
}
fn materialize_key(&self, ctx: &AsyncContext, key: K) -> H {
{
let state = self
.inner
.state
.lock()
.expect("family state mutex poisoned");
if let Some(handle) = state.materialized.get(&key) {
return *handle;
}
}
let factory = Arc::clone(&self.inner.factory);
let k = key.clone();
let compute: Arc<dyn Fn() -> V + Send + Sync> = Arc::new(move || factory(&k));
let handle = H::materialize(ctx, compute);
let mut state = self
.inner
.state
.lock()
.expect("family state mutex poisoned");
if let Some(existing) = state.materialized.get(&key) {
return *existing;
}
state.materialized.insert(key.clone(), handle);
state.order.push(key);
handle
}
pub fn get(&self, ctx: &AsyncContext, key: K) -> H {
self.materialize_key(ctx, key)
}
pub fn observe(&self, ctx: &AsyncContext, key: K) -> Option<V> {
self.get(ctx, key).observe(ctx)
}
pub fn is_present(&self, key: &K) -> bool {
self.inner
.state
.lock()
.expect("family state mutex poisoned")
.materialized
.contains_key(key)
}
pub fn present_keys(&self) -> Vec<K> {
self.inner
.state
.lock()
.expect("family state mutex poisoned")
.order
.clone()
}
pub fn present_count(&self) -> usize {
self.inner
.state
.lock()
.expect("family state mutex poisoned")
.order
.len()
}
pub fn mode(&self) -> MaterializationMode {
self.inner.mode
}
pub fn entry_kind(&self) -> EntryKind {
H::KIND
}
}
pub type AsyncCellFamily<K, V> = AsyncReactiveFamily<K, V, AsyncCellHandle<V>>;
pub type AsyncSlotFamily<K, V> = AsyncReactiveFamily<K, V, AsyncSlotHandle<V>>;
#[cfg(test)]
mod tests {
use super::*;
fn assert_send_sync<T: Send + Sync>() {}
#[test]
fn family_is_send_sync() {
assert_send_sync::<AsyncCellFamily<u64, bool>>();
assert_send_sync::<AsyncSlotFamily<u64, usize>>();
}
#[tokio::test]
async fn eager_cell_family_resolves_immediately() {
let ctx = AsyncContext::new();
let fam: AsyncCellFamily<u64, bool> = AsyncReactiveFamily::eager(&ctx, [1, 2, 3], |_| true);
assert_eq!(fam.entry_kind(), EntryKind::Cell);
assert_eq!(fam.present_count(), 3);
assert_eq!(fam.observe(&ctx, 2), Some(true));
assert_eq!(fam.present_keys(), vec![1, 2, 3]);
}
#[tokio::test]
async fn lazy_slot_family_defers_until_read() {
let ctx = AsyncContext::new();
let fam: AsyncSlotFamily<u64, usize> =
AsyncReactiveFamily::lazy(&ctx, [], |k| (*k as usize) * 10);
assert_eq!(fam.mode(), MaterializationMode::Lazy);
assert_eq!(fam.present_count(), 0);
let handle = fam.get(&ctx, 4);
assert!(fam.is_present(&4));
assert_eq!(fam.present_count(), 1);
assert_eq!(ctx.get_async(&handle).await, 40);
}
#[tokio::test]
async fn eventual_transparency_eager_equals_lazy() {
let ctx_e = AsyncContext::new();
let eager: AsyncSlotFamily<u64, usize> =
AsyncReactiveFamily::eager(&ctx_e, [1, 2, 3], |k| (*k as usize) * 2);
let ctx_l = AsyncContext::new();
let lazy: AsyncSlotFamily<u64, usize> =
AsyncReactiveFamily::lazy(&ctx_l, [1, 2, 3], |k| (*k as usize) * 2);
for k in [1u64, 2, 3] {
let ve = ctx_e.get_async(&eager.get(&ctx_e, k)).await;
let vl = ctx_l.get_async(&lazy.get(&ctx_l, k)).await;
assert_eq!(ve, vl);
}
}
#[tokio::test]
async fn present_set_grows_monotonically() {
let ctx = AsyncContext::new();
let fam: AsyncSlotFamily<u64, usize> = AsyncReactiveFamily::lazy(&ctx, [], |k| *k as usize);
let _ = fam.get(&ctx, 5);
let _ = fam.get(&ctx, 5);
let _ = fam.get(&ctx, 9);
assert_eq!(fam.present_count(), 2);
assert_eq!(fam.present_keys(), vec![5, 9]);
}
#[tokio::test]
async fn cell_family_reacts_to_set() {
let ctx = AsyncContext::new();
let fam: AsyncCellFamily<u64, bool> = AsyncReactiveFamily::eager(&ctx, [10, 20], |_| true);
assert_eq!(fam.observe(&ctx, 20), Some(true));
let h = fam.get(&ctx, 20);
ctx.set_cell(&h, false);
assert_eq!(fam.observe(&ctx, 20), Some(false));
}
}