use std::any::Any;
use std::collections::{HashMap, HashSet};
use std::sync::{Arc, Mutex, Weak};
use crate::diagnostics::{BindingDiagnostics, DependencyStatus};
use crate::error::CordisError;
use crate::fiber::{FiberInner, FiberState, Intent, PluginId};
use crate::key::{ScopeId, TypeKey};
pub(crate) type CheckFn = Arc<dyn Fn() -> bool + Send + Sync>;
#[derive(Clone)]
pub(crate) struct StoredValue(Arc<Box<dyn Any + Send + Sync>>);
impl StoredValue {
pub(crate) fn new<T: ?Sized + Send + Sync + 'static>(value: Arc<T>) -> Self {
Self(Arc::new(Box::new(value) as Box<dyn Any + Send + Sync>))
}
pub(crate) fn downcast<T: ?Sized + Send + Sync + 'static>(&self) -> Option<Arc<T>> {
(**self.0).downcast_ref::<Arc<T>>().cloned()
}
}
pub(crate) struct Binding {
pub value: ValueSlot,
pub provider: Weak<FiberInner>,
pub provider_id: PluginId,
pub provider_gen: u64,
pub check: Option<CheckFn>,
pub check_status: Mutex<Option<DependencyStatus>>,
pub removing: std::sync::atomic::AtomicBool,
}
pub(crate) enum ValueSlot {
Fixed(StoredValue),
Mutable(Mutex<StoredValue>),
}
impl ValueSlot {
pub(crate) fn snapshot(&self) -> StoredValue {
match self {
Self::Fixed(value) => value.clone(),
Self::Mutable(value) => value.lock().unwrap().clone(),
}
}
fn replace_mutable(&self, value: StoredValue) -> Option<StoredValue> {
match self {
Self::Fixed(_) => None,
Self::Mutable(slot) => {
let old = std::mem::replace(&mut *slot.lock().unwrap(), value);
Some(old)
}
}
}
}
pub(crate) struct Registry {
bindings: Mutex<HashMap<(TypeKey, Option<ScopeId>), Arc<Binding>>>,
inject_index: Mutex<HashMap<TypeKey, Vec<Weak<FiberInner>>>>,
}
fn shrink_if_sparse<K: Eq + std::hash::Hash, V>(map: &mut HashMap<K, V>) {
if map.capacity() > 64 && map.len() * 4 < map.capacity() {
map.shrink_to_fit();
}
}
impl Registry {
pub(crate) fn new() -> Self {
Self {
bindings: Mutex::new(HashMap::new()),
inject_index: Mutex::new(HashMap::new()),
}
}
#[cfg(test)]
pub(crate) fn table_counts(&self) -> (usize, usize) {
(
self.bindings.lock().unwrap().len(),
self.inject_index.lock().unwrap().len(),
)
}
pub(crate) fn insert_binding(
&self,
key: TypeKey,
scope: Option<ScopeId>,
binding: Binding,
) -> Result<Arc<Binding>, CordisError> {
let mut bindings = self.bindings.lock().unwrap();
let entry = (key.clone(), scope.clone());
if let Some(existing) = bindings.get(&entry) {
if !existing.removing.load(std::sync::atomic::Ordering::SeqCst) {
let scope_desc = entry.1.as_deref().unwrap_or("<default>");
return Err(CordisError::ServiceExists(format!(
"{} in scope {scope_desc}",
key.describe()
)));
}
}
let stored = Arc::new(binding);
bindings.insert(entry, stored.clone());
Ok(stored)
}
pub(crate) fn lookup(&self, key: &TypeKey, scope: Option<&ScopeId>) -> Option<Arc<Binding>> {
let bindings = self.bindings.lock().unwrap();
bindings.get(&(key.clone(), scope.cloned())).cloned()
}
pub(crate) fn replace_mutable_if_current(
&self,
key: &TypeKey,
scope: Option<&ScopeId>,
expected: &Arc<Binding>,
value: StoredValue,
) -> Result<StoredValue, StoredValue> {
let bindings = self.bindings.lock().unwrap();
let current = bindings.get(&(key.clone(), scope.cloned()));
let Some(current) = current else {
return Err(value);
};
if !Arc::ptr_eq(current, expected)
|| current.removing.load(std::sync::atomic::Ordering::SeqCst)
{
return Err(value);
}
match ¤t.value {
ValueSlot::Mutable(_) => {
Ok(current.value.replace_mutable(value).unwrap())
}
ValueSlot::Fixed(_) => Err(value),
}
}
pub(crate) fn mark_removing_if(
&self,
key: &TypeKey,
scope: Option<&ScopeId>,
expected: &Arc<Binding>,
) {
let bindings = self.bindings.lock().unwrap();
if bindings
.get(&(key.clone(), scope.cloned()))
.is_some_and(|current| Arc::ptr_eq(current, expected))
{
expected
.removing
.store(true, std::sync::atomic::Ordering::SeqCst);
}
}
pub(crate) fn bindings_snapshot(&self) -> Vec<BindingDiagnostics> {
self.bindings
.lock()
.unwrap()
.iter()
.map(|((key, scope), binding)| BindingDiagnostics {
key: key.clone(),
scope: scope.as_ref().map(|s| s.to_string()),
provider: binding.provider_id,
generation: binding.provider_gen,
removing: binding.removing.load(std::sync::atomic::Ordering::SeqCst),
})
.collect()
}
pub(crate) fn dependency_status(
&self,
key: &TypeKey,
scope: Option<&ScopeId>,
) -> DependencyStatus {
let Some(binding) = self.lookup(key, scope) else {
return DependencyStatus::Missing;
};
Self::binding_status(&binding)
}
pub(crate) fn binding_status(binding: &Binding) -> DependencyStatus {
if binding.removing.load(std::sync::atomic::Ordering::SeqCst) {
return DependencyStatus::Removing;
}
let Some(provider) = binding.provider.upgrade() else {
return DependencyStatus::Missing;
};
let state = provider.state();
if state != FiberState::Active {
return DependencyStatus::ProviderInactive(state);
}
if binding.check.is_some() {
return binding
.check_status
.lock()
.unwrap()
.unwrap_or(DependencyStatus::CheckPending);
}
DependencyStatus::Ready
}
pub(crate) fn finalize_binding_if(
&self,
key: TypeKey,
scope: Option<ScopeId>,
expected: &Arc<Binding>,
) {
let mut bindings = self.bindings.lock().unwrap();
let still_old = bindings
.get(&(key.clone(), scope.clone()))
.is_some_and(|b| Arc::ptr_eq(b, expected));
if still_old {
bindings.remove(&(key, scope));
shrink_if_sparse(&mut bindings);
}
}
pub(crate) fn consumers_of(
&self,
key: &TypeKey,
quad: (PluginId, u64, TypeKey, Option<ScopeId>),
) -> Vec<Arc<FiberInner>> {
let index = self.inject_index.lock().unwrap();
let Some(list) = index.get(key) else {
return Vec::new();
};
list.iter()
.filter_map(|weak| {
let fiber = weak.upgrade()?;
let bound = fiber
.last_deps
.lock()
.unwrap()
.as_ref()
.is_some_and(|deps| deps.contains(&quad));
bound.then_some(fiber)
})
.collect()
}
pub(crate) fn register_inject(&self, key: TypeKey, fiber: &Arc<FiberInner>) {
self.inject_index
.lock()
.unwrap()
.entry(key)
.or_default()
.push(Arc::downgrade(fiber));
}
pub(crate) fn unregister_injects(&self, fiber: &Arc<FiberInner>, keys: &[TypeKey]) {
let weak = Arc::downgrade(fiber);
let mut index = self.inject_index.lock().unwrap();
for key in keys {
let mut empty = false;
if let Some(list) = index.get_mut(key) {
list.retain(|w| !Weak::ptr_eq(w, &weak));
empty = list.is_empty();
}
if empty {
index.remove(key);
}
}
shrink_if_sparse(&mut index);
}
pub(crate) fn notify_key_changed(&self, key: &TypeKey) {
let fibers: Vec<Arc<FiberInner>> = {
let index = self.inject_index.lock().unwrap();
index
.get(key)
.map(|list| list.iter().filter_map(|w| w.upgrade()).collect())
.unwrap_or_default()
};
for fiber in fibers {
fiber.post(Intent::RefreshDeps);
}
}
pub(crate) fn refresh_all(&self) {
let mut seen: HashSet<PluginId> = HashSet::new();
let index = self.inject_index.lock().unwrap();
for list in index.values() {
for weak in list {
if let Some(fiber) = weak.upgrade() {
if seen.insert(fiber.id) {
fiber.post(Intent::RefreshDeps);
}
}
}
}
}
pub(crate) fn resolve_dep(
&self,
key: &TypeKey,
scope: Option<&ScopeId>,
) -> Option<(PluginId, u64)> {
let binding = self.lookup(key, scope)?;
if binding.removing.load(std::sync::atomic::Ordering::SeqCst) {
return None;
}
let provider = binding.provider.upgrade()?;
if provider.state() != FiberState::Active {
return None;
}
if let Some(check) = &binding.check {
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| check()));
let passed = result.as_ref().copied().unwrap_or(false);
*binding.check_status.lock().unwrap() = Some(match result {
Ok(true) => DependencyStatus::Ready,
Ok(false) => DependencyStatus::CheckRejected,
Err(_) => DependencyStatus::CheckPanicked,
});
if !passed {
return None;
}
}
Some((binding.provider_id, binding.provider_gen))
}
}
#[cfg(test)]
mod transient_tests;