1use alloc::string::String;
4use alloc::vec::Vec;
5
6use crate::operation::StageOperation;
7use crate::schedule::system::FlushMode;
8use crate::world::WorldError;
9
10#[non_exhaustive]
12#[derive(Clone, Debug, Eq, PartialEq)]
13pub enum BuildError {
14 PendingCommands,
16 WorldRunning,
18 WorldMutationPoisoned,
20 LeaseMismatch,
22 LiveLeaseAlreadyAttached,
24 UnknownStage { label: String },
26 UnknownSystem { label: String },
28 UnknownSystemSet { label: String },
30 DuplicateSystemSet { label: String },
32 DuplicateSystemLabel { label: String },
34 SystemInitialization { system: String, detail: String },
36 CrossOperationEdge { from: String, to: String },
38 CrossStageSystemEdge { from: String, to: String },
40 MissingRequiredResource { name: String },
42 UnregisteredEventRole { system: String, event: String },
44 EventOperationMismatch {
46 system: String,
47 event: String,
48 event_operation: StageOperation,
49 system_operation: StageOperation,
50 },
51 MissingEventProducer { system: String, event: String },
53 UnreachableEventProducer {
55 producer: String,
56 consumer: String,
57 event: String,
58 },
59 SelfEdge { label: String },
61 Cycle { path: Vec<String> },
63 FixedUpdateWithoutConfig,
65 FixedConfigWithoutFixedUpdate,
67 StageOperationMismatch { label: String },
69 InvalidStageFlushMode { label: String, mode: FlushMode },
71 InvalidSystemFlushMode { label: String, mode: FlushMode },
73 WorldBuild(WorldError),
75}
76
77impl From<WorldError> for BuildError {
78 fn from(value: WorldError) -> Self {
79 Self::WorldBuild(value)
80 }
81}
82
83#[non_exhaustive]
85#[derive(Clone, Debug, Eq, PartialEq)]
86pub enum ScheduleError {
87 OwnerMismatch,
89 StaleHandle,
91 DuplicateStageInPlan,
93 NonUpdateStageInPlan,
95 StartupStageInPlan,
97 SystemNotFound { label: String },
99}
100
101#[cfg(feature = "std")]
102impl core::fmt::Display for BuildError {
103 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
104 match self {
105 Self::PendingCommands => f.write_str("world has pending commands"),
106 Self::WorldRunning => f.write_str("world is running"),
107 Self::WorldMutationPoisoned => f.write_str("world mutation is poisoned"),
108 Self::LeaseMismatch => f.write_str("world and schedule execution lease mismatch"),
109 Self::LiveLeaseAlreadyAttached => {
110 f.write_str("world already has a live compiled schedule lease")
111 }
112 Self::UnknownStage { label } => write!(f, "unknown stage '{label}'"),
113 Self::UnknownSystem { label } => write!(f, "unknown system '{label}'"),
114 Self::UnknownSystemSet { label } => write!(f, "unknown system set '{label}'"),
115 Self::DuplicateSystemSet { label } => write!(f, "duplicate system set '{label}'"),
116 Self::DuplicateSystemLabel { label } => write!(f, "duplicate system label '{label}'"),
117 Self::SystemInitialization { system, detail } => {
118 write!(f, "system '{system}' initialization failed: {detail}")
119 }
120 Self::CrossStageSystemEdge { from, to } => {
121 write!(f, "cross-stage system edge: {from} -> {to}")
122 }
123 Self::MissingRequiredResource { name } => {
124 write!(f, "missing required resource '{name}'")
125 }
126 Self::UnregisteredEventRole { system, event } => {
127 write!(f, "system '{system}' declares unregistered event role '{event}'")
128 }
129 Self::EventOperationMismatch {
130 system,
131 event,
132 event_operation,
133 system_operation,
134 } => write!(
135 f,
136 "system '{system}' runs in {system_operation:?} but event '{event}' belongs to {event_operation:?}"
137 ),
138 Self::MissingEventProducer { system, event } => {
139 write!(f, "event consumer '{system}' has no producer for '{event}'")
140 }
141 Self::UnreachableEventProducer {
142 producer,
143 consumer,
144 event,
145 } => write!(
146 f,
147 "event producer '{producer}' is not ordered before consumer '{consumer}' for '{event}'"
148 ),
149 Self::CrossOperationEdge { from, to } => {
150 write!(f, "ordering edge crosses operations: {from} -> {to}")
151 }
152 Self::SelfEdge { label } => write!(f, "system cannot depend on itself: {label}"),
153 Self::Cycle { path } => write!(f, "schedule cycle: {}", path.join(" -> ")),
154 Self::FixedUpdateWithoutConfig => {
155 f.write_str("FixedUpdate systems require fixed configuration")
156 }
157 Self::FixedConfigWithoutFixedUpdate => {
158 f.write_str("fixed configuration requires a FixedUpdate stage")
159 }
160 Self::StageOperationMismatch { label } => {
161 write!(f, "stage operation mismatch for '{label}'")
162 }
163 Self::InvalidStageFlushMode { label, mode } => {
164 write!(f, "invalid {mode:?} flush mode for stage '{label}'")
165 }
166 Self::InvalidSystemFlushMode { label, mode } => {
167 write!(f, "invalid {mode:?} flush mode for system '{label}'")
168 }
169 Self::WorldBuild(error) => write!(f, "world build failed: {error}"),
170 }
171 }
172}
173
174#[cfg(feature = "std")]
175impl core::fmt::Display for ScheduleError {
176 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
177 match self {
178 Self::OwnerMismatch => f.write_str("schedule handle belongs to a different schedule"),
179 Self::StaleHandle => f.write_str("stale schedule handle"),
180 Self::DuplicateStageInPlan => f.write_str("update plan contains a duplicate stage"),
181 Self::NonUpdateStageInPlan => f.write_str("update plan contains a non-update stage"),
182 Self::StartupStageInPlan => f.write_str("update plan cannot select Startup"),
183 Self::SystemNotFound { label } => write!(f, "system not found: {label}"),
184 }
185 }
186}
187
188#[cfg(feature = "std")]
189impl std::error::Error for BuildError {}
190
191#[cfg(feature = "std")]
192impl std::error::Error for ScheduleError {}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197 use crate::world::WorldError;
198 #[cfg(feature = "std")]
199 use alloc::string::ToString;
200
201 #[test]
202 fn world_error_converts_into_build_error() {
203 let error: BuildError = WorldError::NestedRun.into();
204 assert!(matches!(
205 error,
206 BuildError::WorldBuild(WorldError::NestedRun)
207 ));
208 }
209
210 #[cfg(feature = "std")]
211 #[test]
212 fn build_error_display_covers_detailed_variants() {
213 let errors = [
214 BuildError::SystemInitialization {
215 system: "init".into(),
216 detail: "failed".into(),
217 },
218 BuildError::UnregisteredEventRole {
219 system: "reader".into(),
220 event: "event".into(),
221 },
222 BuildError::EventOperationMismatch {
223 system: "reader".into(),
224 event: "event".into(),
225 event_operation: crate::schedule::StageOperation::Render,
226 system_operation: crate::schedule::StageOperation::Update,
227 },
228 BuildError::MissingEventProducer {
229 system: "reader".into(),
230 event: "event".into(),
231 },
232 BuildError::UnreachableEventProducer {
233 producer: "writer".into(),
234 consumer: "reader".into(),
235 event: "event".into(),
236 },
237 BuildError::InvalidStageFlushMode {
238 label: "Render".into(),
239 mode: crate::schedule::FlushMode::Stage,
240 },
241 BuildError::InvalidSystemFlushMode {
242 label: "render".into(),
243 mode: crate::schedule::FlushMode::AfterSystem,
244 },
245 ];
246
247 for error in errors {
248 assert!(!error.to_string().is_empty());
249 }
250 }
251}