Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic system for specifying and validating archetype invariants #5121

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
7afcc27
Add basic types for archetype invariants
sixfold-origami Jun 21, 2022
c4dc9e2
Add docs for Components init methods
sixfold-origami Jun 21, 2022
22b7698
Add archetype invariants to World
sixfold-origami Jun 21, 2022
fbb2d54
Add basic archetype invariant helper
sixfold-origami Jun 21, 2022
df94ac3
Recheck archetype invariants when a new invariant is added
sixfold-origami Jun 22, 2022
7dde3e8
Refactor to use both typed and untyped archetype invariants // WIP
sixfold-origami Jun 22, 2022
ff169fe
Rework API to use bundles and raw types
sixfold-origami Jun 24, 2022
177afe6
Add `archetype_invariants` to `Archetype::new`
sixfold-origami Jun 24, 2022
cc1f9c1
Add archetype checking skeleton
sixfold-origami Jun 24, 2022
3a88bb8
Add naieve archetype checking
sixfold-origami Jun 24, 2022
dc3d834
Remove superfluous comment
sixfold-origami Jun 24, 2022
842f896
Run cargo fmt and clippy
sixfold-origami Jun 26, 2022
2f64784
Check all archetypes whenever a new invariant is added
sixfold-origami Jun 26, 2022
d9a38c8
Improve error messages on archetype invariant failure
sixfold-origami Jun 26, 2022
6f75a66
Change `BTreeSet`s to use `HashSet` instead
sixfold-origami Jun 26, 2022
541111f
Add `AtMostOneOf` variant
sixfold-origami Jun 27, 2022
6914e06
Run cargo fmt and cargo clippy
sixfold-origami Jun 27, 2022
5b82d09
Fix docstring type references
sixfold-origami Jun 28, 2022
2776b48
Apply documentation suggestions from code review
sixfold-origami Jul 1, 2022
347db39
Improve docstrings and names
sixfold-origami Jul 1, 2022
5eceedb
Add `Only` variant to `ArchetypeStatement`
sixfold-origami Jul 1, 2022
67ff12e
Fix module imports
sixfold-origami Jul 1, 2022
7a5d757
Fix test names
sixfold-origami Jul 2, 2022
b10e954
Fix docstring for `ArchetypeInvariants.add`
sixfold-origami Jul 2, 2022
2bc2cfa
Change `predicate` to `premise`
sixfold-origami Jul 2, 2022
a8716cc
Rename `AtLeastOneOf` to `AnyOf`
sixfold-origami Jul 2, 2022
3e06385
Add helper methods and tests for common invariants
sixfold-origami Jul 2, 2022
702ef4e
Add `add_archetype_invariant` methods to `App`
sixfold-origami Jul 2, 2022
1ff1299
Add archetype rechecking when adding an untyped archetype invariant
sixfold-origami Jul 2, 2022
5d80c9f
Improve error messages when archetypes fail
sixfold-origami Jul 2, 2022
db0927d
Add archetype invariant types to the prelude
sixfold-origami Jul 2, 2022
01d50f9
Fix format and clippy errors
sixfold-origami Jul 2, 2022
f8a23fc
Use `get_short_name` to further improve error messages
sixfold-origami Jul 2, 2022
9d63648
Apply suggestions from code review - fix docstrings
sixfold-origami Jul 2, 2022
b293063
Rename exhaustive to exclusive
sixfold-origami Jul 2, 2022
4c1759a
Apply suggestions from code review - fix docstrings
sixfold-origami Jul 4, 2022
22b6af1
Remove warning when constructing `ArchetypeStatement::AnyOf` with a s…
sixfold-origami Jul 4, 2022
5c0b806
Add basic example // WIP
sixfold-origami Jul 4, 2022
7e76275
Add static true and false variants to `ArchetypeStatement`
sixfold-origami Jul 4, 2022
5ec538d
Rename `requires_all` to `requires`
sixfold-origami Jul 4, 2022
52cd331
Clean up example
sixfold-origami Jul 4, 2022
5b20079
Add example metadata and transitivity information
sixfold-origami Jul 4, 2022
38123ca
Clean up docs for archetype statements
sixfold-origami Jul 4, 2022
4109138
Clean up docs around `UntypedArchetypeInvariant`
sixfold-origami Jul 4, 2022
95428eb
Run cargo fmt
sixfold-origami Jul 4, 2022
011bd2e
Update example name
sixfold-origami Jul 4, 2022
9bb0cf4
Clean up example
sixfold-origami Jul 6, 2022
0a920cb
Remove use of "tautologically true", replace with "always true"
sixfold-origami Jul 6, 2022
97a33e6
Fix error message for on_insert failures, clean up error display logic
sixfold-origami Jul 6, 2022
07dce54
Apply suggestions from code review - clean up docstrings and comments
sixfold-origami Jul 8, 2022
16bc245
Remove `With` parameter from example
sixfold-origami Jul 8, 2022
5ded872
Add trailing period to docstring
sixfold-origami Jul 11, 2022
6691081
Make archetype testing functions pub(crate) instead of pub
sixfold-origami Jul 11, 2022
966cdde
Merge remote-tracking branch 'plof27/basic-archetype-invariants' into…
sixfold-origami Jul 11, 2022
835f7cd
Remove references to how to get `component_ids`
sixfold-origami Jul 27, 2022
3649696
Fix statement docs
sixfold-origami Jul 29, 2022
21a1a23
Rework helper method API to be defined on Bundles
sixfold-origami Jul 29, 2022
190e7c9
Merge branch 'main' into basic-archetype-invariants
sixfold-origami Oct 8, 2022
1acce11
Fix merge issues
sixfold-origami Oct 8, 2022
ed16ff7
🥺
sixfold-origami Oct 8, 2022
b7b7d47
Update docstrings to match 🥺
sixfold-origami Oct 8, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,16 @@ description = "Full guide to Bevy's ECS"
category = "ECS (Entity Component System)"
wasm = false

