unlit_ecs/error.rs
1//! The errors an access that named an entity or an archetype answers with.
2//!
3//! Every lookup that names an entity answers [`Result`] rather than `Option`.
4//! A handle travels over wires, save files and scope boundaries, so a lookup can
5//! be turned down for reasons a caller has to tell apart: the entity is gone, it
6//! is alive but out of the scope's reach, or its archetype does not carry what
7//! was asked for. `Option` collapses all of them into one silence.
8//!
9//! A structural change is answered the same way. Despawning and relinking are
10//! turned down for reasons of their own - the entity is out of the subtree, it is
11//! shared, it is scaffolding - and a caller that was refused has to know which it
12//! was: the answer to "not yours" is to ask the scope that owns it, and the answer
13//! to "scaffolding" is to stop asking.
14//!
15//! A borrow conflict is deliberately not one of these. It says nothing about the
16//! handle, only that another holder has already claimed the component, and it
17//! stays a panic; see the module documentation of [`crate`].
18
19use crate::Entity;
20
21/// Why a lookup of, or through, an entity handle was turned down.
22///
23/// The handle is carried by every variant, so a caller that reports the error
24/// does not have to thread it alongside.
25#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
26#[non_exhaustive]
27pub enum EntityAccessError {
28 /// No live entity has this handle: it was despawned, or it never named one.
29 #[error("{entity:?} names no live entity")]
30 NotAlive {
31 /// The handle that was looked up.
32 entity: Entity,
33 },
34
35 /// The entity is alive, but out of this scope's reach.
36 #[error("{entity:?} is not visible from {scope:?}")]
37 NotVisible {
38 /// The handle that was looked up.
39 entity: Entity,
40 /// The scope it was looked up from.
41 scope: Entity,
42 },
43
44 /// The entity is visible here, but its archetype does not satisfy the
45 /// query it was looked up with.
46 #[error("{entity:?} does not match the query")]
47 NotMatched {
48 /// The handle that was looked up.
49 entity: Entity,
50 },
51
52 /// The handle was not reserved, so there is no row to spawn on.
53 ///
54 /// A handle from [`crate::World::reserve_entity`] can be spawned on once.
55 /// Spawning on it again, on a handle that was released, or on one that was
56 /// never reserved would leave a row nobody can reach or free.
57 #[error("{entity:?} was not reserved")]
58 NotReserved {
59 /// The handle that was spawned on.
60 entity: Entity,
61 },
62
63 /// The entity is alive, but this scope's subtree does not reach it, so it
64 /// is not the scope's to remove or move.
65 ///
66 /// Despawning and relinking walk the subtree, so what a scope cannot reach
67 /// from inside it is also not its to change. The scope that owns the entity
68 /// - the root, for anything beside this one - is the one to ask.
69 #[error("{entity:?} is outside {scope:?}, so it is not its to move or remove")]
70 NotOwned {
71 /// The handle that was moved or removed.
72 entity: Entity,
73 /// The scope that tried.
74 scope: Entity,
75 },
76
77 /// The entity is visible here, but only the root may move or remove it.
78 ///
79 /// The shared subtree is readable from every scope and rearrangeable by none
80 /// but the root: a scope that reads a resource there is a borrower, not an
81 /// owner.
82 #[error("{entity:?} is shared, so only the root may move or remove it")]
83 NotMovable {
84 /// The handle that was moved or removed.
85 entity: Entity,
86 /// The scope that tried.
87 scope: Entity,
88 },
89
90 /// The entity is one of the tree's two scaffolding entities.
91 ///
92 /// The root and the shared scope are what the tree hangs from: they are
93 /// neither counted nor queried, and they do not move or go.
94 #[error("{entity:?} is scaffolding: it neither moves nor goes")]
95 Scaffolding {
96 /// The handle that was moved or removed.
97 entity: Entity,
98 },
99
100 /// Moving `child` under `parent` would put a scope inside its own subtree,
101 /// which would cut the tree in two.
102 #[error("{child:?} cannot be moved under {parent:?}, which it holds")]
103 WouldCycle {
104 /// The handle that would have moved.
105 child: Entity,
106 /// The parent it would have moved under.
107 parent: Entity,
108 },
109
110 /// The entity's archetype could not be reached.
111 #[error(transparent)]
112 Archetype(#[from] ArchetypeAccessError),
113}
114
115/// Why a lookup by archetype id was turned down.
116///
117/// An archetype id is the row it was registered at, so a stale id keeps naming
118/// a different archetype instead of naming nothing. These variants are what a
119/// caller checks against.
120#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
121#[non_exhaustive]
122pub enum ArchetypeAccessError {
123 /// No archetype was ever registered under this id.
124 #[error("no archetype has id {id}")]
125 NoSuchArchetype {
126 /// The id that was looked up.
127 id: u32,
128 },
129
130 /// The archetype exists, but it holds no row to look at.
131 #[error("archetype {id} holds no row")]
132 Empty {
133 /// The id that was looked up.
134 id: u32,
135 },
136
137 /// The archetype holds rows this scope cannot reach.
138 #[error("archetype {id} holds rows that {scope:?} cannot see")]
139 NotVisible {
140 /// The id that was looked up.
141 id: u32,
142 /// The scope the lookup was made from.
143 scope: Entity,
144 },
145}