use std::collections::{BTreeMap, BTreeSet};
use std::future::Future;
use bcode_plugin_sdk::tui::PluginTuiHost;
use tokio::sync::mpsc;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AsyncValue<T> {
Missing,
Loading,
Ready(T),
Error(String),
}
#[derive(Debug)]
pub struct AsyncValueUpdate<K, V> {
key: K,
result: Result<V, String>,
}
impl<K, V> AsyncValueUpdate<K, V> {
#[must_use]
pub const fn key(&self) -> &K {
&self.key
}
}
#[derive(Debug)]
pub struct AsyncValueStore<K, V> {
values: BTreeMap<K, AsyncValue<V>>,
in_flight: BTreeSet<K>,
sender: mpsc::UnboundedSender<AsyncValueUpdate<K, V>>,
receiver: mpsc::UnboundedReceiver<AsyncValueUpdate<K, V>>,
}
impl<K, V> Default for AsyncValueStore<K, V>
where
K: Clone + Ord + Send + 'static,
V: Send + 'static,
{
fn default() -> Self {
Self::new()
}
}
impl<K, V> AsyncValueStore<K, V>
where
K: Clone + Ord + Send + 'static,
V: Send + 'static,
{
#[must_use]
pub fn new() -> Self {
let (sender, receiver) = mpsc::unbounded_channel();
Self {
values: BTreeMap::new(),
in_flight: BTreeSet::new(),
sender,
receiver,
}
}
#[must_use]
pub fn get(&self, key: &K) -> AsyncValue<&V> {
match self.values.get(key) {
Some(AsyncValue::Missing) | None => AsyncValue::Missing,
Some(AsyncValue::Loading) => AsyncValue::Loading,
Some(AsyncValue::Ready(value)) => AsyncValue::Ready(value),
Some(AsyncValue::Error(error)) => AsyncValue::Error(error.clone()),
}
}
pub fn ensure<F, Fut>(&mut self, host: &dyn PluginTuiHost, key: K, load: F) -> bool
where
F: FnOnce(K) -> Fut + Send + 'static,
Fut: Future<Output = Result<V, String>> + Send + 'static,
{
if matches!(self.values.get(&key), Some(AsyncValue::Ready(_)))
|| self.in_flight.contains(&key)
{
return false;
}
self.values.insert(key.clone(), AsyncValue::Loading);
self.in_flight.insert(key.clone());
let sender = self.sender.clone();
host.spawn(Box::pin(async move {
let result = load(key.clone()).await;
let _ = sender.send(AsyncValueUpdate { key, result });
}));
true
}
pub fn remove(&mut self, key: &K) {
self.values.remove(key);
self.in_flight.remove(key);
}
pub fn apply(&mut self, update: AsyncValueUpdate<K, V>) {
self.in_flight.remove(&update.key);
let value = match update.result {
Ok(value) => AsyncValue::Ready(value),
Err(error) => AsyncValue::Error(error),
};
self.values.insert(update.key, value);
}
pub fn try_recv(&mut self) -> Result<AsyncValueUpdate<K, V>, mpsc::error::TryRecvError> {
self.receiver.try_recv()
}
}