wowlab_node/worker/
runner.rs1use std::sync::Arc;
4
5use prost::Message;
6use wowlab_common::{
7 RuntimeWorkFidelity, RuntimeWorkItem, RuntimeWorkResult,
8 sim::{derive::derive_work_unit_config, intent::SimConfigIntent},
9};
10use wowlab_engine_application::{ApplicationError, simulate_intent};
11use wowlab_engine_ports::{ChunkAssignment, ContentCatalog, DynDataResolver, NoopProgress};
12use wowlab_types::proto::{self, TournamentPayload};
13
14#[derive(Debug, thiserror::Error)]
15#[non_exhaustive]
16pub(crate) enum SimError {
17 #[error("Derive error: {0}")]
18 Derive(#[from] wowlab_common::sim::derive::DeriveError),
19 #[error("Intent error: {0}")]
20 Intent(#[from] ApplicationError),
21 #[error("Intent error: {0}")]
22 TelemetryDecode(#[from] prost::DecodeError),
23}
24
25pub(crate) async fn run_item(
26 catalog: &'static ContentCatalog,
27 base_intent: &SimConfigIntent,
28 payload: &TournamentPayload,
29 item: &RuntimeWorkItem,
30 chunk_index: u32,
31 resolver: Arc<DynDataResolver<'static>>,
32) -> Result<RuntimeWorkResult, SimError> {
33 let config_toml = derive_work_unit_config(base_intent, payload, &item.kind)?;
34 let telemetry_bytes = run_simulation(
35 catalog,
36 &config_toml,
37 item.iterations,
38 item.seed_offset,
39 chunk_index,
40 resolver,
41 )
42 .await?;
43
44 let telemetry = proto::ChunkTelemetry::decode(telemetry_bytes.as_slice())?;
45
46 let telemetry_pb = match item.fidelity {
47 RuntimeWorkFidelity::Full => Some(telemetry_bytes),
48 _ => None,
49 };
50
51 Ok(RuntimeWorkResult {
52 item_id: item.item_id,
53 tag: item.tag,
54 iterations: telemetry.iterations,
55 mean_dps_x10: telemetry.mean_dps_x10,
56 m2_dps_bits: telemetry.m2_dps_bits,
57 telemetry_pb,
58 })
59}
60
61async fn run_simulation(
62 catalog: &'static ContentCatalog,
63 config_toml: &str,
64 iterations: u32,
65 base_seed: u64,
66 chunk_index: u32,
67 resolver: Arc<DynDataResolver<'static>>,
68) -> Result<Vec<u8>, SimError> {
69 let mut chunk = ChunkAssignment::single_indexed("", chunk_index, iterations);
70
71 chunk.job_id = String::new();
72 chunk.permutation_index = None;
73
74 let report = simulate_intent(
75 catalog,
76 config_toml,
77 &chunk,
78 base_seed,
79 resolver.as_ref(),
80 &NoopProgress,
81 )
82 .await?;
83
84 Ok(report.telemetry_bytes)
85}
86
87#[cfg(test)]
88mod tests {
89 use std::error::Error as _;
90
91 use googletest::prelude::*;
92
93 use super::*;
94
95 #[gtest]
96 fn application_error_keeps_node_presentation_and_typed_source() -> Result<()> {
97 let application = ApplicationError::from_engine(
98 wowlab_engine_application::ApplicationStage::Simulation,
99 wowlab_engine_ports::EngineError::simulation_runtime("event queue failed"),
100 );
101 let error = SimError::from(application);
102
103 verify_that!(
104 error.to_string(),
105 eq("Intent error: simulation runtime error: event queue failed")
106 )?;
107
108 verify_true!(
109 error
110 .source()
111 .is_some_and(<dyn std::error::Error>::is::<ApplicationError>)
112 )
113 }
114}