use parking_lot::{MutexGuard, RwLockReadGuard, RwLockWriteGuard};
use std::{
any::Any,
cell::OnceCell,
collections::{HashMap, HashSet},
};
use crate::{
Capsule, CapsuleId, CapsuleManager, CreateCapsuleId, SideEffectTxnOrchestrator,
EXCLUSIVE_OWNER_MSG,
};
#[allow(clippy::module_name_repetitions)] pub struct ContainerReadTxn<'a> {
pub(crate) data: RwLockReadGuard<'a, HashMap<CapsuleId, Box<dyn Any + Send + Sync>>>,
}
impl<'a> ContainerReadTxn<'a> {
pub(crate) fn new(
data: RwLockReadGuard<'a, HashMap<CapsuleId, Box<dyn Any + Send + Sync>>>,
) -> Self {
Self { data }
}
}
impl ContainerReadTxn<'_> {
#[must_use]
pub fn try_read<C: Capsule>(&self, capsule: &C) -> Option<C::Data>
where
C::Data: Clone,
{
self.try_read_ref(capsule).cloned()
}
#[must_use]
pub fn try_read_ref<C: Capsule>(&self, capsule: &C) -> Option<&C::Data> {
self.data
.get(&capsule.id())
.map(crate::downcast_capsule_data::<C>)
}
}
#[allow(clippy::module_name_repetitions)] pub struct ContainerWriteTxn<'a> {
pub(crate) side_effect_txn_orchestrator: SideEffectTxnOrchestrator,
pub(crate) data: RwLockWriteGuard<'a, HashMap<CapsuleId, Box<dyn Any + Send + Sync>>>,
nodes: MutexGuard<'a, HashMap<CapsuleId, CapsuleManager>>,
}
impl<'a> ContainerWriteTxn<'a> {
pub(crate) fn new(
data: RwLockWriteGuard<'a, HashMap<CapsuleId, Box<dyn Any + Send + Sync>>>,
nodes: MutexGuard<'a, HashMap<CapsuleId, CapsuleManager>>,
side_effect_txn_orchestrator: SideEffectTxnOrchestrator,
) -> Self {
Self {
side_effect_txn_orchestrator,
data,
nodes,
}
}
pub(crate) fn downgrade(self) -> ContainerReadTxn<'a> {
ContainerReadTxn::new(RwLockWriteGuard::downgrade(self.data))
}
}
impl ContainerWriteTxn<'_> {
pub fn read_or_init<C: Capsule>(&mut self, capsule: C) -> C::Data
where
C::Data: Clone,
{
self.read_or_init_ref(capsule).clone()
}
#[allow(clippy::missing_panics_doc)] pub fn read_or_init_ref<C: Capsule>(&mut self, capsule: C) -> &C::Data {
let id = capsule.id();
self.ensure_initialized(capsule);
self.try_read_ref_raw::<C>(&id)
.expect("Ensured capsule was initialized above")
}
#[must_use]
pub fn try_read<C: Capsule>(&self, capsule: &C) -> Option<C::Data>
where
C::Data: Clone,
{
self.try_read_ref::<C>(capsule).cloned()
}
#[must_use]
pub fn try_read_ref<C: Capsule>(&self, capsule: &C) -> Option<&C::Data> {
self.try_read_ref_raw::<C>(&capsule.id())
}
pub(crate) fn try_read_ref_raw<C: Capsule>(&self, id: &CapsuleId) -> Option<&C::Data> {
self.data.get(id).map(crate::downcast_capsule_data::<C>)
}
pub(crate) fn ensure_initialized<C: Capsule>(&mut self, capsule: C) {
let id = capsule.id();
if let std::collections::hash_map::Entry::Vacant(e) =
self.nodes.entry(CapsuleId::clone(&id))
{
#[cfg(feature = "logging")]
log::debug!("Initializing {} ({:?})", std::any::type_name::<C>(), id);
e.insert(CapsuleManager::new(capsule));
self.build_single_node(&id);
}
}
pub(crate) fn dispose_node(&mut self, id: &CapsuleId) {
self.data.remove(id);
self.nodes
.remove(id)
.expect("Node should be in graph")
.dependencies
.iter()
.for_each(|dep| {
self.node_or_panic(dep).dependents.remove(id);
});
}
pub(crate) fn add_dependency_relationship(
&mut self,
dependency: &CapsuleId,
dependent: &CapsuleId,
) {
self.node_or_panic(dependency)
.dependents
.insert(CapsuleId::clone(dependent));
self.node_or_panic(dependent)
.dependencies
.insert(CapsuleId::clone(dependency));
}
pub(crate) fn take_capsule_and_side_effect(
&mut self,
id: &CapsuleId,
) -> (Box<dyn Any + Send>, OnceCell<Box<dyn Any + Send>>) {
let node = self.node_or_panic(id);
let capsule = node.capsule.take().expect(EXCLUSIVE_OWNER_MSG);
let side_effect = node.side_effect.take().expect(EXCLUSIVE_OWNER_MSG);
(capsule, side_effect)
}
pub(crate) fn yield_capsule_and_side_effect(
&mut self,
id: &CapsuleId,
capsule: Box<dyn Any + Send>,
side_effect: OnceCell<Box<dyn Any + Send>>,
) {
let node = self.node_or_panic(id);
assert!(
node.capsule.is_none() && node.side_effect.is_none(),
"Manager had ownership over a capsule and side effect when ownership was yielded back",
);
node.capsule = Some(capsule);
node.side_effect = Some(side_effect);
}
pub(crate) fn build_capsules_or_panic(&mut self, ids: &HashSet<CapsuleId>) {
let build_order_stack = self.create_build_order_stack(ids);
let disposable_nodes = self.get_disposable_nodes_from_build_order_stack(&build_order_stack);
let mut changed_nodes = HashSet::new();
for curr_id in build_order_stack.into_iter().rev() {
let node = self.node_or_panic(&curr_id);
let build_is_required = ids.contains(&curr_id);
let have_deps_changed = node
.dependencies
.iter()
.any(|dep| changed_nodes.contains(dep));
if !build_is_required && !have_deps_changed {
continue;
}
if disposable_nodes.contains(&curr_id) {
self.dispose_single_node(&curr_id);
changed_nodes.insert(curr_id);
} else {
let did_node_change = self.build_single_node(&curr_id);
if did_node_change {
changed_nodes.insert(curr_id);
}
}
}
}
fn node(&mut self, id: &CapsuleId) -> Option<&mut CapsuleManager> {
self.nodes.get_mut(id)
}
fn node_or_panic(&mut self, id: &CapsuleId) -> &mut CapsuleManager {
self.node(id).expect("Node should be in graph")
}
fn build_single_node(&mut self, id: &CapsuleId) -> bool {
let node = self.node_or_panic(id);
let old_deps = core::mem::take(&mut node.dependencies);
for dep in old_deps {
self.node_or_panic(&dep).dependents.remove(id);
}
(self.node_or_panic(id).build)(CapsuleId::clone(id), self)
}
fn dispose_single_node(&mut self, id: &CapsuleId) {
self.data.remove(id);
self.nodes
.remove(id)
.expect("Node should be in graph")
.dependencies
.iter()
.for_each(|dep| {
if let Some(node) = self.node(dep) {
node.dependents.remove(id);
}
});
}
fn create_build_order_stack(&mut self, start: &HashSet<CapsuleId>) -> Vec<CapsuleId> {
let mut to_visit_stack = start
.iter()
.cloned()
.map(|id| (false, id))
.collect::<Vec<_>>();
let mut visited = HashSet::new();
let mut build_order_stack = Vec::new();
while let Some((has_visited_before, node)) = to_visit_stack.pop() {
if has_visited_before {
build_order_stack.push(node);
} else if !visited.contains(&node) {
visited.insert(CapsuleId::clone(&node));
to_visit_stack.push((true, CapsuleId::clone(&node))); self.node_or_panic(&node)
.dependents
.iter()
.filter(|dep| !visited.contains(*dep))
.cloned()
.for_each(|dep| to_visit_stack.push((false, dep)));
}
}
build_order_stack
}
fn get_disposable_nodes_from_build_order_stack(
&mut self,
build_order_stack: &Vec<CapsuleId>,
) -> HashSet<CapsuleId> {
let mut disposable = HashSet::new();
for id in build_order_stack {
let node = self.node_or_panic(id);
let dependents_all_disposable =
node.dependents.iter().all(|dep| disposable.contains(dep));
if node.is_idempotent() && dependents_all_disposable {
disposable.insert(CapsuleId::clone(id));
}
}
disposable
}
}