use henad_compute::entry::{ModelEntry, ModelState};
use henad_compute::gpu::GpuContext;
use henad_core::metadata::Backend;
use henad_core::model::SimState;
use henad_core::params::ParamValue;
use super::{SEED, fault_text};
fn build(entry: &ModelEntry, values: &[ParamValue], gpu: Option<&GpuContext>) -> Result<ModelState, String> {
entry
.build(values, Some(SEED), gpu)
.map_err(|fault| format!("The model did not build. {}", fault_text(&fault)))
}
fn sim_state(state: &mut ModelState) -> &mut dyn SimState {
match state {
ModelState::Cpu(state) => state.as_mut(),
ModelState::Gpu(state) => state.as_mut(),
}
}
pub(super) fn apply_modes(entry: &ModelEntry, values: &[ParamValue], gpu: Option<&GpuContext>) -> Result<(), String> {
let mut built = build(entry, values, gpu)?;
let state = sim_state(&mut built);
let mut disagreeing = Vec::new();
for (index, descriptor) in entry.param_descriptors().iter().enumerate() {
let accepted = state.set_param(index, &values[index]);
if accepted != descriptor.is_live() {
disagreeing.push(format!(
"'{}' is declared {:?}, but the state {} a live edit",
descriptor.id,
descriptor.apply,
if accepted { "accepts" } else { "refuses" }
));
}
}
if disagreeing.is_empty() {
Ok(())
} else {
Err(format!("Parameter {}.", disagreeing.join(", and ")))
}
}
pub(super) fn views(entry: &ModelEntry, values: &[ParamValue], gpu: Option<&GpuContext>) -> Result<(), String> {
let backend = entry.metadata().backend;
let hint = entry.topology_hint();
let built = build(entry, values, gpu)?;
let (grid, agents, edges) = match (&built, backend) {
(ModelState::Cpu(state), Backend::Cpu) => (
Some(state.grid_view().is_some()),
Some(state.point_view().is_some()),
Some(state.edge_view().is_some()),
),
(ModelState::Gpu(state), Backend::Gpu) => {
let view = state.view();
(Some(view.display.is_some()), Some(view.agents.is_some()), None)
}
_ => {
return Err(format!(
"The entry declares {backend:?}, but its factory returns the other arm."
));
}
};
let mut disagreeing = Vec::new();
for (layer, declared, found) in [
("grid", hint.grid, grid),
("agents", hint.agents, agents),
("edges", hint.edges, edges),
] {
if found.is_some_and(|found| found != declared) {
disagreeing.push(format!("{layer}={declared}"));
}
}
if disagreeing.is_empty() {
Ok(())
} else {
Err(format!(
"The topology hint declares {}, but the state's views disagree.",
disagreeing.join(" and ")
))
}
}
pub(super) fn parallel_jobs(entry: &ModelEntry, values: &[ParamValue], gpu: Option<&GpuContext>) -> Result<(), String> {
let mut built = build(entry, values, gpu)?;
let cpu = matches!(built, ModelState::Cpu(_));
match sim_state(&mut built).parallel_jobs() {
Some(0) => Err("A step splits into no jobs.".to_owned()),
Some(jobs) if !cpu => Err(format!("A GPU state reports {jobs} parallel jobs.")),
None if cpu => Err("A CPU state reports no parallel jobs.".to_owned()),
_ => Ok(()),
}
}
pub(super) fn actions(entry: &ModelEntry, values: &[ParamValue], gpu: Option<&GpuContext>) -> Result<(), String> {
let mut built = build(entry, values, gpu)?;
let state = sim_state(&mut built);
let declared = entry.action_descriptors();
let refused: Vec<String> = declared
.iter()
.enumerate()
.filter(|&(index, _)| !state.act(index))
.map(|(index, action)| format!("'{}' at index {index}", action.id))
.collect();
if !refused.is_empty() {
return Err(format!("The state refuses declared action {}.", refused.join(" and ")));
}
if state.act(declared.len()) {
return Err(format!(
"The state accepts an action at index {}, past the {} it declares.",
declared.len(),
declared.len()
));
}
Ok(())
}
pub(super) fn stat_count(entry: &ModelEntry, values: &[ParamValue], gpu: Option<&GpuContext>) -> Result<(), String> {
let mut built = build(entry, values, gpu)?;
let found = sim_state(&mut built).stats().len();
let declared = entry.stat_descriptors().len();
if found == declared {
Ok(())
} else {
Err(format!(
"The entry declares {declared} stats, and the state returns {found} values."
))
}
}