Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_ecs/src/schedule/error.rs
6849 views
1
use alloc::{format, string::String, vec::Vec};
2
use core::fmt::Write as _;
3
4
use thiserror::Error;
5
6
use crate::{
7
component::{ComponentId, Components},
8
schedule::{graph::GraphNodeId, NodeId, ScheduleGraph, SystemKey, SystemSetKey},
9
world::World,
10
};
11
12
/// Category of errors encountered during [`Schedule::initialize`](crate::schedule::Schedule::initialize).
13
#[non_exhaustive]
14
#[derive(Error, Debug)]
15
pub enum ScheduleBuildError {
16
/// A system set contains itself.
17
#[error("System set `{0:?}` contains itself.")]
18
HierarchyLoop(NodeId),
19
/// The hierarchy of system sets contains a cycle.
20
#[error("The hierarchy of system sets contains a cycle: {0:?}")]
21
HierarchyCycle(Vec<Vec<NodeId>>),
22
/// A system (set) has been told to run before itself.
23
#[error("`{0:?}` has been told to run before itself.")]
24
DependencyLoop(NodeId),
25
/// The dependency graph contains a cycle.
26
#[error("The dependency graph contains a cycle: {0:?}")]
27
DependencyCycle(Vec<Vec<NodeId>>),
28
/// Tried to order a system (set) relative to a system set it belongs to.
29
#[error("`{0:?}` and `{1:?}` have both `in_set` and `before`-`after` relationships (these might be transitive). This combination is unsolvable as a system cannot run before or after a set it belongs to.")]
30
CrossDependency(NodeId, NodeId),
31
/// Tried to order system sets that share systems.
32
#[error("`{0:?}` and `{1:?}` have a `before`-`after` relationship (which may be transitive) but share systems.")]
33
SetsHaveOrderButIntersect(SystemSetKey, SystemSetKey),
34
/// Tried to order a system (set) relative to all instances of some system function.
35
#[error("Tried to order against `{0:?}` in a schedule that has more than one `{0:?}` instance. `{0:?}` is a `SystemTypeSet` and cannot be used for ordering if ambiguous. Use a different set without this restriction.")]
36
SystemTypeSetAmbiguity(SystemSetKey),
37
/// Tried to run a schedule before all of its systems have been initialized.
38
#[error("Tried to run a schedule before all of its systems have been initialized.")]
39
Uninitialized,
40
/// A warning that was elevated to an error.
41
#[error(transparent)]
42
Elevated(#[from] ScheduleBuildWarning),
43
}
44
45
/// Category of warnings encountered during [`Schedule::initialize`](crate::schedule::Schedule::initialize).
46
#[non_exhaustive]
47
#[derive(Error, Debug)]
48
pub enum ScheduleBuildWarning {
49
/// The hierarchy of system sets contains redundant edges.
50
///
51
/// This warning is **enabled** by default, but can be disabled by setting
52
/// [`ScheduleBuildSettings::hierarchy_detection`] to [`LogLevel::Ignore`]
53
/// or upgraded to a [`ScheduleBuildError`] by setting it to [`LogLevel::Error`].
54
///
55
/// [`ScheduleBuildSettings::hierarchy_detection`]: crate::schedule::ScheduleBuildSettings::hierarchy_detection
56
/// [`LogLevel::Ignore`]: crate::schedule::LogLevel::Ignore
57
/// [`LogLevel::Error`]: crate::schedule::LogLevel::Error
58
#[error("The hierarchy of system sets contains redundant edges: {0:?}")]
59
HierarchyRedundancy(Vec<(NodeId, NodeId)>),
60
/// Systems with conflicting access have indeterminate run order.
61
///
62
/// This warning is **disabled** by default, but can be enabled by setting
63
/// [`ScheduleBuildSettings::ambiguity_detection`] to [`LogLevel::Warn`]
64
/// or upgraded to a [`ScheduleBuildError`] by setting it to [`LogLevel::Error`].
65
///
66
/// [`ScheduleBuildSettings::ambiguity_detection`]: crate::schedule::ScheduleBuildSettings::ambiguity_detection
67
/// [`LogLevel::Warn`]: crate::schedule::LogLevel::Warn
68
/// [`LogLevel::Error`]: crate::schedule::LogLevel::Error
69
#[error("Systems with conflicting access have indeterminate run order: {0:?}")]
70
Ambiguity(Vec<(SystemKey, SystemKey, Vec<ComponentId>)>),
71
}
72
73
impl ScheduleBuildError {
74
/// Renders the error as a human-readable string with node identifiers
75
/// replaced with their names.
76
///
77
/// The given `graph` and `world` are used to resolve the names of the nodes
78
/// and components involved in the error. The same `graph` and `world`
79
/// should be used as those used to [`initialize`] the [`Schedule`]. Failure
80
/// to do so will result in incorrect or incomplete error messages.
81
///
82
/// [`initialize`]: crate::schedule::Schedule::initialize
83
/// [`Schedule`]: crate::schedule::Schedule
84
pub fn to_string(&self, graph: &ScheduleGraph, world: &World) -> String {
85
match self {
86
ScheduleBuildError::HierarchyLoop(node_id) => {
87
Self::hierarchy_loop_to_string(node_id, graph)
88
}
89
ScheduleBuildError::HierarchyCycle(cycles) => {
90
Self::hierarchy_cycle_to_string(cycles, graph)
91
}
92
ScheduleBuildError::DependencyLoop(node_id) => {
93
Self::dependency_loop_to_string(node_id, graph)
94
}
95
ScheduleBuildError::DependencyCycle(cycles) => {
96
Self::dependency_cycle_to_string(cycles, graph)
97
}
98
ScheduleBuildError::CrossDependency(a, b) => {
99
Self::cross_dependency_to_string(a, b, graph)
100
}
101
ScheduleBuildError::SetsHaveOrderButIntersect(a, b) => {
102
Self::sets_have_order_but_intersect_to_string(a, b, graph)
103
}
104
ScheduleBuildError::SystemTypeSetAmbiguity(set) => {
105
Self::system_type_set_ambiguity_to_string(set, graph)
106
}
107
ScheduleBuildError::Uninitialized => Self::uninitialized_to_string(),
108
ScheduleBuildError::Elevated(e) => e.to_string(graph, world),
109
}
110
}
111
112
fn hierarchy_loop_to_string(node_id: &NodeId, graph: &ScheduleGraph) -> String {
113
format!(
114
"{} `{}` contains itself",
115
node_id.kind(),
116
graph.get_node_name(node_id)
117
)
118
}
119
120
fn hierarchy_cycle_to_string(cycles: &[Vec<NodeId>], graph: &ScheduleGraph) -> String {
121
let mut message = format!("schedule has {} in_set cycle(s):\n", cycles.len());
122
for (i, cycle) in cycles.iter().enumerate() {
123
let mut names = cycle.iter().map(|id| (id.kind(), graph.get_node_name(id)));
124
let (first_kind, first_name) = names.next().unwrap();
125
writeln!(
126
message,
127
"cycle {}: {first_kind} `{first_name}` contains itself",
128
i + 1,
129
)
130
.unwrap();
131
writeln!(message, "{first_kind} `{first_name}`").unwrap();
132
for (kind, name) in names.chain(core::iter::once((first_kind, first_name))) {
133
writeln!(message, " ... which contains {kind} `{name}`").unwrap();
134
}
135
writeln!(message).unwrap();
136
}
137
message
138
}
139
140
fn hierarchy_redundancy_to_string(
141
transitive_edges: &[(NodeId, NodeId)],
142
graph: &ScheduleGraph,
143
) -> String {
144
let mut message = String::from("hierarchy contains redundant edge(s)");
145
for (parent, child) in transitive_edges {
146
writeln!(
147
message,
148
" -- {} `{}` cannot be child of {} `{}`, longer path exists",
149
child.kind(),
150
graph.get_node_name(child),
151
parent.kind(),
152
graph.get_node_name(parent),
153
)
154
.unwrap();
155
}
156
message
157
}
158
159
fn dependency_loop_to_string(node_id: &NodeId, graph: &ScheduleGraph) -> String {
160
format!(
161
"{} `{}` has been told to run before itself",
162
node_id.kind(),
163
graph.get_node_name(node_id)
164
)
165
}
166
167
fn dependency_cycle_to_string(cycles: &[Vec<NodeId>], graph: &ScheduleGraph) -> String {
168
let mut message = format!("schedule has {} before/after cycle(s):\n", cycles.len());
169
for (i, cycle) in cycles.iter().enumerate() {
170
let mut names = cycle.iter().map(|id| (id.kind(), graph.get_node_name(id)));
171
let (first_kind, first_name) = names.next().unwrap();
172
writeln!(
173
message,
174
"cycle {}: {first_kind} `{first_name}` must run before itself",
175
i + 1,
176
)
177
.unwrap();
178
writeln!(message, "{first_kind} `{first_name}`").unwrap();
179
for (kind, name) in names.chain(core::iter::once((first_kind, first_name))) {
180
writeln!(message, " ... which must run before {kind} `{name}`").unwrap();
181
}
182
writeln!(message).unwrap();
183
}
184
message
185
}
186
187
fn cross_dependency_to_string(a: &NodeId, b: &NodeId, graph: &ScheduleGraph) -> String {
188
format!(
189
"{} `{}` and {} `{}` have both `in_set` and `before`-`after` relationships (these might be transitive). \
190
This combination is unsolvable as a system cannot run before or after a set it belongs to.",
191
a.kind(),
192
graph.get_node_name(a),
193
b.kind(),
194
graph.get_node_name(b)
195
)
196
}
197
198
fn sets_have_order_but_intersect_to_string(
199
a: &SystemSetKey,
200
b: &SystemSetKey,
201
graph: &ScheduleGraph,
202
) -> String {
203
format!(
204
"`{}` and `{}` have a `before`-`after` relationship (which may be transitive) but share systems.",
205
graph.get_node_name(&NodeId::Set(*a)),
206
graph.get_node_name(&NodeId::Set(*b)),
207
)
208
}
209
210
fn system_type_set_ambiguity_to_string(set: &SystemSetKey, graph: &ScheduleGraph) -> String {
211
let name = graph.get_node_name(&NodeId::Set(*set));
212
format!(
213
"Tried to order against `{name}` in a schedule that has more than one `{name}` instance. `{name}` is a \
214
`SystemTypeSet` and cannot be used for ordering if ambiguous. Use a different set without this restriction."
215
)
216
}
217
218
pub(crate) fn ambiguity_to_string(
219
ambiguities: &[(SystemKey, SystemKey, Vec<ComponentId>)],
220
graph: &ScheduleGraph,
221
components: &Components,
222
) -> String {
223
let n_ambiguities = ambiguities.len();
224
let mut message = format!(
225
"{n_ambiguities} pairs of systems with conflicting data access have indeterminate execution order. \
226
Consider adding `before`, `after`, or `ambiguous_with` relationships between these:\n",
227
);
228
let ambiguities = graph.conflicts_to_string(ambiguities, components);
229
for (name_a, name_b, conflicts) in ambiguities {
230
writeln!(message, " -- {name_a} and {name_b}").unwrap();
231
232
if !conflicts.is_empty() {
233
writeln!(message, " conflict on: {conflicts:?}").unwrap();
234
} else {
235
// one or both systems must be exclusive
236
let world = core::any::type_name::<World>();
237
writeln!(message, " conflict on: {world}").unwrap();
238
}
239
}
240
message
241
}
242
243
fn uninitialized_to_string() -> String {
244
String::from("tried to run a schedule before all of its systems have been initialized")
245
}
246
}
247
248
impl ScheduleBuildWarning {
249
/// Renders the warning as a human-readable string with node identifiers
250
/// replaced with their names.
251
pub fn to_string(&self, graph: &ScheduleGraph, world: &World) -> String {
252
match self {
253
ScheduleBuildWarning::HierarchyRedundancy(transitive_edges) => {
254
ScheduleBuildError::hierarchy_redundancy_to_string(transitive_edges, graph)
255
}
256
ScheduleBuildWarning::Ambiguity(ambiguities) => {
257
ScheduleBuildError::ambiguity_to_string(ambiguities, graph, world.components())
258
}
259
}
260
}
261
}
262
263