use std::any::{Any, TypeId, type_name};
use std::collections::{HashMap, HashSet};
use std::mem::take;
use std::ops::{Deref, DerefMut};
use std::sync::Mutex;
use dashmap::DashMap;
use dashmap::mapref::one::{MappedRef, MappedRefMut};
use crate::{AppError, DynPlugin, Plugin};
type ComponentBox = Box<dyn Any + Send + Sync>;
pub struct ComponentRef<'a, T>(MappedRef<'a, TypeId, ComponentBox, T>);
impl<T> Deref for ComponentRef<'_, T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
pub struct ComponentMut<'a, T>(MappedRefMut<'a, TypeId, ComponentBox, T>);
impl<T> Deref for ComponentMut<'_, T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
impl<T> DerefMut for ComponentMut<'_, T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.0
}
}
pub struct AppContext {
pub(crate) components: DashMap<TypeId, ComponentBox>,
pub(crate) plugins: DashMap<TypeId, Box<dyn DynPlugin>>,
pub(crate) pending_plugins: Mutex<Vec<TypeId>>,
}
impl AppContext {
pub fn add_component<T>(&self, component: T)
where
T: Send + Sync + 'static,
{
let type_id = TypeId::of::<T>();
if self.components.contains_key(&type_id) {
panic!("Component {} already added", type_name::<T>());
}
self.components.insert(type_id, Box::new(component));
}
pub fn get_component<T>(&self) -> Option<T>
where
T: Clone + Send + Sync + 'static,
{
self.get_component_ref::<T>().map(|r| r.clone())
}
pub fn has_component<T>(&self) -> bool
where
T: Send + Sync + 'static,
{
self.components.contains_key(&TypeId::of::<T>())
}
pub fn get_component_ref<T>(&self) -> Option<ComponentRef<'_, T>>
where
T: Send + Sync + 'static,
{
self.components
.get(&TypeId::of::<T>())
.and_then(|r| r.try_map(|v| v.downcast_ref::<T>()).ok())
.map(ComponentRef)
}
pub fn get_component_mut<T>(&self) -> Option<ComponentMut<'_, T>>
where
T: Send + Sync + 'static,
{
self.components
.get_mut(&TypeId::of::<T>())
.and_then(|r| r.try_map(|v| v.downcast_mut::<T>()).ok())
.map(ComponentMut)
}
pub fn add_plugin<T>(&self, plugin: T)
where
T: Plugin + 'static,
{
let type_id = TypeId::of::<T>();
if self.plugins.contains_key(&type_id) {
panic!("Plugin {} already added", plugin.name());
}
self.plugins.insert(type_id, Box::new(plugin));
self.pending_plugins.lock().unwrap().push(type_id);
}
pub fn has_plugin<T>(&self) -> bool
where
T: Plugin + 'static,
{
self.plugins.contains_key(&TypeId::of::<T>())
}
pub(crate) async fn build_app(self) -> Result<crate::App, AppError> {
let mut graph = HashMap::new();
let mut names: HashMap<TypeId, &'static str> = HashMap::new();
let mut used = HashMap::new();
loop {
let pending_plugins = take(&mut *self.pending_plugins.lock().unwrap());
if pending_plugins.is_empty() {
break;
}
let mut order = Vec::new();
for type_id in &pending_plugins {
let plugin = self.plugins.get(type_id).unwrap();
names.insert(*type_id, plugin.name());
let deps = plugin.dependencies();
names.extend(deps.names.iter());
graph.insert(*type_id, deps.plugins);
}
let mut ready_plugins = HashSet::new();
let mut deferred = Vec::new();
let mut stack = Vec::new();
for type_id in pending_plugins {
if used.contains_key(&type_id) {
ready_plugins.insert(type_id);
continue;
}
if topological_sort(type_id, &graph, &names, &mut order, &mut used, &mut stack)? {
ready_plugins.insert(type_id);
continue;
}
deferred.push(type_id);
}
if order.is_empty() {
let blocked = deferred
.iter()
.map(|type_id| {
let name = *names.get(type_id).unwrap_or(&"<unknown>");
let missing_deps: Vec<&'static str> = graph
.get(type_id)
.into_iter()
.flatten()
.filter(|dep_id| !used.contains_key(dep_id))
.map(|dep_id| *names.get(dep_id).unwrap_or(&"<unknown>"))
.collect();
(name, missing_deps)
})
.collect();
return Err(AppError::MissingDependency { blocked });
}
for type_id in order {
assert!(ready_plugins.remove(&type_id));
let plugin = self.plugins.get(&type_id).unwrap();
plugin.build(&self).await.map_err(AppError::PluginError)?;
}
assert!(ready_plugins.is_empty());
self.pending_plugins.lock().unwrap().extend(deferred);
}
let components = self.components.into_iter().collect::<HashMap<_, _>>();
Ok(crate::App { components })
}
}
enum DependencyStatus {
Pending,
Ready,
}
fn topological_sort(
type_id: TypeId,
graph: &HashMap<TypeId, HashSet<TypeId>>,
names: &HashMap<TypeId, &'static str>,
order: &mut Vec<TypeId>,
used: &mut HashMap<TypeId, DependencyStatus>,
stack: &mut Vec<TypeId>,
) -> Result<bool, AppError> {
let dependencies = match graph.get(&type_id) {
Some(v) => v,
None => return Ok(false),
};
stack.push(type_id);
used.insert(type_id, DependencyStatus::Pending);
for dep_type_id in dependencies {
match used.get(dep_type_id) {
Some(DependencyStatus::Pending) => {
let cycle_start = stack.iter().position(|&id| id == *dep_type_id).unwrap();
let cycle: Vec<&'static str> = stack[cycle_start..]
.iter()
.chain(std::iter::once(dep_type_id))
.map(|id| *names.get(id).unwrap_or(&"<unknown>"))
.collect();
return Err(AppError::CircularDependency { cycle });
}
Some(DependencyStatus::Ready) => continue,
None => {}
}
if !topological_sort(*dep_type_id, graph, names, order, used, stack)? {
stack.pop();
used.remove(&type_id);
return Ok(false);
}
}
stack.pop();
used.insert(type_id, DependencyStatus::Ready);
order.push(type_id);
Ok(true)
}