use std::any::Any;
use crate::engine::commands::Command;
use crate::engine::component::Bundle;
use crate::engine::error::ECSResult;
use crate::engine::manager::ECSReference;
use crate::engine::types::ComponentID;
use super::error::{AgentError, AgentResult};
use super::template::AgentTemplate;
pub struct AgentSpawner<'t> {
template: &'t AgentTemplate,
overrides: Vec<(ComponentID, Box<dyn Any + Send>)>,
}
impl<'t> AgentSpawner<'t> {
pub fn new(template: &'t AgentTemplate) -> Self {
Self {
template,
overrides: Vec::new(),
}
}
pub fn set<T: Any + Send + 'static>(mut self, id: ComponentID, value: T) -> AgentResult<Self> {
if !self
.template
.signature
.try_has(id)
.map_err(|_| AgentError::invalid_component_id(id))?
{
return Err(AgentError::MissingComponent(id));
}
self.overrides.retain(|(cid, _)| *cid != id);
self.overrides.push((id, Box::new(value)));
Ok(self)
}
pub fn spawn(self, ecs: ECSReference<'_>) -> ECSResult<()> {
let mut bundle = Bundle::new();
for cid in self.template.signature.iterate_over_components() {
if let Some(factory) = self.template.defaults.get(&cid) {
bundle.insert_boxed(cid, factory());
}
}
for (cid, value) in self.overrides {
bundle.insert_boxed(cid, value);
}
ecs.defer(Command::SpawnTagged {
bundle,
tag: self.template.name().to_owned(),
})
}
}
#[cfg(test)]
mod tests {
use crate::agents::error::AgentError;
use crate::agents::template::AgentTemplate;
use crate::engine::types::ComponentID;
#[derive(Default, Clone)]
struct Wealth {
_value: f64,
}
#[test]
fn spawner_set_overrides_are_stored() {
let tmpl = AgentTemplate::builder("A")
.with_component::<Wealth>(0)
.unwrap()
.build();
let spawner = tmpl
.spawner()
.set::<Wealth>(0, Wealth { _value: 99.0 })
.unwrap();
assert_eq!(spawner.overrides.len(), 1);
let (cid, _) = &spawner.overrides[0];
assert_eq!(*cid, 0);
}
#[test]
fn spawner_duplicate_set_last_wins() {
let tmpl = AgentTemplate::builder("B")
.with_component::<Wealth>(0)
.unwrap()
.build();
let spawner = tmpl
.spawner()
.set::<Wealth>(0, Wealth { _value: 1.0 })
.unwrap()
.set::<Wealth>(0, Wealth { _value: 2.0 })
.unwrap();
assert_eq!(spawner.overrides.len(), 1);
}
#[test]
fn spawner_rejects_unknown_component() {
let tmpl = AgentTemplate::builder("C")
.with_component::<Wealth>(0)
.unwrap()
.build();
let result = tmpl.spawner().set::<Wealth>(99, Wealth { _value: 1.0 });
assert!(matches!(result, Err(AgentError::MissingComponent(99))));
}
#[test]
fn spawner_rejects_invalid_component_id() {
let tmpl = AgentTemplate::builder("C")
.with_component::<Wealth>(0)
.unwrap()
.build();
let invalid = crate::engine::types::COMPONENT_CAP as ComponentID;
let result = tmpl
.spawner()
.set::<Wealth>(invalid, Wealth { _value: 1.0 });
match result {
Err(err) => assert_eq!(err, AgentError::invalid_component_id(invalid)),
Ok(_) => panic!("expected invalid component id error"),
}
}
}