use std::collections::HashSet;
use crate::plugin::{Plugin, PluginGroup, PluginGroupAdapter};
use crate::resource::Resources;
pub struct Engine {
resources: Resources,
plugin_names: HashSet<&'static str>,
}
impl Engine {
pub fn builder() -> EngineBuilder {
EngineBuilder::new()
}
pub fn resources(&self) -> &Resources {
&self.resources
}
pub fn resources_mut(&mut self) -> &mut Resources {
&mut self.resources
}
pub fn get<R: crate::resource::Resource>(&self) -> Option<&R> {
self.resources.get::<R>()
}
pub fn get_mut<R: crate::resource::Resource>(&mut self) -> Option<&mut R> {
self.resources.get_mut::<R>()
}
pub fn has_plugin(&self, name: &str) -> bool {
self.plugin_names.contains(name)
}
pub fn plugin_names(&self) -> impl Iterator<Item = &'static str> + '_ {
self.plugin_names.iter().copied()
}
}
impl Default for Engine {
fn default() -> Self {
EngineBuilder::new().build()
}
}
pub struct EngineBuilder {
plugins: Vec<Box<dyn Plugin>>,
resources: Resources,
}
impl EngineBuilder {
pub fn new() -> Self {
Self {
plugins: Vec::new(),
resources: Resources::new(),
}
}
pub fn add_plugin(mut self, plugin: impl Plugin + 'static) -> Self {
self.plugins.push(Box::new(plugin));
self
}
pub fn add_plugins(mut self, group: impl PluginGroup) -> Self {
let adapter = PluginGroupAdapter::new(group);
for plugin in adapter.into_plugins() {
self.plugins.push(plugin);
}
self
}
pub fn insert_resource<R: crate::resource::Resource>(mut self, resource: R) -> Self {
self.resources.insert(resource);
self
}
pub fn build(mut self) -> Engine {
let sorted_indices = self.sort_plugins_by_dependency_indices();
let mut plugin_names = HashSet::new();
for &idx in &sorted_indices {
let plugin = &self.plugins[idx];
tracing::debug!("Building plugin: {}", plugin.name());
plugin.build(&mut self.resources);
plugin_names.insert(plugin.name());
}
for &idx in &sorted_indices {
self.plugins[idx].finish(&mut self.resources);
}
tracing::info!(
"Engine built with {} plugins: {:?}",
plugin_names.len(),
plugin_names
);
Engine {
resources: self.resources,
plugin_names,
}
}
fn sort_plugins_by_dependency_indices(&self) -> Vec<usize> {
let mut sorted = Vec::new();
let mut visited = HashSet::new();
let mut visiting = HashSet::new();
let plugin_map: std::collections::HashMap<_, _> = self
.plugins
.iter()
.enumerate()
.map(|(i, p)| (p.name(), i))
.collect();
fn visit(
name: &'static str,
plugins: &[Box<dyn Plugin>],
plugin_map: &std::collections::HashMap<&'static str, usize>,
visited: &mut HashSet<&'static str>,
visiting: &mut HashSet<&'static str>,
sorted: &mut Vec<usize>,
) {
if visited.contains(name) {
return;
}
if visiting.contains(name) {
tracing::warn!("Circular plugin dependency detected involving: {}", name);
return;
}
if let Some(&idx) = plugin_map.get(name) {
visiting.insert(name);
for dep in plugins[idx].dependencies() {
visit(dep, plugins, plugin_map, visited, visiting, sorted);
}
visiting.remove(name);
visited.insert(name);
sorted.push(idx);
}
}
for plugin in &self.plugins {
visit(
plugin.name(),
&self.plugins,
&plugin_map,
&mut visited,
&mut visiting,
&mut sorted,
);
}
sorted
}
}
impl Default for EngineBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::plugin::FnPlugin;
#[test]
fn test_engine_builder() {
let engine = EngineBuilder::new()
.add_plugin(FnPlugin::new("test", |resources| {
resources.insert(42i32);
}))
.build();
assert_eq!(*engine.get::<i32>().unwrap(), 42);
assert!(engine.has_plugin("test"));
}
#[test]
fn test_insert_resource() {
let engine = EngineBuilder::new()
.insert_resource("pre-inserted".to_string())
.build();
assert_eq!(engine.get::<String>().unwrap(), "pre-inserted");
}
#[test]
fn test_plugin_order() {
struct FirstPlugin;
impl Plugin for FirstPlugin {
fn name(&self) -> &'static str {
"FirstPlugin"
}
fn build(&self, resources: &mut Resources) {
resources.insert(vec!["first"]);
}
}
struct SecondPlugin;
impl Plugin for SecondPlugin {
fn name(&self) -> &'static str {
"SecondPlugin"
}
fn dependencies(&self) -> &[&'static str] {
&["FirstPlugin"]
}
fn build(&self, resources: &mut Resources) {
if let Some(v) = resources.get_mut::<Vec<&'static str>>() {
v.push("second");
}
}
}
let engine = EngineBuilder::new()
.add_plugin(SecondPlugin)
.add_plugin(FirstPlugin)
.build();
let order = engine.get::<Vec<&'static str>>().unwrap();
assert_eq!(order, &vec!["first", "second"]);
}
#[test]
fn test_default_engine() {
let engine = Engine::default();
assert!(engine.resources().is_empty());
}
}