murk_core/command.rs
1//! Command, command payload, and receipt types for the ingress pipeline.
2
3use crate::error::IngressError;
4use crate::id::{Coord, FieldId, ParameterKey, TickId};
5
6/// A command submitted to the simulation via the ingress pipeline.
7///
8/// Commands are ordered by `priority_class` (lower = higher priority),
9/// then by `source_id` for disambiguation, then by `arrival_seq` as
10/// a final tiebreaker.
11///
12/// # Examples
13///
14/// ```
15/// use murk_core::{Command, CommandPayload, FieldId, TickId, ParameterKey};
16///
17/// // A command that sets a global parameter.
18/// let cmd = Command {
19/// payload: CommandPayload::SetParameter {
20/// key: ParameterKey(0),
21/// value: 2.5,
22/// },
23/// expires_after_tick: TickId(100),
24/// source_id: Some(1),
25/// source_seq: Some(0),
26/// priority_class: 1,
27/// arrival_seq: 0,
28/// };
29///
30/// assert_eq!(cmd.priority_class, 1);
31/// assert_eq!(cmd.expires_after_tick, TickId(100));
32/// ```
33#[derive(Clone, Debug, PartialEq)]
34pub struct Command {
35 /// The operation to perform.
36 pub payload: CommandPayload,
37 /// The command expires if not applied by this tick.
38 pub expires_after_tick: TickId,
39 /// Optional source identifier for deduplication and ordering.
40 pub source_id: Option<u64>,
41 /// Optional per-source sequence number for ordering.
42 pub source_seq: Option<u64>,
43 /// Priority class. Lower values = higher priority.
44 /// 0 = system, 1 = user default.
45 pub priority_class: u8,
46 /// Monotonic arrival sequence number, set by the ingress pipeline.
47 pub arrival_seq: u64,
48}
49
50/// All command payloads.
51///
52/// `WorldEvent` variants affect per-cell state; `GlobalParameter` variants
53/// affect simulation-wide scalar parameters.
54///
55/// # Examples
56///
57/// ```
58/// use murk_core::{CommandPayload, FieldId, ParameterKey};
59///
60/// // Set a single field value at a coordinate.
61/// let coord: murk_core::Coord = vec![3i32, 7].into();
62/// let payload = CommandPayload::SetField {
63/// coord,
64/// field_id: FieldId(0),
65/// value: 42.0,
66/// };
67///
68/// // Batch-set multiple global parameters atomically.
69/// let batch = CommandPayload::SetParameterBatch {
70/// params: vec![(ParameterKey(0), 1.0), (ParameterKey(1), 0.5)],
71/// };
72///
73/// assert!(matches!(payload, CommandPayload::SetField { .. }));
74/// assert!(matches!(batch, CommandPayload::SetParameterBatch { .. }));
75/// ```
76#[derive(Clone, Debug, PartialEq)]
77pub enum CommandPayload {
78 // --- WorldEvent variants ---
79 /// Move an entity to a target coordinate.
80 ///
81 /// Rejected if `entity_id` is unknown or `target_coord` is out of bounds.
82 Move {
83 /// The entity to move.
84 entity_id: u64,
85 /// The destination coordinate.
86 target_coord: Coord,
87 },
88 /// Spawn a new entity at a coordinate with initial field values.
89 Spawn {
90 /// The spawn location.
91 coord: Coord,
92 /// Initial field values for the new entity.
93 field_values: Vec<(FieldId, f32)>,
94 },
95 /// Remove an entity. Associated field values are cleared at the next tick.
96 Despawn {
97 /// The entity to remove.
98 entity_id: u64,
99 },
100 /// Set a single field value at a coordinate. Primarily for `Sparse` fields.
101 SetField {
102 /// The target cell coordinate.
103 coord: Coord,
104 /// The field to modify.
105 field_id: FieldId,
106 /// The new value.
107 value: f32,
108 },
109 /// Extension point for domain-specific commands.
110 Custom {
111 /// User-registered type identifier.
112 type_id: u32,
113 /// Opaque payload data.
114 data: Vec<u8>,
115 },
116
117 // --- GlobalParameter variants ---
118 /// Set a single global parameter. Takes effect at the next tick boundary.
119 SetParameter {
120 /// The parameter to set.
121 key: ParameterKey,
122 /// The new value.
123 value: f64,
124 },
125 /// Batch-set multiple parameters atomically.
126 SetParameterBatch {
127 /// The parameters to set.
128 params: Vec<(ParameterKey, f64)>,
129 },
130}
131
132/// Receipt returned for each command in a submitted batch.
133///
134/// Indicates whether the command was accepted and, if applied,
135/// which tick it was applied in.
136///
137/// # Examples
138///
139/// ```
140/// use murk_core::command::Receipt;
141/// use murk_core::TickId;
142///
143/// let receipt = Receipt {
144/// accepted: true,
145/// applied_tick_id: Some(TickId(5)),
146/// reason_code: None,
147/// command_index: 0,
148/// };
149///
150/// assert!(receipt.accepted);
151/// assert_eq!(receipt.applied_tick_id, Some(TickId(5)));
152/// ```
153#[derive(Clone, Debug, PartialEq, Eq)]
154pub struct Receipt {
155 /// Whether the command was accepted by the ingress pipeline.
156 pub accepted: bool,
157 /// The tick at which the command was applied, if applicable.
158 pub applied_tick_id: Option<TickId>,
159 /// The reason the command was rejected, if applicable.
160 pub reason_code: Option<IngressError>,
161 /// Index of this command within the submitted batch.
162 pub command_index: usize,
163}