Skip to content

Commit 5ac9021

Browse files
Add AtMostOneOf variant
This makes mutually exclusive bundles much easier to specify
1 parent d66f3e0 commit 5ac9021

File tree

1 file changed

+29
-1
lines changed

1 file changed

+29
-1
lines changed

crates/bevy_ecs/src/world/archetype_invariants.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ pub enum ArchetypeStatement<B: Bundle> {
7575
/// The entity has at least one component in the bundle `B`, and may have all of them.
7676
/// When using a single-component bundle, `AllOf` is preferred.
7777
AtLeastOneOf(PhantomData<B>),
78+
/// The entity has zero or one of the components in the bundle `B`, and may have all of them.
79+
/// When using a single-component bundle, this is a tautology.
80+
AtMostOneOf(PhantomData<B>),
7881
/// The entity has none of the components in the bundle `B`
7982
NoneOf(PhantomData<B>),
8083
}
@@ -95,6 +98,7 @@ impl<B: Bundle> ArchetypeStatement<B> {
9598
}
9699
UntypedArchetypeStatement::AtLeastOneOf(component_ids)
97100
}
101+
ArchetypeStatement::AtMostOneOf(_) => UntypedArchetypeStatement::AtMostOneOf(component_ids),
98102
ArchetypeStatement::NoneOf(_) => UntypedArchetypeStatement::NoneOf(component_ids),
99103
}
100104
}
@@ -111,6 +115,12 @@ impl<B: Bundle> ArchetypeStatement<B> {
111115
ArchetypeStatement::AtLeastOneOf(PhantomData)
112116
}
113117

118+
/// Constructs a new [`ArchetypeStatement::AtMostOneOf`] variant for all components stored in the bundle `B`
119+
#[inline]
120+
pub const fn at_most_one_of() -> Self {
121+
ArchetypeStatement::AtMostOneOf(PhantomData)
122+
}
123+
114124
/// Constructs a new [`ArchetypeStatement::NoneOf`] variant for all components stored in the bundle `B`
115125
#[inline]
116126
pub const fn none_of() -> Self {
@@ -119,6 +129,7 @@ impl<B: Bundle> ArchetypeStatement<B> {
119129
}
120130

121131
/// A type-erased version of [`ArchetypeInvariant`].
132+
///
122133
/// Intended to be used with dynamic components that cannot be represented with Rust types.
123134
/// Prefer [`ArchetypeInvariant`] when possible.
124135
#[derive(Clone, Debug, PartialEq)]
@@ -160,8 +171,11 @@ pub enum UntypedArchetypeStatement {
160171
/// Evaluates to true if and only if the entity has all of the components present in the set
161172
AllOf(HashSet<ComponentId>),
162173
/// The entity has at least one component in the set, and may have all of them.
163-
/// When using a single-component bundle, `AllOf` is preferred
174+
/// When using a single-component set, `AllOf` is preferred
164175
AtLeastOneOf(HashSet<ComponentId>),
176+
/// The entity has zero or one of the components in the set, and may have all of them.
177+
/// When using a single-component set, this is a tautology.
178+
AtMostOneOf(HashSet<ComponentId>),
165179
/// The entity has none of the components in the set
166180
NoneOf(HashSet<ComponentId>),
167181
}
@@ -172,6 +186,7 @@ impl UntypedArchetypeStatement {
172186
match self {
173187
UntypedArchetypeStatement::AllOf(set)
174188
| UntypedArchetypeStatement::AtLeastOneOf(set)
189+
| UntypedArchetypeStatement::AtMostOneOf(set)
175190
| UntypedArchetypeStatement::NoneOf(set) => set,
176191
}
177192
}
@@ -195,6 +210,19 @@ impl UntypedArchetypeStatement {
195210
}
196211
false
197212
}
213+
UntypedArchetypeStatement::AtMostOneOf(exclusive_ids) => {
214+
let mut found_previous = false;
215+
for exclusive_id in exclusive_ids {
216+
if component_ids.contains(exclusive_id) {
217+
if found_previous {
218+
return false
219+
} else {
220+
found_previous = true;
221+
}
222+
}
223+
}
224+
true
225+
}
198226
UntypedArchetypeStatement::NoneOf(forbidden_ids) => {
199227
for forbidden_id in forbidden_ids {
200228
if component_ids.contains(forbidden_id) {

0 commit comments

Comments
 (0)