1#![cfg_attr(
7 all(target_arch = "wasm32", target_feature = "atomics"),
8 expect(
9 clippy::arc_with_non_send_sync,
10 reason = "a factory is not `Send` on wasm with atomics, see `send_sync`"
11 )
12)]
13
14mod set;
15
16pub use set::{ModelLookupError, ModelSet, ModelSetError, ModelSetIter};
17
18use std::any::type_name;
19use std::fmt;
20use std::sync::Arc;
21
22use henad_core::action::ActionDescriptor;
23use henad_core::authoring::model::agent_model::{AgentLanes, AgentModel, NeighborIndex};
24use henad_core::authoring::model::field::FieldLayer;
25use henad_core::authoring::model::gpu_agent_model::GpuAgentModel;
26use henad_core::authoring::model::gpu_grid_model::GpuGridModel;
27use henad_core::authoring::model::grid_model::GridModel;
28use henad_core::authoring::model::network_model::NetworkModel;
29use henad_core::explore::plan::ModelSchema;
30use henad_core::metadata::{Backend, ModelMetadata, Structure};
31use henad_core::model::SimState;
32use henad_core::params::{ParamDescriptor, ParamValue};
33use henad_core::provenance::ModelSource;
34use henad_core::send_sync::{WasmNotSend, WasmNotSync};
35use henad_core::topology::TopologyHint;
36use henad_core::view::StatDescriptor;
37
38use crate::cpu::agent_engine::{AgentModelState, agent_model_param_descriptors};
39use crate::cpu::grid_engine::{GridModelState, grid_model_param_descriptors};
40use crate::cpu::network_engine::{NetworkModelState, network_model_param_descriptors};
41use crate::fault::{BUILDING, Fault, catching};
42use crate::gpu::agent_engine::GpuAgentState;
43use crate::gpu::capacity::Demand;
44use crate::gpu::fault::catching_on;
45use crate::gpu::grid_engine::GpuGridState;
46use crate::gpu::sim_thread::GpuSimState;
47use crate::gpu::{GpuContext, GpuNeeds};
48use crate::simulation::RunSetup;
49
50pub enum ModelState {
56 Cpu(Box<dyn SimState>),
58 Gpu(Box<dyn GpuSimState>),
60}
61
62impl fmt::Debug for ModelState {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 match self {
66 Self::Cpu(_) => f.debug_tuple("Cpu").finish_non_exhaustive(),
67 Self::Gpu(_) => f.debug_tuple("Gpu").finish_non_exhaustive(),
68 }
69 }
70}
71
72#[doc(hidden)]
77pub trait Factory:
78 Fn(&[ParamValue], Option<u64>, Option<&GpuContext>) -> Result<ModelState, Fault> + WasmNotSend + WasmNotSync
79{
80}
81
82impl<F> Factory for F where
83 F: Fn(&[ParamValue], Option<u64>, Option<&GpuContext>) -> Result<ModelState, Fault> + WasmNotSend + WasmNotSync
84{
85}
86
87pub(crate) trait Capacity: Fn(&[ParamValue], &wgpu::Limits) -> Demand + WasmNotSend + WasmNotSync {}
89
90impl<F: Fn(&[ParamValue], &wgpu::Limits) -> Demand + WasmNotSend + WasmNotSync> Capacity for F {}
91
92#[derive(Clone)]
96pub struct ModelEntry {
97 parts: Arc<EntryParts>,
98}
99
100#[derive(Clone)]
101struct EntryParts {
102 id: String,
103 name: String,
104 description: String,
105 param_descriptors: Vec<ParamDescriptor>,
106 stat_descriptors: Vec<StatDescriptor>,
107 action_descriptors: Vec<ActionDescriptor>,
108 topology_hint: TopologyHint,
109 metadata: ModelMetadata,
110 gpu_needs: Option<GpuNeeds>,
111 source: ModelSource,
112 factory: Arc<dyn Factory>,
113 capacity: Option<Arc<dyn Capacity>>,
116}
117
118impl fmt::Debug for ModelEntry {
120 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121 let parts = &self.parts;
122 f.debug_struct("ModelEntry")
123 .field("id", &parts.id)
124 .field("name", &parts.name)
125 .field("description", &parts.description)
126 .field("param_descriptors", &parts.param_descriptors)
127 .field("stat_descriptors", &parts.stat_descriptors)
128 .field("action_descriptors", &parts.action_descriptors)
129 .field("topology_hint", &parts.topology_hint)
130 .field("metadata", &parts.metadata)
131 .field("gpu_needs", &parts.gpu_needs)
132 .field("source", &parts.source)
133 .finish_non_exhaustive()
134 }
135}
136
137impl ModelEntry {
138 pub fn id(&self) -> &str {
140 &self.parts.id
141 }
142
143 pub fn name(&self) -> &str {
145 &self.parts.name
146 }
147
148 pub fn description(&self) -> &str {
150 &self.parts.description
151 }
152
153 pub fn param_descriptors(&self) -> &[ParamDescriptor] {
155 &self.parts.param_descriptors
156 }
157
158 pub fn stat_descriptors(&self) -> &[StatDescriptor] {
160 &self.parts.stat_descriptors
161 }
162
163 pub fn action_descriptors(&self) -> &[ActionDescriptor] {
165 &self.parts.action_descriptors
166 }
167
168 pub fn topology_hint(&self) -> TopologyHint {
170 self.parts.topology_hint
171 }
172
173 pub fn metadata(&self) -> &ModelMetadata {
175 &self.parts.metadata
176 }
177
178 pub fn schema(&self) -> ModelSchema<'_> {
180 ModelSchema {
181 id: &self.parts.id,
182 params: &self.parts.param_descriptors,
183 stats: &self.parts.stat_descriptors,
184 actions: &self.parts.action_descriptors,
185 }
186 }
187
188 pub fn gpu_needs(&self) -> Option<GpuNeeds> {
190 self.parts.gpu_needs
191 }
192
193 pub fn source(&self) -> &ModelSource {
195 &self.parts.source
196 }
197
198 pub fn param_index(&self, id: &str) -> Option<usize> {
200 self.parts.param_descriptors.iter().position(|desc| desc.id == id)
201 }
202
203 pub fn action_index(&self, id: &str) -> Option<usize> {
205 self.parts.action_descriptors.iter().position(|action| action.id == id)
206 }
207
208 pub fn setup(&self) -> RunSetup {
210 RunSetup::new(self.clone())
211 }
212
213 pub fn build(
221 &self,
222 params: &[ParamValue],
223 seed: Option<u64>,
224 gpu: Option<&GpuContext>,
225 ) -> Result<ModelState, Fault> {
226 (self.parts.factory)(params, seed, gpu)
227 }
228
229 pub fn demand(&self, params: &[ParamValue], limits: &wgpu::Limits) -> Option<Demand> {
233 self.parts.capacity.as_ref().map(|capacity| capacity(params, limits))
234 }
235
236 pub fn shortfalls(&self, params: &[ParamValue], limits: &wgpu::Limits) -> Vec<String> {
238 self.demand(params, limits)
239 .map_or_else(Vec::new, |demand| demand.shortfalls(limits))
240 }
241
242 #[doc(hidden)]
244 pub fn wrap_factory(self, wrap: impl FnOnce(Arc<dyn Factory>) -> Arc<dyn Factory>) -> Self {
245 let mut parts = Arc::unwrap_or_clone(self.parts);
246 parts.factory = wrap(parts.factory);
247 Self { parts: Arc::new(parts) }
248 }
249
250 fn with_source(mut self, source: ModelSource) -> Self {
252 Arc::make_mut(&mut self.parts).source = source;
253 self
254 }
255}
256
257fn no_device(id: &str) -> Fault {
259 Fault::refused(
260 BUILDING,
261 format!("model '{id}' runs on the GPU and needs a device to build on"),
262 )
263}
264
265pub fn register_grid_model<M: GridModel>() -> ModelEntry {
267 ModelEntry {
268 parts: Arc::new(EntryParts {
269 id: M::ID.to_owned(),
270 name: M::NAME.to_owned(),
271 description: M::DESCRIPTION.to_owned(),
272 param_descriptors: grid_model_param_descriptors::<M>(),
273 stat_descriptors: M::STATS.to_vec(),
274 action_descriptors: M::ACTIONS.to_vec(),
275 topology_hint: TopologyHint::GRID,
276 metadata: ModelMetadata {
277 backend: Backend::Cpu,
278 palette: Some(M::PALETTE),
279 structure: Structure::Grid {
280 neighborhood: M::NEIGHBORHOOD,
281 },
282 replays_exactly: true,
283 },
284 gpu_needs: None,
285 source: ModelSource::__from_type_path(type_name::<M>()),
286 factory: Arc::new(|params: &[ParamValue], seed: Option<u64>, _gpu: Option<&GpuContext>| {
287 catching(BUILDING, || {
288 ModelState::Cpu(Box::new(GridModelState::<M>::from_params_seeded(params, seed)))
289 })
290 }),
291 capacity: None,
292 }),
293 }
294}
295
296pub fn register_agent_model<A: AgentModel>() -> ModelEntry {
298 ModelEntry {
299 parts: Arc::new(EntryParts {
300 id: A::ID.to_owned(),
301 name: A::NAME.to_owned(),
302 description: A::DESCRIPTION.to_owned(),
303 param_descriptors: agent_model_param_descriptors::<A>(),
304 stat_descriptors: A::STATS.to_vec(),
305 action_descriptors: A::ACTIONS.to_vec(),
306 topology_hint: TopologyHint {
307 grid: <A::Field as FieldLayer>::HAS_GRID,
308 agents: true,
309 edges: false,
310 },
311 metadata: ModelMetadata {
312 backend: Backend::Cpu,
313 palette: Some(A::PALETTE),
314 structure: Structure::Agents {
315 chunk: A::CHUNK,
316 lanes: <A::Lanes as AgentLanes>::LANES,
317 index: <A::Index as NeighborIndex>::KIND,
318 field: <A::Field as FieldLayer>::KIND,
319 },
320 replays_exactly: true,
321 },
322 gpu_needs: None,
323 source: ModelSource::__from_type_path(type_name::<A>()),
324 factory: Arc::new(|params: &[ParamValue], seed: Option<u64>, _gpu: Option<&GpuContext>| {
325 catching(BUILDING, || {
326 ModelState::Cpu(Box::new(AgentModelState::<A>::from_params_seeded(params, seed)))
327 })
328 }),
329 capacity: None,
330 }),
331 }
332}
333
334pub fn register_network_model<N: NetworkModel>() -> ModelEntry {
336 ModelEntry {
337 parts: Arc::new(EntryParts {
338 id: N::ID.to_owned(),
339 name: N::NAME.to_owned(),
340 description: N::DESCRIPTION.to_owned(),
341 param_descriptors: network_model_param_descriptors::<N>(),
342 stat_descriptors: N::STATS.to_vec(),
343 action_descriptors: N::ACTIONS.to_vec(),
344 topology_hint: TopologyHint::NETWORK,
345 metadata: ModelMetadata {
346 backend: Backend::Cpu,
347 palette: Some(N::PALETTE),
348 structure: Structure::Network {
349 chunk: N::CHUNK,
350 lanes: <N::Lanes as AgentLanes>::LANES,
351 edge_palette: N::EDGE_PALETTE,
352 },
353 replays_exactly: true,
354 },
355 gpu_needs: None,
356 source: ModelSource::__from_type_path(type_name::<N>()),
357 factory: Arc::new(|params: &[ParamValue], seed: Option<u64>, _gpu: Option<&GpuContext>| {
358 catching(BUILDING, || {
359 ModelState::Cpu(Box::new(NetworkModelState::<N>::from_params_seeded(params, seed)))
360 })
361 }),
362 capacity: None,
363 }),
364 }
365}
366
367pub fn register_gpu_grid_model<M: GpuGridModel>() -> ModelEntry {
371 ModelEntry {
372 parts: Arc::new(EntryParts {
373 id: M::ID.to_owned(),
374 name: M::NAME.to_owned(),
375 description: M::DESCRIPTION.to_owned(),
376 param_descriptors: M::param_descriptors()
377 .into_iter()
378 .map(ParamDescriptor::on_reload)
379 .collect(),
380 stat_descriptors: M::STATS.to_vec(),
381 action_descriptors: M::ACTIONS.iter().map(|action| action.desc).collect(),
382 topology_hint: TopologyHint::GRID,
384 metadata: ModelMetadata {
385 backend: Backend::Gpu,
386 palette: Some(M::PALETTE),
387 structure: Structure::GpuGrid {
388 buffers: M::BUFFERS,
389 workgroup: M::WORKGROUP_SIZE,
390 },
391 replays_exactly: M::REPLAYS_EXACTLY,
392 },
393 gpu_needs: Some(GpuNeeds::with_storage_buffers(GpuGridState::<M>::max_storage_bindings())),
394 source: ModelSource::__from_type_path(type_name::<M>()),
395 factory: Arc::new(|params: &[ParamValue], seed: Option<u64>, gpu: Option<&GpuContext>| {
396 let ctx = gpu.ok_or_else(|| no_device(M::ID))?;
397 catching_on(ctx, BUILDING, || {
398 ModelState::Gpu(Box::new(GpuGridState::<M>::new_seeded(ctx, params, seed)))
399 })
400 }),
401 capacity: Some(Arc::new(GpuGridState::<M>::demand)),
402 }),
403 }
404}
405
406pub fn register_gpu_agent_model<M: GpuAgentModel>() -> ModelEntry {
410 ModelEntry {
411 parts: Arc::new(EntryParts {
412 id: M::ID.to_owned(),
413 name: M::NAME.to_owned(),
414 description: M::DESCRIPTION.to_owned(),
415 param_descriptors: M::param_descriptors()
416 .into_iter()
417 .map(ParamDescriptor::on_reload)
418 .collect(),
419 stat_descriptors: M::STATS.to_vec(),
420 action_descriptors: M::ACTIONS.iter().map(|action| action.desc).collect(),
421 topology_hint: TopologyHint {
423 grid: M::DISPLAY.is_some(),
424 agents: true,
425 edges: false,
426 },
427 metadata: ModelMetadata {
428 backend: Backend::Gpu,
429 palette: None,
431 structure: Structure::GpuAgents {
432 buffers: M::BUFFERS,
433 passes: M::STEP_PASSES,
434 index: M::INDEX,
435 display: M::DISPLAY.is_some(),
436 counters: M::COUNTERS,
437 },
438 replays_exactly: M::REPLAYS_EXACTLY,
439 },
440 gpu_needs: Some(GpuNeeds::with_storage_buffers(
441 GpuAgentState::<M>::max_storage_bindings(),
442 )),
443 source: ModelSource::__from_type_path(type_name::<M>()),
444 factory: Arc::new(|params: &[ParamValue], seed: Option<u64>, gpu: Option<&GpuContext>| {
445 let ctx = gpu.ok_or_else(|| no_device(M::ID))?;
446 catching_on(ctx, BUILDING, || {
447 ModelState::Gpu(Box::new(GpuAgentState::<M>::new_seeded(ctx, params, seed)))
448 })
449 }),
450 capacity: Some(Arc::new(GpuAgentState::<M>::demand)),
451 }),
452 }
453}