use crate::context::ResourceContext;
use crate::error::{IssunError, Result};
use crate::plugin::{Plugin, PluginBuilder};
use crate::service::Service;
use crate::system::System;
use std::any::TypeId;
use std::collections::{HashMap, HashSet};
pub struct GameBuilder {
plugins: Vec<Box<dyn Plugin>>,
plugin_names: HashSet<String>,
runtime_resources: HashMap<TypeId, Box<dyn RuntimeResourceEntry>>,
extra_services: Vec<Box<dyn Service>>,
extra_systems: Vec<Box<dyn System>>,
}
impl GameBuilder {
pub fn new() -> Self {
Self {
plugins: Vec::new(),
plugin_names: HashSet::new(),
runtime_resources: HashMap::new(),
extra_services: Vec::new(),
extra_systems: Vec::new(),
}
}
pub fn with_plugin(mut self, plugin: impl Plugin + 'static) -> Result<Self> {
let name = plugin.name().to_string();
if self.plugin_names.contains(&name) {
return Err(IssunError::Plugin(format!(
"Plugin '{}' already registered",
name
)));
}
self.plugin_names.insert(name);
self.plugins.push(Box::new(plugin));
Ok(self)
}
pub fn with_resource<T: 'static + Send + Sync>(mut self, resource: T) -> Self {
self.runtime_resources
.insert(TypeId::of::<T>(), Box::new(resource));
self
}
pub fn with_service(mut self, service: impl Service + 'static) -> Self {
self.extra_services.push(Box::new(service));
self
}
pub fn with_system(mut self, system: impl System + 'static) -> Self {
self.extra_systems.push(Box::new(system));
self
}
#[allow(deprecated)]
pub async fn build(mut self) -> Result<Game> {
for plugin in &mut self.plugins {
plugin.initialize().await;
}
let sorted_indices = self.resolve_dependency_order()?;
let mut plugin_builder = DefaultPluginBuilder::new();
for idx in sorted_indices {
self.plugins[idx].build(&mut plugin_builder);
}
let DefaultPluginBuilder {
entities,
services: plugin_services,
systems: plugin_systems,
assets,
resources: plugin_resources,
runtime_resources: plugin_runtime_resources,
} = plugin_builder;
let mut all_services = plugin_services;
all_services.extend(self.extra_services.into_iter());
let mut all_systems = plugin_systems;
all_systems.extend(self.extra_systems.into_iter());
let mut context = crate::context::Context::new();
let mut resource_context = crate::context::ResourceContext::new();
resource_context.insert(crate::event::EventBus::new());
let mut service_context = crate::context::ServiceContext::new();
let mut system_context = crate::context::SystemContext::new();
for service in all_services {
let cloned = service.as_ref().clone_box();
context.register_service(service);
service_context.register(cloned);
}
for system in all_systems {
system_context.register_boxed(system);
}
for (_, entry) in plugin_runtime_resources.into_iter() {
entry.insert(&mut resource_context);
}
for (_, entry) in self.runtime_resources.into_iter() {
entry.insert(&mut resource_context);
}
for (type_id, boxed) in plugin_resources.into_inner().into_iter() {
resource_context.insert_boxed(type_id, boxed);
}
Ok(Game {
resources: resource_context,
services: service_context,
systems: system_context,
entities,
assets,
})
}
fn resolve_dependency_order(&self) -> Result<Vec<usize>> {
let mut sorted_indices = Vec::new();
let mut visited = HashSet::new();
let mut visiting = HashSet::new();
for (idx, _) in self.plugins.iter().enumerate() {
self.visit_plugin_index(idx, &mut visited, &mut visiting, &mut sorted_indices)?;
}
Ok(sorted_indices)
}
fn visit_plugin_index(
&self,
idx: usize,
visited: &mut HashSet<usize>,
visiting: &mut HashSet<usize>,
sorted: &mut Vec<usize>,
) -> Result<()> {
if visited.contains(&idx) {
return Ok(());
}
if visiting.contains(&idx) {
let name = self.plugins[idx].name().to_string();
return Err(IssunError::CircularDependency(vec![name]));
}
visiting.insert(idx);
let plugin = &self.plugins[idx];
let name = plugin.name().to_string();
for dep_name in plugin.dependencies() {
let dep_idx = self
.plugins
.iter()
.position(|p| p.name() == dep_name)
.ok_or_else(|| IssunError::PluginDependency {
plugin: name.clone(),
dependency: dep_name.to_string(),
})?;
self.visit_plugin_index(dep_idx, visited, visiting, sorted)?;
}
visiting.remove(&idx);
visited.insert(idx);
sorted.push(idx);
Ok(())
}
}
impl Default for GameBuilder {
fn default() -> Self {
Self::new()
}
}
pub trait RuntimeResourceEntry: Send {
fn insert(self: Box<Self>, ctx: &mut ResourceContext);
}
impl<T: 'static + Send + Sync> RuntimeResourceEntry for T {
fn insert(self: Box<Self>, ctx: &mut ResourceContext) {
ctx.insert(*self);
}
}
struct DefaultPluginBuilder {
entities: HashMap<String, Box<dyn crate::entity::Entity>>,
services: Vec<Box<dyn crate::service::Service>>,
systems: Vec<Box<dyn crate::system::System>>,
assets: HashMap<String, Box<dyn std::any::Any + Send + Sync>>,
resources: crate::resources::Resources,
runtime_resources: HashMap<TypeId, Box<dyn RuntimeResourceEntry>>,
}
impl DefaultPluginBuilder {
fn new() -> Self {
Self {
entities: HashMap::new(),
services: Vec::new(),
systems: Vec::new(),
assets: HashMap::new(),
resources: crate::resources::Resources::default(),
runtime_resources: HashMap::new(),
}
}
}
impl PluginBuilder for DefaultPluginBuilder {
fn register_entity(&mut self, name: &str, entity: Box<dyn crate::entity::Entity>) {
self.entities.insert(name.to_string(), entity);
}
fn register_service(&mut self, service: Box<dyn crate::service::Service>) {
self.services.push(service);
}
fn register_system(&mut self, system: Box<dyn crate::system::System>) {
self.systems.push(system);
}
fn register_runtime_resource_boxed(
&mut self,
type_id: TypeId,
resource: Box<dyn RuntimeResourceEntry>,
) {
self.runtime_resources.insert(type_id, resource);
}
fn register_asset(&mut self, name: &str, asset: Box<dyn std::any::Any + Send + Sync>) {
self.assets.insert(name.to_string(), asset);
}
fn resources_mut(&mut self) -> &mut crate::resources::Resources {
&mut self.resources
}
}
pub struct Game {
pub resources: crate::context::ResourceContext,
pub services: crate::context::ServiceContext,
pub systems: crate::context::SystemContext,
pub entities: HashMap<String, Box<dyn crate::entity::Entity>>,
pub assets: HashMap<String, Box<dyn std::any::Any + Send + Sync>>,
}
impl Game {
pub fn resources(&self) -> &crate::context::ResourceContext {
&self.resources
}
pub fn resources_mut(&mut self) -> &mut crate::context::ResourceContext {
&mut self.resources
}
pub fn services(&self) -> &crate::context::ServiceContext {
&self.services
}
pub fn systems(&self) -> &crate::context::SystemContext {
&self.systems
}
pub fn systems_mut(&mut self) -> &mut crate::context::SystemContext {
&mut self.systems
}
pub fn run(self) -> Result<()> {
println!(
"Game running with {} services and {} systems...",
self.services.len(),
self.systems.len()
);
Ok(())
}
}