Skip to main content

moirai/world/
error.rs

1//! [`World`] and flush error vocabulary.
2//!
3//! [`WorldError`] covers entity ownership, component registration, structural mutation
4//! guards, resource scopes, events, and command flush failures.
5
6use crate::component::RegistrationError;
7use crate::entity::EntityId;
8use crate::time::ChangeTick;
9use alloc::string::String;
10
11/// Entity allocator faults surfaced through [`WorldError::Allocator`].
12#[non_exhaustive]
13#[derive(Clone, Debug, Eq, PartialEq)]
14pub enum WorldAllocatorError {
15    GenerationOverflow,
16    SlotRetired,
17}
18
19/// Summary of a committed structural command batch.
20#[derive(Copy, Clone, Debug, Eq, PartialEq)]
21pub struct FlushReport {
22    /// Number of deferred commands committed by the flush.
23    pub commands_applied: usize,
24    /// Change tick issued for the committed batch.
25    pub change_tick: ChangeTick,
26}
27
28/// Command preflight or commit failure during [`crate::world::World::flush`].
29#[non_exhaustive]
30#[derive(Clone, Debug, Eq, PartialEq)]
31pub enum FlushError {
32    CommandValidation { index: usize, detail: String },
33    ChangeTickExhausted,
34}
35
36/// Checked world operation failure.
37#[non_exhaustive]
38#[derive(Clone, Debug, Eq, PartialEq)]
39pub enum WorldError {
40    EntityOwnerMismatch { entity: EntityId },
41    StaleEntity { entity: EntityId },
42    EntityNotLive { entity: EntityId },
43    UnregisteredComponent { name: String },
44    WrongStorageKind { name: String },
45    Registration(RegistrationError),
46    Allocator(WorldAllocatorError),
47    ChangeTickExhausted,
48    StructuralMutationDuringRun,
49    StructuralCommandsDuringRender,
50    FlushDuringRun,
51    DiscardDuringRun,
52    Flush(FlushError),
53    UnregisteredResource { name: String },
54    ResourceInUse { name: String },
55    ResourceScoped { name: String },
56    UnregisteredEvent { name: String },
57    EventChannelClosed,
58    NestedRun,
59}
60
61impl From<RegistrationError> for WorldError {
62    fn from(value: RegistrationError) -> Self {
63        Self::Registration(value)
64    }
65}
66
67impl From<FlushError> for WorldError {
68    fn from(value: FlushError) -> Self {
69        Self::Flush(value)
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76    use crate::component::RegistrationError;
77
78    #[test]
79    fn registration_error_converts_into_world_error() {
80        let error: WorldError = RegistrationError::InvalidTag {
81            name: String::from("tag"),
82            detail: String::from("detail"),
83        }
84        .into();
85        assert!(matches!(error, WorldError::Registration(_)));
86    }
87}
88
89/// Event reader consumption failure from [`crate::world::World::read_event`].
90#[non_exhaustive]
91#[derive(Clone, Debug, Eq, PartialEq)]
92pub enum EventReadError {
93    Lagged { dropped: u64 },
94    ChannelClosed,
95    UnregisteredEvent { name: String },
96    OwnerMismatch { name: String },
97}
98
99#[cfg(feature = "std")]
100impl core::fmt::Display for WorldAllocatorError {
101    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
102        match self {
103            Self::GenerationOverflow => f.write_str("entity generation overflow"),
104            Self::SlotRetired => f.write_str("entity slot retired"),
105        }
106    }
107}
108
109#[cfg(feature = "std")]
110impl std::error::Error for WorldAllocatorError {}
111
112#[cfg(feature = "std")]
113impl core::fmt::Display for FlushError {
114    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
115        match self {
116            Self::CommandValidation { index, detail } => {
117                write!(f, "command {index} failed validation: {detail}")
118            }
119            Self::ChangeTickExhausted => f.write_str("change tick exhausted during flush"),
120        }
121    }
122}
123
124#[cfg(feature = "std")]
125impl std::error::Error for FlushError {}
126
127#[cfg(feature = "std")]
128impl core::fmt::Display for WorldError {
129    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
130        match self {
131            Self::EntityOwnerMismatch { entity } => {
132                write!(
133                    f,
134                    "entity {:?}:{:?} belongs to another world",
135                    entity.slot(),
136                    entity.generation()
137                )
138            }
139            Self::StaleEntity { entity } => {
140                write!(
141                    f,
142                    "stale entity {:?}:{:?}",
143                    entity.slot(),
144                    entity.generation()
145                )
146            }
147            Self::EntityNotLive { entity } => {
148                write!(
149                    f,
150                    "entity {:?}:{:?} is not live",
151                    entity.slot(),
152                    entity.generation()
153                )
154            }
155            Self::UnregisteredComponent { name } => {
156                write!(f, "unregistered component {name}")
157            }
158            Self::WrongStorageKind { name } => {
159                write!(f, "wrong storage kind for {name}")
160            }
161            Self::Registration(error) => write!(f, "component registration failed: {error}"),
162            Self::Allocator(error) => write!(f, "entity allocator failed: {error}"),
163            Self::ChangeTickExhausted => f.write_str("change tick exhausted"),
164            Self::StructuralMutationDuringRun => {
165                f.write_str("structural mutation is deferred while the world is running")
166            }
167            Self::StructuralCommandsDuringRender => {
168                f.write_str("structural commands are unavailable during render")
169            }
170            Self::FlushDuringRun => f.write_str("flush is idle-only"),
171            Self::DiscardDuringRun => f.write_str("discard_commands is idle-only"),
172            Self::Flush(error) => write!(f, "flush failed: {error}"),
173            Self::UnregisteredResource { name } => write!(f, "unregistered resource {name}"),
174            Self::ResourceInUse { name } => write!(f, "resource {name} is in use"),
175            Self::ResourceScoped { name } => write!(f, "resource {name} is scoped"),
176            Self::UnregisteredEvent { name } => write!(f, "unregistered event {name}"),
177            Self::EventChannelClosed => f.write_str("event channel is closed"),
178            Self::NestedRun => f.write_str("nested world execution is not supported"),
179        }
180    }
181}
182
183#[cfg(feature = "std")]
184impl std::error::Error for WorldError {
185    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
186        match self {
187            Self::Registration(error) => Some(error),
188            Self::Allocator(error) => Some(error),
189            Self::Flush(error) => Some(error),
190            _ => None,
191        }
192    }
193}
194
195#[cfg(feature = "std")]
196impl core::fmt::Display for EventReadError {
197    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
198        match self {
199            Self::Lagged { dropped } => write!(f, "event reader lagged by {dropped} events"),
200            Self::ChannelClosed => f.write_str("event channel is closed"),
201            Self::UnregisteredEvent { name } => write!(f, "unregistered event {name}"),
202            Self::OwnerMismatch { name } => write!(f, "event reader owner mismatch for {name}"),
203        }
204    }
205}
206
207#[cfg(feature = "std")]
208impl std::error::Error for EventReadError {}