[[example]]
name = "archetype_invariants"
path = "examples/ecs/archetype_invariants.rs"

[package.metadata.example.archetype_invariants]
name = "Archetype Invariants"
description = "Assertions about valid combinations of components"
category = "ECS (Entity Component System)"
wasm = false

[[example]]
name = "component_change_detection"
path = "examples/ecs/component_change_detection.rs"
Expand Down
30 changes: 28 additions & 2 deletions crates/bevy_app/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ pub use bevy_derive::AppLabel;
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
event::{Event, Events},
prelude::FromWorld,
prelude::{Bundle, FromWorld},
schedule::{
IntoSystemDescriptor, Schedule, ShouldRun, Stage, StageLabel, State, StateData, SystemSet,
SystemStage,
},
system::Resource,
world::World,
world::{ArchetypeInvariant, UntypedArchetypeInvariant, World},
};
use bevy_utils::{tracing::debug, HashMap};
use std::fmt::Debug;
Expand Down Expand Up @@ -141,6 +141,32 @@ impl App {
(runner)(app);
}

/// Adds a new [`ArchetypeInvariant`] to this app's [`World`].
///
/// Whenever a new archetype invariant is added to a world, all existing archetypes are re-checked.
/// This may include empty archetypes- archetypes that contain no entities.
pub fn add_archetype_invariant<B1: Bundle, B2: Bundle>(
&mut self,
archetype_invariant: ArchetypeInvariant<B1, B2>,
) -> &mut Self {
self.world.add_archetype_invariant(archetype_invariant);
self
}

/// Inserts a new [`UntypedArchetypeInvariant`] to this app's [`World`].
///
/// Whenever a new archetype invariant is added to a world, all existing archetypes are re-checked.
/// This may include empty archetypes- archetypes that contain no entities.
/// Prefer [`add_archetype_invariant`](App::add_archetype_invariant) where possible.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be worth it to add why this is preferred

pub fn add_untyped_archetype_invariant(
&mut self,
archetype_invariant: UntypedArchetypeInvariant,
) -> &mut Self {
self.world
.add_untyped_archetype_invariant(archetype_invariant);
self
}

/// Adds a [`Stage`] with the given `label` to the last position of the app's
/// [`Schedule`].
///
Expand Down
31 changes: 25 additions & 6 deletions crates/bevy_ecs/src/archetype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@

