use std::collections::HashMap;
use std::hash::Hash;
use std::marker::PhantomData;
use std::sync::{Arc, Mutex};
use crate::cell_family::EntryKind;
use crate::{AsyncCellHandle, AsyncContext, AsyncSlotHandle};
mod sealed {
pub trait Sealed {}
}
pub trait AsyncMapHandle<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> AsyncMapHandle<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> AsyncMapHandle<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 MapState<K, H> {
materialized: HashMap<K, H>,
order: Vec<K>,
}
struct MapInner<K, H> {
state: Mutex<MapState<K, H>>,
}
pub struct AsyncReactiveMap<K, V, H> {
inner: Arc<MapInner<K, H>>,
_marker: PhantomData<V>,
}
impl<K, V, H> Clone for AsyncReactiveMap<K, V, H> {
fn clone(&self) -> Self {
Self {
inner: Arc::clone(&self.inner),
_marker: PhantomData,
}
}
}
impl<K, V, H> AsyncReactiveMap<K, V, H>
where
K: Eq + Hash + Clone + Send + Sync + 'static,
V: PartialEq + Clone + Send + Sync + 'static,
H: AsyncMapHandle<V>,
{
pub fn new(_ctx: &AsyncContext) -> Self {
Self {
inner: Arc::new(MapInner {
state: Mutex::new(MapState {
materialized: HashMap::new(),
order: Vec::new(),
}),
}),
_marker: PhantomData,
}
}
fn mint_with(
&self,
ctx: &AsyncContext,
key: K,
compute: Arc<dyn Fn() -> V + Send + Sync>,
) -> H {
{
let state = self.inner.state.lock().expect("map state mutex poisoned");
if let Some(handle) = state.materialized.get(&key) {
return *handle;
}
}
let handle = H::materialize(ctx, compute);
let mut state = self.inner.state.lock().expect("map 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_or_insert_handle(
&self,
ctx: &AsyncContext,
key: K,
factory: impl Fn(&K) -> V + Send + Sync + 'static,
) -> H {
let k = key.clone();
let compute: Arc<dyn Fn() -> V + Send + Sync> = Arc::new(move || factory(&k));
self.mint_with(ctx, key, compute)
}
pub fn observe(&self, ctx: &AsyncContext, key: &K) -> Option<V> {
let handle = {
let state = self.inner.state.lock().expect("map state mutex poisoned");
state.materialized.get(key).copied()
};
handle.and_then(|h| h.observe(ctx))
}
pub fn handle(&self, key: &K) -> Option<H> {
self.inner
.state
.lock()
.expect("map state mutex poisoned")
.materialized
.get(key)
.copied()
}
pub fn is_present(&self, key: &K) -> bool {
self.inner
.state
.lock()
.expect("map state mutex poisoned")
.materialized
.contains_key(key)
}
pub fn present_keys(&self) -> Vec<K> {
self.inner
.state
.lock()
.expect("map state mutex poisoned")
.order
.clone()
}
pub fn present_count(&self) -> usize {
self.inner
.state
.lock()
.expect("map state mutex poisoned")
.order
.len()
}
pub fn entry_kind(&self) -> EntryKind {
H::KIND
}
}
impl<K, V> AsyncReactiveMap<K, V, AsyncCellHandle<V>>
where
K: Eq + Hash + Clone + Send + Sync + 'static,
V: PartialEq + Clone + Send + Sync + 'static,
{
pub fn set(&self, ctx: &AsyncContext, key: K, value: V) {
let existing = {
let state = self.inner.state.lock().expect("map state mutex poisoned");
state.materialized.get(&key).copied()
};
if let Some(handle) = existing {
ctx.set_cell(&handle, value);
return;
}
self.get_or_insert_handle(ctx, key, move |_| value.clone());
}
}
impl<K, V> AsyncReactiveMap<K, V, AsyncSlotHandle<V>>
where
K: Eq + Hash + Clone + Send + Sync + 'static,
V: PartialEq + Clone + Send + Sync + 'static,
{
pub fn materialize_all(
&self,
ctx: &AsyncContext,
keys: impl IntoIterator<Item = K>,
factory: impl Fn(&K) -> V + Send + Sync + 'static,
) {
let factory = Arc::new(factory);
for key in keys {
let f = Arc::clone(&factory);
self.get_or_insert_handle(ctx, key, move |k| f(k));
}
}
}
pub type AsyncCellMap<K, V> = AsyncReactiveMap<K, V, AsyncCellHandle<V>>;
pub type AsyncSlotMap<K, V> = AsyncReactiveMap<K, V, AsyncSlotHandle<V>>;
#[cfg(test)]
mod tests {
use super::*;
fn assert_send_sync<T: Send + Sync>() {}
#[test]
fn map_is_send_sync() {
assert_send_sync::<AsyncCellMap<u64, bool>>();
assert_send_sync::<AsyncSlotMap<u64, usize>>();
}
#[tokio::test]
async fn eager_cell_map_resolves_immediately() {
let ctx = AsyncContext::new();
let fam: AsyncCellMap<u64, bool> = AsyncCellMap::new(&ctx);
for k in [1u64, 2, 3] {
fam.set(&ctx, k, 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_map_defers_until_read() {
let ctx = AsyncContext::new();
let fam: AsyncSlotMap<u64, usize> = AsyncSlotMap::new(&ctx);
assert_eq!(fam.present_count(), 0);
let handle = fam.get_or_insert_handle(&ctx, 4, |k| (*k as usize) * 10);
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: AsyncSlotMap<u64, usize> = AsyncSlotMap::new(&ctx_e);
eager.materialize_all(&ctx_e, [1, 2, 3], |k| (*k as usize) * 2);
let ctx_l = AsyncContext::new();
let lazy: AsyncSlotMap<u64, usize> = AsyncSlotMap::new(&ctx_l);
for k in [1u64, 2, 3] {
let ve = ctx_e.get_async(&eager.handle(&k).unwrap()).await;
let vl = ctx_l
.get_async(&lazy.get_or_insert_handle(&ctx_l, k, |k| (*k as usize) * 2))
.await;
assert_eq!(ve, vl);
}
}
#[tokio::test]
async fn present_set_grows_monotonically() {
let ctx = AsyncContext::new();
let fam: AsyncSlotMap<u64, usize> = AsyncSlotMap::new(&ctx);
let _ = fam.get_or_insert_handle(&ctx, 5, |k| *k as usize);
let _ = fam.get_or_insert_handle(&ctx, 5, |k| *k as usize);
let _ = fam.get_or_insert_handle(&ctx, 9, |k| *k as usize);
assert_eq!(fam.present_count(), 2);
assert_eq!(fam.present_keys(), vec![5, 9]);
}
#[tokio::test]
async fn cell_map_reacts_to_set() {
let ctx = AsyncContext::new();
let fam: AsyncCellMap<u64, bool> = AsyncCellMap::new(&ctx);
for k in [10u64, 20] {
fam.set(&ctx, k, true);
}
assert_eq!(fam.observe(&ctx, &20), Some(true));
fam.set(&ctx, 20, false);
assert_eq!(fam.observe(&ctx, &20), Some(false));
}
}