use crate::plugins::algorithm::{AlgorithmResult, FastHashMap, GraphAlgorithm, PluginContext, PluginInfo};
use crate::vgi::VgiResult;
use crate::vgi::VirtualGraph;
use std::any::Any;
pub struct PluginMetadata {
pub info: PluginInfo,
pub instance: Box<dyn Any + Send + Sync>,
}
impl PluginMetadata {
pub fn new<A: GraphAlgorithm + 'static>(info: PluginInfo, instance: A) -> Self {
Self {
info,
instance: Box::new(instance),
}
}
pub fn as_algorithm<A: GraphAlgorithm + 'static>(&self) -> Option<&A> {
self.instance.downcast_ref::<A>()
}
}
pub struct PluginRegistry {
plugins: FastHashMap<String, PluginMetadata>,
tag_index: FastHashMap<String, Vec<String>>,
}
impl Default for PluginRegistry {
fn default() -> Self {
Self::new()
}
}
impl PluginRegistry {
pub fn new() -> Self {
Self {
plugins: FastHashMap::default(),
tag_index: FastHashMap::default(),
}
}
pub fn register_algorithm<A: GraphAlgorithm + 'static>(
&mut self,
name: impl Into<String>,
algorithm: A,
) -> VgiResult<()> {
let name = name.into();
if self.plugins.contains_key(&name) {
return Err(crate::vgi::VgiError::PluginRegistrationFailed {
plugin_name: name.clone(),
reason: "Plugin with this name already exists".to_string(),
});
}
let info = algorithm.info();
for tag in &info.tags {
self.tag_index
.entry(tag.clone())
.or_default()
.push(name.clone());
}
let metadata = PluginMetadata::new(info, algorithm);
self.plugins.insert(name, metadata);
Ok(())
}
pub fn execute_by_name<G: VirtualGraph + ?Sized>(
&self,
name: &str,
_graph: &G,
ctx: &mut PluginContext<'_, G>,
) -> VgiResult<AlgorithmResult> {
let metadata =
self.plugins
.get(name)
.ok_or_else(|| crate::vgi::VgiError::PluginNotFound {
plugin_name: name.to_string(),
})?;
macro_rules! try_downcast_and_execute {
($($t:ty),*) => {
$(
if let Some(algo) = metadata.instance.downcast_ref::<$t>() {
algo.validate(ctx)?;
algo.before_execute(ctx)?;
let result = algo.execute(ctx)?;
algo.after_execute(ctx, &result)?;
return Ok(result);
}
)*
};
}
try_downcast_and_execute!(
crate::plugins::PageRankPlugin,
crate::plugins::BfsPlugin,
crate::plugins::DfsPlugin,
crate::plugins::ConnectedComponentsPlugin,
crate::plugins::DijkstraPlugin,
crate::plugins::BellmanFordPlugin,
crate::plugins::BetweennessCentralityPlugin,
crate::plugins::ClosenessCentralityPlugin,
crate::plugins::LouvainPlugin,
crate::plugins::TopologicalSortPlugin
);
Err(crate::vgi::VgiError::Internal {
message: format!(
"Failed to downcast plugin '{}' to any known algorithm type",
name
),
})
}
#[deprecated(
since = "0.6.1",
note = "Use `execute_by_name` instead for simpler API"
)]
pub fn execute<G, A>(
&self,
name: &str,
graph: &G,
ctx: &mut PluginContext<'_, G>,
) -> VgiResult<AlgorithmResult>
where
G: VirtualGraph + ?Sized,
A: GraphAlgorithm + 'static,
{
self.execute_by_name(name, graph, ctx)
}
pub fn get_metadata(&self, name: &str) -> Option<&PluginMetadata> {
self.plugins.get(name)
}
pub fn is_registered(&self, name: &str) -> bool {
self.plugins.contains_key(name)
}
pub fn list_plugins(&self) -> Vec<&String> {
self.plugins.keys().collect()
}
pub fn find_by_tag(&self, tag: &str) -> Vec<&String> {
self.tag_index
.get(tag)
.map(|names| names.iter().collect())
.unwrap_or_default()
}
pub fn len(&self) -> usize {
self.plugins.len()
}
pub fn is_empty(&self) -> bool {
self.plugins.is_empty()
}
pub fn clear(&mut self) {
self.plugins.clear();
self.tag_index.clear();
}
pub fn get_algorithm_info(&self, name: &str) -> Option<&PluginInfo> {
self.plugins.get(name).map(|m| &m.info)
}
pub fn unregister(&mut self, name: &str) -> VgiResult<Option<PluginMetadata>> {
if let Some(metadata) = self.plugins.remove(name) {
for tag in &metadata.info.tags {
if let Some(names) = self.tag_index.get_mut(tag) {
names.retain(|n| n != name);
}
}
Ok(Some(metadata))
} else {
Ok(None)
}
}
}
pub struct PluginMetadataBuilder {
name: String,
info: Option<PluginInfo>,
}
impl PluginMetadataBuilder {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
info: None,
}
}
pub fn info(mut self, info: PluginInfo) -> Self {
self.info = Some(info);
self
}
pub fn register<A: GraphAlgorithm + 'static>(
self,
registry: &mut PluginRegistry,
algorithm: A,
) -> VgiResult<()> {
let info = self
.info
.ok_or_else(|| crate::vgi::VgiError::PluginRegistrationFailed {
plugin_name: self.name.clone(),
reason: "No plugin info provided".to_string(),
})?;
let name = self.name.clone();
registry.register_algorithm(name.clone(), algorithm)?;
if let Some(metadata) = registry.plugins.get_mut(&name) {
metadata.info = info;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::Graph;
use crate::graph::traits::GraphOps;
use crate::plugins::algorithm::{AlgorithmResult, PluginInfo};
use crate::vgi::VirtualGraph;
use std::any::Any;
struct TestAlgorithm;
impl GraphAlgorithm for TestAlgorithm {
fn info(&self) -> PluginInfo {
PluginInfo::new("test", "1.0.0", "Test Algorithm").with_tags(&["test", "demo"])
}
fn execute<G>(&self, _ctx: &mut PluginContext<G>) -> VgiResult<AlgorithmResult>
where
G: VirtualGraph + ?Sized,
{
Ok(AlgorithmResult::scalar(42.0))
}
fn as_any(&self) -> &dyn Any {
self
}
}
#[test]
fn test_plugin_registry() {
let mut registry = PluginRegistry::new();
assert!(registry.is_empty());
assert_eq!(registry.len(), 0);
registry.register_algorithm("test", TestAlgorithm).unwrap();
assert!(!registry.is_empty());
assert_eq!(registry.len(), 1);
assert!(registry.is_registered("test"));
assert!(registry.get_metadata("test").is_some());
}
#[test]
fn test_plugin_find_by_tag() {
let mut registry = PluginRegistry::new();
struct TaggedAlgorithm {
tags: Vec<&'static str>,
}
impl GraphAlgorithm for TaggedAlgorithm {
fn info(&self) -> PluginInfo {
PluginInfo::new("tagged", "1.0.0", "Tagged Algorithm")
.with_tags(&self.tags.to_vec())
}
fn execute<G>(&self, _ctx: &mut PluginContext<G>) -> VgiResult<AlgorithmResult>
where
G: VirtualGraph + ?Sized,
{
Ok(AlgorithmResult::scalar(1.0))
}
fn as_any(&self) -> &dyn Any {
self
}
}
registry
.register_algorithm(
"algo1",
TaggedAlgorithm {
tags: vec!["tag1", "common"],
},
)
.unwrap();
registry
.register_algorithm(
"algo2",
TaggedAlgorithm {
tags: vec!["tag2", "common"],
},
)
.unwrap();
assert_eq!(registry.find_by_tag("tag1").len(), 1);
assert_eq!(registry.find_by_tag("common").len(), 2);
assert_eq!(registry.find_by_tag("nonexistent").len(), 0);
}
#[test]
fn test_plugin_execute() {
use crate::plugins::PageRankPlugin;
let mut registry = PluginRegistry::new();
registry
.register_algorithm("pagerank", PageRankPlugin::default())
.unwrap();
let graph = Graph::<String, f64>::directed();
let mut ctx = PluginContext::new(&graph);
let result = registry.execute_by_name("pagerank", &graph, &mut ctx);
assert!(result.is_ok());
let result = result.unwrap();
assert!(result.data.as_node_values().is_some());
}
#[test]
fn test_plugin_unregister() {
let mut registry = PluginRegistry::new();
registry.register_algorithm("test", TestAlgorithm).unwrap();
assert!(registry.is_registered("test"));
registry.unregister("test").unwrap();
assert!(!registry.is_registered("test"));
}
}