use std::{collections::HashSet, sync::Arc};
use hyphae::{Cell, CellImmutable, MapExt, SelectExt};
use super::super::item::AnyItem;
use crate::store::StoreRegistry;
#[derive(Clone)]
pub struct CellReportContext {
pub registry: Arc<StoreRegistry>,
}
impl CellReportContext {
pub fn new(registry: Arc<StoreRegistry>) -> Self {
Self { registry }
}
pub fn query_all(&self, entity_type: &str) -> Cell<Vec<Arc<dyn AnyItem>>, CellImmutable> {
self.registry
.get_or_create(entity_type)
.select(|_| true)
.entries()
.map(|entries| entries.iter().map(|(_, item)| item.clone()).collect())
}
pub fn query_by_ids(
&self,
entity_type: &str,
ids: Vec<Arc<str>>,
) -> Cell<Vec<Arc<dyn AnyItem>>, CellImmutable> {
let id_set: HashSet<Arc<str>> = ids.into_iter().collect();
self.registry
.get_or_create(entity_type)
.select(move |item| id_set.contains(&item.id()))
.entries()
.map(|entries| entries.iter().map(|(_, item)| item.clone()).collect())
}
pub fn query_where<F>(
&self,
entity_type: &str,
predicate: F,
) -> Cell<Vec<Arc<dyn AnyItem>>, CellImmutable>
where
F: Fn(&Arc<dyn AnyItem>) -> bool + Send + Sync + 'static,
{
self.registry
.get_or_create(entity_type)
.select(predicate)
.entries()
.map(|entries| entries.iter().map(|(_, item)| item.clone()).collect())
}
pub fn query_by_id(
&self,
entity_type: &str,
id: Arc<str>,
) -> Cell<Option<Arc<dyn AnyItem>>, CellImmutable> {
self.registry
.get_or_create(entity_type)
.select(move |item| *item.id() == *id)
.entries()
.map(|entries| entries.iter().next().map(|(_, item)| item.clone()))
}
pub fn query_count<F>(&self, entity_type: &str, predicate: F) -> Cell<usize, CellImmutable>
where
F: Fn(&Arc<dyn AnyItem>) -> bool + Send + Sync + 'static,
{
self.registry
.get_or_create(entity_type)
.select(predicate)
.len()
}
pub fn report<R: CellReportHandler>(&self, report: R) -> Cell<R::Output, CellImmutable> {
report.compute(self)
}
}
pub trait CellReportHandler: Sized + Send + Sync + 'static {
type Output: Clone + Send + Sync + 'static;
fn compute(&self, ctx: &CellReportContext) -> Cell<Self::Output, CellImmutable>;
}
#[cfg(test)]
mod tests {
use hyphae::{Gettable, MapExt};
use super::*;
use crate::common::with_id::WithId;
#[derive(Debug, Clone, PartialEq, serde::Serialize)]
struct TestTarget {
id: Arc<str>,
name: String,
online: bool,
}
impl WithId for TestTarget {
fn id(&self) -> Arc<str> {
self.id.clone()
}
}
impl AnyItem for TestTarget {
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn entity_type(&self) -> &'static str {
"TestTarget"
}
fn equals(&self, other: &dyn AnyItem) -> bool {
other
.as_any()
.downcast_ref::<Self>()
.map(|typed| self == typed)
.unwrap_or(false)
}
}
fn make_target(id: &str, name: &str, online: bool) -> Arc<dyn AnyItem> {
Arc::new(TestTarget {
id: id.into(),
name: name.to_string(),
online,
}) as Arc<dyn AnyItem>
}
struct OnlineTargetCount;
impl CellReportHandler for OnlineTargetCount {
type Output = usize;
fn compute(&self, ctx: &CellReportContext) -> Cell<Self::Output, CellImmutable> {
ctx.query_where("Target", |item| {
if let Some(target) = item.as_any().downcast_ref::<TestTarget>() {
target.online
} else {
false
}
})
.map(|targets| targets.len())
}
}
struct TargetNames;
impl CellReportHandler for TargetNames {
type Output = Vec<String>;
fn compute(&self, ctx: &CellReportContext) -> Cell<Self::Output, CellImmutable> {
ctx.query_all("Target").map(|targets| {
targets
.iter()
.filter_map(|item| item.as_any().downcast_ref::<TestTarget>())
.map(|t| t.name.clone())
.collect()
})
}
}
#[test]
fn test_simple_report() {
let registry = Arc::new(StoreRegistry::new());
let store = registry.get_or_create("Target");
store.insert("a".into(), make_target("a", "Alice", true));
store.insert("b".into(), make_target("b", "Bob", false));
store.insert("c".into(), make_target("c", "Charlie", true));
let ctx = CellReportContext::new(registry.clone());
let online_count = ctx.report(OnlineTargetCount);
assert_eq!(online_count.get(), 2);
store.insert("b".into(), make_target("b", "Bob", true));
assert_eq!(online_count.get(), 3);
store.remove(&"a".into());
assert_eq!(online_count.get(), 2);
}
#[test]
fn test_composed_report() {
let registry = Arc::new(StoreRegistry::new());
let store = registry.get_or_create("Target");
store.insert("a".into(), make_target("a", "Alice", true));
store.insert("b".into(), make_target("b", "Bob", false));
let ctx = CellReportContext::new(registry.clone());
let names = ctx.report(TargetNames);
let mut result = names.get();
result.sort();
assert_eq!(result, vec!["Alice", "Bob"]);
store.insert("c".into(), make_target("c", "Charlie", true));
let mut result = names.get();
result.sort();
assert_eq!(result, vec!["Alice", "Bob", "Charlie"]);
}
struct OnlineTargetReport {
threshold: usize,
}
impl CellReportHandler for OnlineTargetReport {
type Output = bool;
fn compute(&self, ctx: &CellReportContext) -> Cell<Self::Output, CellImmutable> {
let threshold = self.threshold;
ctx.report(OnlineTargetCount)
.map(move |count| *count >= threshold)
}
}
#[test]
fn test_sub_report() {
let registry = Arc::new(StoreRegistry::new());
let store = registry.get_or_create("Target");
store.insert("a".into(), make_target("a", "Alice", true));
store.insert("b".into(), make_target("b", "Bob", true));
let ctx = CellReportContext::new(registry.clone());
let report = ctx.report(OnlineTargetReport { threshold: 2 });
assert!(report.get());
let report = ctx.report(OnlineTargetReport { threshold: 3 });
assert!(!report.get());
store.insert("c".into(), make_target("c", "Charlie", true));
assert!(report.get());
}
}