use std::any::Any;
use std::collections::HashMap;
use crate::engine::commands::{BatchColumn, Command, SpawnBatch};
use crate::engine::error::ECSResult;
use crate::engine::manager::ECSReference;
use crate::engine::types::{AgentTemplateId, ComponentID};
use super::error::{AgentError, AgentResult};
use super::template::AgentTemplate;
pub struct AgentBatch<'t> {
template: &'t AgentTemplate,
template_id: AgentTemplateId,
count: usize,
columns: HashMap<ComponentID, (Box<dyn Any + Send>, usize)>,
}
impl<'t> AgentBatch<'t> {
pub(crate) fn new(
template: &'t AgentTemplate,
template_id: AgentTemplateId,
count: usize,
) -> Self {
Self {
template,
template_id,
count,
columns: HashMap::new(),
}
}
pub fn set_column<T: Any + Send + 'static>(
mut self,
component_id: ComponentID,
values: Vec<T>,
) -> AgentResult<Self> {
if !self
.template
.signature
.try_has(component_id)
.map_err(|_| AgentError::invalid_component_id(component_id))?
{
return Err(AgentError::MissingComponent(component_id));
}
if values.len() != self.count {
return Err(AgentError::BatchLengthMismatch {
component_id,
expected: self.count,
actual: values.len(),
});
}
let len = values.len();
self.columns.insert(component_id, (Box::new(values), len));
Ok(self)
}
pub(crate) fn set_erased_column(
mut self,
component_id: ComponentID,
values: Box<dyn Any + Send>,
len: usize,
) -> AgentResult<Self> {
if !self
.template
.signature
.try_has(component_id)
.map_err(|_| AgentError::invalid_component_id(component_id))?
{
return Err(AgentError::MissingComponent(component_id));
}
if len != self.count {
return Err(AgentError::BatchLengthMismatch {
component_id,
expected: self.count,
actual: len,
});
}
self.columns.insert(component_id, (values, len));
Ok(self)
}
pub fn into_spawn_batch(mut self) -> SpawnBatch {
let mut columns = Vec::new();
for component_id in self.template.signature.iterate_over_components() {
let (values, len) = self.columns.remove(&component_id).unwrap_or_else(|| {
let factory = &self.template.column_defaults[&component_id];
(factory(self.count), self.count)
});
columns.push(BatchColumn {
component_id,
values,
len,
});
}
SpawnBatch {
count: self.count,
signature: self.template.signature,
columns,
}
}
pub fn spawn(self, ecs: ECSReference<'_>) -> ECSResult<()> {
let template_id = self.template_id;
ecs.defer(Command::SpawnBatchTagged {
batch: self.into_spawn_batch(),
template_id,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::agents::registry::AgentRegistry;
#[test]
fn set_column_rejects_invalid_component_id() {
let mut registry = AgentRegistry::new();
registry
.register(
AgentTemplate::builder("Sheep")
.with_component::<u32>(0)
.unwrap()
.build(),
)
.unwrap();
let template = registry.get("Sheep").unwrap();
let invalid = crate::engine::types::COMPONENT_CAP as ComponentID;
let result = template
.batch(1)
.unwrap()
.set_column::<u32>(invalid, vec![1]);
match result {
Err(err) => assert_eq!(err, AgentError::invalid_component_id(invalid)),
Ok(_) => panic!("expected invalid component id error"),
}
}
#[test]
fn unset_columns_fill_from_column_defaults() {
let mut registry = AgentRegistry::new();
registry
.register(
AgentTemplate::builder("Sheep")
.with_component::<u32>(0)
.unwrap()
.with_component_factory(1, || 7u64)
.unwrap()
.build(),
)
.unwrap();
let template = registry.get("Sheep").unwrap();
let batch = template
.batch(3)
.unwrap()
.set_column::<u32>(0, vec![1, 2, 3])
.unwrap()
.into_spawn_batch();
assert_eq!(batch.count, 3);
assert_eq!(batch.columns.len(), 2);
for column in &batch.columns {
assert_eq!(column.len, 3);
}
let defaults = batch
.columns
.into_iter()
.find(|column| column.component_id == 1)
.unwrap();
let values = defaults.values.downcast::<Vec<u64>>().unwrap();
assert_eq!(*values, vec![7, 7, 7]);
}
}