use std::convert::Infallible;
use std::sync::{Arc, Mutex, MutexGuard};
use crate::analysis::index::{AnalysisContext, SharedAnalysis};
use crate::analysis::{
BetweennessCentrality, CondensationGraph, DeclaredModulePartition, DegreeCentrality,
GraphAnalysisError, GraphAnalysisLimits, GraphComponents, GraphDirection, GraphDivergence,
GraphEdgeSelection, GraphNeighbor, GraphPath, GraphSubgraph, LayoutAssistMetadata, betweenness,
components, degree, divergence, layout, traversal,
};
use crate::graph::CodeGraph;
use crate::id::GraphNodeId;
use super::graph::CachedGraphState;
use super::limits::GraphCacheLimits;
use super::product::{Product, ProductClaim, ProductOperation, ProductStore};
use super::state::{CacheCategory, CacheCounters, CacheEvent};
use super::storage::BoundedStore;
struct Derived {
indexes: BoundedStore<GraphEdgeSelection, SharedAnalysis>,
products: ProductStore,
}
impl Derived {
fn new(limits: GraphCacheLimits) -> Self {
Self {
indexes: BoundedStore::new(limits.max_selected_indexes_per_graph()),
products: ProductStore::new(limits.max_derived_products_per_graph()),
}
}
fn clear(&mut self) {
self.indexes.clear();
self.products.clear();
}
}
pub(crate) struct DerivedState {
retained: Mutex<Derived>,
counters: Arc<CacheCounters>,
}
impl DerivedState {
pub(crate) fn new(limits: GraphCacheLimits, counters: Arc<CacheCounters>) -> Self {
Self {
retained: Mutex::new(Derived::new(limits)),
counters,
}
}
pub(crate) fn clear(&self) {
self.locked().clear();
}
pub(crate) fn indexed(
&self,
graph: &CodeGraph,
selection: GraphEdgeSelection,
admitted: (u32, u32),
) -> Result<SharedAnalysis, GraphAnalysisError> {
let mut retained = self.locked();
match retained.indexes.get(&selection) {
Some(held) => {
self.count(CacheCategory::SelectedIndex, CacheEvent::Hit, 1);
Ok(held)
}
None => {
self.count(CacheCategory::SelectedIndex, CacheEvent::Miss, 1);
let built = SharedAnalysis::from_admitted(graph, selection, admitted)?;
let evicted = retained.indexes.retain(selection, built.clone());
self.count(CacheCategory::SelectedIndex, CacheEvent::Eviction, evicted);
Ok(built)
}
}
}
pub(crate) fn product<T: Product, E>(
&self,
claim: ProductClaim,
derive: impl FnOnce() -> Result<T, E>,
) -> Result<T, E> {
let mut retained = self.locked();
let found = retained.products.get(&claim).and_then(T::read);
match found {
Some(answer) => {
self.count(CacheCategory::DerivedProduct, CacheEvent::Hit, 1);
Ok(answer)
}
None => {
self.count(CacheCategory::DerivedProduct, CacheEvent::Miss, 1);
let answer = derive()?;
let evicted = retained.products.retain(claim, answer.clone().state());
self.count(CacheCategory::DerivedProduct, CacheEvent::Eviction, evicted);
Ok(answer)
}
}
}
pub(crate) fn counters(&self) -> &CacheCounters {
&self.counters
}
fn count(&self, category: CacheCategory, event: CacheEvent, amount: u64) {
self.counters.record(category, event, amount);
}
fn locked(&self) -> MutexGuard<'_, Derived> {
match self.retained.lock() {
Ok(held) => held,
Err(poisoned) => {
let mut discarded = poisoned.into_inner();
discarded.clear();
self.retained.clear_poison();
discarded
}
}
}
}
#[derive(Clone)]
pub struct CachedGraphAnalysis {
state: Arc<CachedGraphState>,
selection: GraphEdgeSelection,
limits: GraphAnalysisLimits,
shared: SharedAnalysis,
}
impl CachedGraphAnalysis {
pub(crate) fn new(
state: Arc<CachedGraphState>,
selection: GraphEdgeSelection,
limits: GraphAnalysisLimits,
shared: SharedAnalysis,
) -> Self {
Self {
state,
selection,
limits,
shared,
}
}
pub fn graph(&self) -> &CodeGraph {
self.state.graph()
}
pub fn selection(&self) -> GraphEdgeSelection {
self.selection
}
pub fn limits(&self) -> GraphAnalysisLimits {
self.limits
}
pub fn declared_partition(&self) -> &DeclaredModulePartition {
self.shared.partition()
}
pub fn neighbors(
&self,
node: GraphNodeId,
depth: u32,
direction: GraphDirection,
) -> Result<Arc<[GraphNeighbor]>, GraphAnalysisError> {
self.admits_walk(node, depth)?;
self.product(
ProductOperation::Neighbors {
node,
depth,
direction,
},
|context| traversal::neighbors(context, node, depth, direction).map(Arc::from),
)
}
pub fn path(
&self,
source: GraphNodeId,
target: GraphNodeId,
) -> Result<Option<Arc<GraphPath>>, GraphAnalysisError> {
traversal::admitted_route(self.shared.indexes(), (source, target))?;
self.product(ProductOperation::Path { source, target }, |context| {
traversal::path(context, (source, target)).map(|found| found.map(Arc::new))
})
}
pub fn subgraph(
&self,
seed: GraphNodeId,
depth: u32,
direction: GraphDirection,
) -> Result<Arc<GraphSubgraph>, GraphAnalysisError> {
self.admits_walk(seed, depth)?;
self.product(
ProductOperation::Subgraph {
seed,
depth,
direction,
},
|context| traversal::subgraph(context, seed, depth, direction).map(Arc::new),
)
}
pub fn degree_centrality(&self) -> Arc<[DegreeCentrality]> {
settled(self.product(ProductOperation::Degree, |context| {
Ok(Arc::from(degree::degree(context)))
}))
}
pub fn betweenness_centrality(
&self,
) -> Result<Arc<[BetweennessCentrality]>, GraphAnalysisError> {
self.admits_centrality_work()?;
self.product(ProductOperation::Betweenness, |context| {
betweenness::betweenness(context).map(Arc::from)
})
}
pub fn strongly_connected_components(&self) -> Arc<GraphComponents> {
settled(self.product(ProductOperation::Components, |context| {
Ok(Arc::new(components::discover(context)))
}))
}
pub fn condensation(&self) -> Arc<CondensationGraph> {
settled(self.product(ProductOperation::Condensation, |context| {
Ok(Arc::new(components::condense(context)))
}))
}
pub fn divergence(&self) -> Arc<GraphDivergence> {
settled(self.product(ProductOperation::Divergence, |context| {
Ok(Arc::new(divergence::divergence(context)))
}))
}
pub fn layout_assist(&self) -> Result<Arc<LayoutAssistMetadata>, GraphAnalysisError> {
self.admits_centrality_work()?;
self.product(ProductOperation::Layout, |context| {
layout::layout_assist(context).map(Arc::new)
})
}
fn product<T: Product, E>(
&self,
operation: ProductOperation,
derive: impl FnOnce(&AnalysisContext<'_>) -> Result<T, E>,
) -> Result<T, E> {
let claim = ProductClaim::new(self.selection, operation);
self.derived().product(claim, || derive(&self.context()))
}
fn derived(&self) -> &DerivedState {
self.state.derived()
}
fn admits_walk(&self, node: GraphNodeId, depth: u32) -> Result<(), GraphAnalysisError> {
traversal::admitted_walk(self.shared.indexes(), self.limits, node, depth)
}
fn admits_centrality_work(&self) -> Result<(), GraphAnalysisError> {
betweenness::admitted_work(self.shared.indexes(), self.limits)
}
fn context(&self) -> AnalysisContext<'_> {
self.shared.context(self.state.graph(), self.limits)
}
}
fn settled<T>(answer: Result<T, Infallible>) -> T {
answer.unwrap_or_else(|never| match never {})
}