use crate::{
bundle::BundleId,
component::{ComponentId, StorageType},
component::{ComponentId, Components, StorageType},
entity::{Entity, EntityLocation},
storage::{Column, SparseArray, SparseSet, SparseSetIndex, TableId},
world::ArchetypeInvariants,
};
use std::{
collections::HashMap,
Expand Down Expand Up @@ -145,20 +146,23 @@ pub struct Archetype {
}

impl Archetype {
#[allow(clippy::too_many_arguments)]
pub fn new(
id: ArchetypeId,
table_id: TableId,
table_components: Box<[ComponentId]>,
sparse_set_components: Box<[ComponentId]>,
table_archetype_components: Vec<ArchetypeComponentId>,
sparse_set_archetype_components: Vec<ArchetypeComponentId>,
archetype_invariants: &ArchetypeInvariants,
components: &Components,
) -> Self {
let mut components =
let mut component_set =
SparseSet::with_capacity(table_components.len() + sparse_set_components.len());
for (component_id, archetype_component_id) in
table_components.iter().zip(table_archetype_components)
{
components.insert(
component_set.insert(
*component_id,
ArchetypeComponentInfo {
storage_type: StorageType::Table,
Expand All @@ -171,21 +175,24 @@ impl Archetype {
.iter()
.zip(sparse_set_archetype_components)
{
components.insert(
component_set.insert(
*component_id,
ArchetypeComponentInfo {
storage_type: StorageType::SparseSet,
archetype_component_id,
},
);
}

archetype_invariants.test_archetype(component_set.indices(), components);

Self {
id,
table_info: TableInfo {
id: table_id,
entity_rows: Default::default(),
},
components,
components: component_set,
table_components,
sparse_set_components,
unique_components: SparseSet::new(),
Expand Down Expand Up @@ -391,7 +398,13 @@ impl Default for Archetypes {
archetype_ids: Default::default(),
archetype_component_count: 0,
};
archetypes.get_id_or_insert(TableId::empty(), Vec::new(), Vec::new());
archetypes.get_id_or_insert(
TableId::empty(),
Vec::new(),
Vec::new(),
&ArchetypeInvariants::default(),
&Components::default(),
);

// adds the resource archetype. it is "special" in that it is inaccessible via a "hash",
// which prevents entities from being added to it
Expand All @@ -402,6 +415,8 @@ impl Default for Archetypes {
Box::new([]),
Vec::new(),
Vec::new(),
&ArchetypeInvariants::default(),
&Components::default(),
));
archetypes
}
Expand Down Expand Up @@ -488,6 +503,8 @@ impl Archetypes {
table_id: TableId,
table_components: Vec<ComponentId>,
sparse_set_components: Vec<ComponentId>,
archetype_invariants: &ArchetypeInvariants,
components: &Components,
) -> ArchetypeId {
let table_components = table_components.into_boxed_slice();
let sparse_set_components = sparse_set_components.into_boxed_slice();
Expand Down Expand Up @@ -521,6 +538,8 @@ impl Archetypes {
sparse_set_components,
table_archetype_components,
sparse_set_archetype_components,
archetype_invariants,
components,
));
id
})
Expand Down
32 changes: 26 additions & 6 deletions crates/bevy_ecs/src/bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::{
component::{Component, ComponentId, ComponentTicks, Components, StorageType},
entity::{Entities, Entity, EntityLocation},
storage::{SparseSetIndex, SparseSets, Storages, Table},
world::ArchetypeInvariants,
};
use bevy_ecs_macros::all_tuples;
use bevy_ptr::OwningPtr;
Expand Down Expand Up @@ -260,6 +261,7 @@ impl BundleInfo {
&self.storage_types
}

#[allow(clippy::too_many_arguments)]
pub(crate) fn get_bundle_inserter<'a, 'b>(
&'b self,
entities: &'a mut Entities,
Expand All @@ -268,9 +270,15 @@ impl BundleInfo {
storages: &'a mut Storages,
archetype_id: ArchetypeId,
change_tick: u32,
archetype_invariants: &ArchetypeInvariants,
) -> BundleInserter<'a, 'b> {
let new_archetype_id =
self.add_bundle_to_archetype(archetypes, storages, components, archetype_id);
let new_archetype_id = self.add_bundle_to_archetype(
archetypes,
storages,
components,
archetype_id,
archetype_invariants,
);
let archetypes_ptr = archetypes.archetypes.as_mut_ptr();
if new_archetype_id == archetype_id {
let archetype = &mut archetypes[archetype_id];
Expand Down Expand Up @@ -327,9 +335,15 @@ impl BundleInfo {
components: &mut Components,
storages: &'a mut Storages,
change_tick: u32,
archetype_invariants: &ArchetypeInvariants,
) -> BundleSpawner<'a, 'b> {
let new_archetype_id =
self.add_bundle_to_archetype(archetypes, storages, components, ArchetypeId::EMPTY);
let new_archetype_id = self.add_bundle_to_archetype(
archetypes,
storages,
components,
ArchetypeId::EMPTY,
archetype_invariants,
);
let (empty_archetype, archetype) =
archetypes.get_2_mut(ArchetypeId::EMPTY, new_archetype_id);
let table = &mut storages.tables[archetype.table_id()];
Expand Down Expand Up @@ -399,6 +413,7 @@ impl BundleInfo {
storages: &mut Storages,
components: &mut Components,
archetype_id: ArchetypeId,
archetype_invariants: &ArchetypeInvariants,
) -> ArchetypeId {
if let Some(add_bundle) = archetypes[archetype_id].edges().get_add_bundle(self.id) {
return add_bundle.archetype_id;
Expand Down Expand Up @@ -461,8 +476,13 @@ impl BundleInfo {
new_sparse_set_components
};
};
let new_archetype_id =
archetypes.get_id_or_insert(table_id, table_components, sparse_set_components);
let new_archetype_id = archetypes.get_id_or_insert(
table_id,
table_components,
sparse_set_components,
archetype_invariants,
components,
);
// add an edge from the old archetype to the new archetype
archetypes[archetype_id].edges_mut().insert_add_bundle(
self.id,
Expand Down
21 changes: 21 additions & 0 deletions crates/bevy_ecs/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::{
};
pub use bevy_ecs_macros::Component;
use bevy_ptr::OwningPtr;
use bevy_utils::get_short_name;
use std::{
alloc::Layout,
any::{Any, TypeId},
Expand Down Expand Up @@ -251,6 +252,22 @@ impl SparseSetIndex for ComponentId {
}
}

/// Returns the shortened type names of the provided [`ComponentId`]s.
///
/// Uses [`get_short_name`] to strip the module paths of the items, resulting in cleaner lists.
pub fn display_component_id_types<'a, I: Iterator<Item = &'a ComponentId>>(
component_ids: I,
components: &Components,
) -> String {
component_ids
.map(|id| match components.get_info(*id) {
Some(info) => get_short_name(info.name()),
None => format!("{:?}", id),
})
.reduce(|acc, s| format!("{}, {}", acc, s))
.unwrap_or_default()
}

pub struct ComponentDescriptor {
name: Cow<'static, str>,
// SAFETY: This must remain private. It must match the statically known StorageType of the
Expand Down Expand Up @@ -370,6 +387,9 @@ pub struct Components {
}

impl Components {
/// Adds a new component type to [`Components`].
///
/// If the component type is already present, it simply returns its [`ComponentId`].
#[inline]
pub fn init_component<T: Component>(&mut self, storages: &mut Storages) -> ComponentId {
let type_id = TypeId::of::<T>();
Expand All @@ -385,6 +405,7 @@ impl Components {
ComponentId(*index)
}

/// Adds a new component with the provided [`ComponentDescriptor`] to [`Components`].
pub fn init_component_with_descriptor(
&mut self,
storages: &mut Storages,
Expand Down
5 changes: 4 additions & 1 deletion crates/bevy_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ pub mod prelude {
NonSendMut, ParallelCommands, ParamSet, Query, RemovedComponents, Res, ResMut,
Resource, System, SystemParamFunction,
},
world::{FromWorld, Mut, World},
world::{
ArchetypeInvariant, ArchetypeInvariantHelpers, ArchetypeStatement, FromWorld, Mut,
World,
},
};
}

Expand Down
Loading