use std::any::Any;
use std::collections::HashMap;
use crate::engine::component::Signature;
use crate::engine::error::ECSResult;
use crate::engine::manager::ECSReference;
use crate::engine::types::{AgentTemplateId, ComponentID};
use crate::Entity;
use super::error::{AgentError, AgentResult};
use super::hooks::{DespawnBatchHook, DespawnHook, SpawnBatchHook, SpawnHook};
pub type DefaultFactory = Box<dyn Fn() -> Box<dyn Any + Send> + Send + Sync>;
pub type ColumnFactory = Box<dyn Fn(usize) -> Box<dyn Any + Send> + Send + Sync>;
pub struct AgentTemplate {
pub(crate) id: Option<AgentTemplateId>,
pub(crate) name: String,
pub(crate) signature: Signature,
pub(crate) defaults: HashMap<ComponentID, DefaultFactory>,
pub(crate) column_defaults: HashMap<ComponentID, ColumnFactory>,
pub(crate) on_spawn: Option<SpawnHook>,
pub(crate) on_despawn: Option<DespawnHook>,
pub(crate) on_spawn_batch: Option<SpawnBatchHook>,
pub(crate) on_despawn_batch: Option<DespawnBatchHook>,
pub(crate) capacity: Option<usize>,
}
impl AgentTemplate {
pub fn builder(name: impl Into<String>) -> AgentTemplateBuilder {
AgentTemplateBuilder {
name: name.into(),
signature: Signature::default(),
defaults: HashMap::new(),
column_defaults: HashMap::new(),
on_spawn: None,
on_despawn: None,
on_spawn_batch: None,
on_despawn_batch: None,
capacity: None,
}
}
pub fn spawner(&self) -> super::spawner::AgentSpawner<'_> {
super::spawner::AgentSpawner::new(self)
}
pub fn batch(&self, count: usize) -> AgentResult<super::batch::AgentBatch<'_>> {
let id = self
.id
.ok_or_else(|| AgentError::UnregisteredTemplate(self.name.clone()))?;
Ok(super::batch::AgentBatch::new(self, id, count))
}
pub fn despawn(&self, ecs: ECSReference<'_>, entity: Entity) -> ECSResult<()> {
ecs.defer(crate::engine::commands::Command::DespawnTagged {
entity,
tag: self.name.clone(),
})
}
#[inline]
pub fn name(&self) -> &str {
&self.name
}
#[inline]
pub fn signature(&self) -> &Signature {
&self.signature
}
#[inline]
pub fn has_component(&self, component_id: ComponentID) -> AgentResult<bool> {
self.signature
.try_has(component_id)
.map_err(|_| AgentError::invalid_component_id(component_id))
}
#[inline]
pub fn on_spawn(&self) -> Option<&SpawnHook> {
self.on_spawn.as_ref()
}
#[inline]
pub fn on_despawn(&self) -> Option<&DespawnHook> {
self.on_despawn.as_ref()
}
#[inline]
pub fn on_spawn_batch(&self) -> Option<&SpawnBatchHook> {
self.on_spawn_batch.as_ref()
}
#[inline]
pub fn on_despawn_batch(&self) -> Option<&DespawnBatchHook> {
self.on_despawn_batch.as_ref()
}
#[inline]
pub fn id(&self) -> Option<AgentTemplateId> {
self.id
}
#[inline]
pub fn capacity(&self) -> Option<usize> {
self.capacity
}
}
impl std::fmt::Debug for AgentTemplate {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AgentTemplate")
.field("name", &self.name)
.field("component_count", &self.defaults.len())
.finish_non_exhaustive()
}
}
pub struct AgentTemplateBuilder {
name: String,
signature: Signature,
defaults: HashMap<ComponentID, DefaultFactory>,
column_defaults: HashMap<ComponentID, ColumnFactory>,
on_spawn: Option<SpawnHook>,
on_despawn: Option<DespawnHook>,
on_spawn_batch: Option<SpawnBatchHook>,
on_despawn_batch: Option<DespawnBatchHook>,
capacity: Option<usize>,
}
impl AgentTemplateBuilder {
pub fn with_component<T>(mut self, id: ComponentID) -> AgentResult<Self>
where
T: Any + Default + Send + 'static,
{
if self
.signature
.try_has(id)
.map_err(|_| AgentError::invalid_component_id(id))?
{
return Err(AgentError::DuplicateComponent(id));
}
self.signature
.try_set(id)
.map_err(|_| AgentError::invalid_component_id(id))?;
self.defaults.insert(
id,
Box::new(|| Box::new(T::default()) as Box<dyn Any + Send>),
);
self.column_defaults.insert(
id,
Box::new(|count| {
let column: Vec<T> = (0..count).map(|_| T::default()).collect();
Box::new(column) as Box<dyn Any + Send>
}),
);
Ok(self)
}
pub fn with_component_factory<T, F>(mut self, id: ComponentID, factory: F) -> AgentResult<Self>
where
T: Any + Send + 'static,
F: Fn() -> T + Send + Sync + 'static,
{
if self
.signature
.try_has(id)
.map_err(|_| AgentError::invalid_component_id(id))?
{
return Err(AgentError::DuplicateComponent(id));
}
self.signature
.try_set(id)
.map_err(|_| AgentError::invalid_component_id(id))?;
let factory = std::sync::Arc::new(factory);
let per_value = std::sync::Arc::clone(&factory);
self.defaults.insert(
id,
Box::new(move || Box::new(per_value()) as Box<dyn Any + Send>),
);
self.column_defaults.insert(
id,
Box::new(move |count| {
let column: Vec<T> = (0..count).map(|_| factory()).collect();
Box::new(column) as Box<dyn Any + Send>
}),
);
Ok(self)
}
pub fn on_spawn(mut self, hook: SpawnHook) -> Self {
self.on_spawn = Some(hook);
self
}
pub fn on_despawn(mut self, hook: DespawnHook) -> Self {
self.on_despawn = Some(hook);
self
}
pub fn on_spawn_batch(mut self, hook: SpawnBatchHook) -> Self {
self.on_spawn_batch = Some(hook);
self
}
pub fn on_despawn_batch(mut self, hook: DespawnBatchHook) -> Self {
self.on_despawn_batch = Some(hook);
self
}
pub fn with_capacity(mut self, expected_count: usize) -> Self {
self.capacity = Some(expected_count);
self
}
pub fn build(self) -> AgentTemplate {
AgentTemplate {
id: None,
name: self.name,
signature: self.signature,
defaults: self.defaults,
column_defaults: self.column_defaults,
on_spawn: self.on_spawn,
on_despawn: self.on_despawn,
on_spawn_batch: self.on_spawn_batch,
on_despawn_batch: self.on_despawn_batch,
capacity: self.capacity,
}
}
}
impl std::fmt::Debug for AgentTemplateBuilder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AgentTemplateBuilder")
.field("name", &self.name)
.field("component_count", &self.defaults.len())
.finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Default, Clone)]
struct Health(f32);
#[derive(Default, Clone)]
struct Wealth {
_value: f32,
}
#[test]
fn builder_registers_components() {
let tmpl = AgentTemplate::builder("Sheep")
.with_component::<Health>(0)
.unwrap()
.with_component::<Wealth>(1)
.unwrap()
.build();
assert_eq!(tmpl.name(), "Sheep");
assert!(tmpl.has_component(0).unwrap());
assert!(tmpl.has_component(1).unwrap());
assert!(!tmpl.has_component(2).unwrap());
assert_eq!(tmpl.defaults.len(), 2);
}
#[test]
fn duplicate_component_is_rejected() {
let res = AgentTemplate::builder("X")
.with_component::<Health>(0)
.unwrap()
.with_component::<Health>(0);
assert_eq!(res.unwrap_err(), AgentError::DuplicateComponent(0));
}
#[test]
fn default_factory_produces_value_of_correct_type() {
let tmpl = AgentTemplate::builder("A")
.with_component::<Health>(0)
.unwrap()
.build();
let boxed = (tmpl.defaults[&0])();
assert!(boxed.downcast::<Health>().is_ok());
}
#[test]
fn explicit_factory_is_stored() {
let tmpl = AgentTemplate::builder("B")
.with_component_factory(7, || Health(42.0))
.unwrap()
.build();
assert!(tmpl.has_component(7).unwrap());
let boxed = (tmpl.defaults[&7])();
let h = boxed.downcast::<Health>().unwrap();
assert!((h.0 - 42.0).abs() < f32::EPSILON);
}
#[test]
fn spawner_is_created_from_template() {
let tmpl = AgentTemplate::builder("C")
.with_component::<Health>(0)
.unwrap()
.build();
let _ = tmpl.spawner();
}
#[test]
fn batch_requires_registered_template_id() {
let tmpl = AgentTemplate::builder("Fox").build();
match tmpl.batch(4) {
Err(err) => assert_eq!(err, AgentError::UnregisteredTemplate("Fox".into())),
Ok(_) => panic!("expected unregistered template error"),
}
}
#[test]
fn invalid_component_id_returns_error() {
let invalid = crate::engine::types::COMPONENT_CAP as ComponentID;
let err = AgentTemplate::builder("Fox")
.with_component::<u32>(invalid)
.unwrap_err();
assert_eq!(err, AgentError::invalid_component_id(invalid));
let tmpl = AgentTemplate::builder("Fox").build();
assert_eq!(
tmpl.has_component(invalid).unwrap_err(),
AgentError::invalid_component_id(invalid)
);
}
}