use bevy_app::{App, Plugin, PostUpdate};
use bevy_ecs::entity::EntityHashSet;
use bevy_ecs::prelude::*;
use bevy_platform::{collections::HashMap, prelude::*};
use bevy_transform::prelude::*;
use crate::{grid::Grid, BigSpace, CellCoord, FloatingOrigin};
struct ValidationStackEntry {
parent_node: Box<dyn ValidHierarchyNode>,
children: Vec<Entity>,
}
pub struct BigSpaceValidationPlugin;
impl Plugin for BigSpaceValidationPlugin {
fn build(&self, app: &mut App) {
app.add_systems(
PostUpdate,
validate_hierarchy::<SpatialHierarchyRoot>.after(TransformSystems::Propagate),
);
}
}
#[derive(Default, Resource)]
struct ValidatorCaches {
query_state_cache: HashMap<&'static str, QueryState<(Entity, Option<&'static Children>)>>,
validator_cache: HashMap<&'static str, Vec<Box<dyn ValidHierarchyNode>>>,
root_query: Option<QueryState<Entity, Without<ChildOf>>>,
stack: Vec<ValidationStackEntry>,
error_entities: EntityHashSet,
}
pub fn validate_hierarchy<V: 'static + ValidHierarchyNode + Default>(world: &mut World) {
world.init_resource::<ValidatorCaches>();
let mut caches = world.remove_resource::<ValidatorCaches>().unwrap();
let root_entities = caches
.root_query
.get_or_insert(world.query_filtered::<Entity, Without<ChildOf>>())
.iter(world)
.collect();
caches.stack.push(ValidationStackEntry {
parent_node: Box::<V>::default(),
children: root_entities,
});
while let Some(stack_entry) = caches.stack.pop() {
let mut validators_and_queries = caches
.validator_cache
.entry(stack_entry.parent_node.name())
.or_insert_with(|| stack_entry.parent_node.allowed_child_nodes())
.iter()
.map(|validator| {
let query = caches
.query_state_cache
.remove(validator.name())
.unwrap_or_else(|| {
let mut query_builder = QueryBuilder::new(world);
validator.match_self(&mut query_builder);
query_builder.build()
});
(validator, query)
})
.collect::<Vec<_>>();
for entity in stack_entry.children.iter() {
let query_result = validators_and_queries
.iter_mut()
.find_map(|(validator, query)| {
query.get(world, *entity).ok().map(|res| (validator, res.1))
});
match query_result {
Some((validator, Some(children))) => {
caches.stack.push(ValidationStackEntry {
parent_node: validator.clone(),
children: children.to_vec(),
});
}
Some(_) => (), None => {
if caches.error_entities.contains(entity) {
continue; }
let mut possibilities = String::new();
stack_entry
.parent_node
.allowed_child_nodes()
.iter()
.for_each(|v| {
possibilities.push_str(" - ");
possibilities.push_str(v.name());
possibilities.push('\n');
});
let mut inspect = String::new();
world
.inspect_entity(*entity)
.into_iter()
.flatten()
.for_each(|info| {
inspect.push_str(" - ");
inspect.push_str(&info.name());
inspect.push('\n');
});
bevy_log::error!("
-------------------------------------------
big_space hierarchy validation error report
-------------------------------------------
Entity {:#} is a child of a {:#?}, but the components on this entity do not match any of the allowed archetypes for children of this parent.
Because it is a child of a {:#?}, the entity must be one of the following:
{}
However, the entity has the following components, which does not match any of the allowed archetypes listed above:
{}
If possible, use commands.spawn_big_space(), which prevents these errors, instead of manually assembling a hierarchy. See {} for details.", entity, stack_entry.parent_node.name(), stack_entry.parent_node.name(), possibilities, inspect, file!());
caches.error_entities.insert(*entity);
}
}
}
for (validator, query) in validators_and_queries.drain(..) {
caches.query_state_cache.insert(validator.name(), query);
}
}
world.insert_resource(caches);
}
pub trait ValidHierarchyNode: sealed::CloneHierarchy + Send + Sync {
fn name(&self) -> &'static str {
core::any::type_name::<Self>()
}
fn match_self(&self, query: &mut QueryBuilder<(Entity, Option<&Children>)>);
fn allowed_child_nodes(&self) -> Vec<Box<dyn ValidHierarchyNode>>;
}
mod sealed {
use super::ValidHierarchyNode;
use bevy_platform::prelude::*;
pub trait CloneHierarchy {
fn clone_box(&self) -> Box<dyn ValidHierarchyNode>;
}
impl<T> CloneHierarchy for T
where
T: 'static + ValidHierarchyNode + Clone,
{
fn clone_box(&self) -> Box<dyn ValidHierarchyNode> {
Box::new(self.clone())
}
}
impl Clone for Box<dyn ValidHierarchyNode> {
fn clone(&self) -> Self {
self.clone_box()
}
}
}
#[derive(Default, Clone)]
pub struct SpatialHierarchyRoot;
impl ValidHierarchyNode for SpatialHierarchyRoot {
fn name(&self) -> &'static str {
"Root"
}
fn match_self(&self, _: &mut QueryBuilder<(Entity, Option<&Children>)>) {}
fn allowed_child_nodes(&self) -> Vec<Box<dyn ValidHierarchyNode>> {
vec![
Box::<RootFrame>::default(),
Box::<RootSpatialLowPrecision>::default(),
Box::<AnyNonSpatial>::default(),
]
}
}
#[derive(Default, Clone)]
struct AnyNonSpatial;
impl ValidHierarchyNode for AnyNonSpatial {
fn name(&self) -> &'static str {
"Any non-spatial entity"
}
fn match_self(&self, query: &mut QueryBuilder<(Entity, Option<&Children>)>) {
query
.without::<CellCoord>()
.without::<Transform>()
.without::<GlobalTransform>()
.without::<BigSpace>()
.without::<Grid>()
.without::<FloatingOrigin>();
}
fn allowed_child_nodes(&self) -> Vec<Box<dyn ValidHierarchyNode>> {
vec![Box::<AnyNonSpatial>::default()]
}
}
#[derive(Default, Clone)]
struct RootFrame;
impl ValidHierarchyNode for RootFrame {
fn name(&self) -> &'static str {
"Root of a BigSpace"
}
fn match_self(&self, query: &mut QueryBuilder<(Entity, Option<&Children>)>) {
query
.with::<BigSpace>()
.with::<Grid>()
.with::<GlobalTransform>()
.without::<CellCoord>()
.without::<Transform>()
.without::<ChildOf>()
.without::<FloatingOrigin>();
}
fn allowed_child_nodes(&self) -> Vec<Box<dyn ValidHierarchyNode>> {
vec![
Box::<ChildFrame>::default(),
Box::<ChildSpatialLowPrecision>::default(),
Box::<ChildSpatialHighPrecision>::default(),
Box::<AnyNonSpatial>::default(),
]
}
}
#[derive(Default, Clone)]
struct RootSpatialLowPrecision;
impl ValidHierarchyNode for RootSpatialLowPrecision {
fn name(&self) -> &'static str {
"Root of a Transform hierarchy at the root of the tree outside of any BigSpace"
}
fn match_self(&self, query: &mut QueryBuilder<(Entity, Option<&Children>)>) {
query
.with::<Transform>()
.with::<GlobalTransform>()
.without::<CellCoord>()
.without::<BigSpace>()
.without::<Grid>()
.without::<ChildOf>()
.without::<FloatingOrigin>();
}
fn allowed_child_nodes(&self) -> Vec<Box<dyn ValidHierarchyNode>> {
vec![
Box::<ChildSpatialLowPrecision>::default(),
Box::<AnyNonSpatial>::default(),
]
}
}
#[derive(Default, Clone)]
struct ChildFrame;
impl ValidHierarchyNode for ChildFrame {
fn name(&self) -> &'static str {
"Non-root Grid"
}
fn match_self(&self, query: &mut QueryBuilder<(Entity, Option<&Children>)>) {
query
.with::<Grid>()
.with::<CellCoord>()
.with::<Transform>()
.with::<GlobalTransform>()
.with::<ChildOf>()
.without::<BigSpace>();
}
fn allowed_child_nodes(&self) -> Vec<Box<dyn ValidHierarchyNode>> {
vec![
Box::<ChildFrame>::default(),
Box::<ChildRootSpatialLowPrecision>::default(),
Box::<ChildSpatialHighPrecision>::default(),
Box::<AnyNonSpatial>::default(),
]
}
}
#[derive(Default, Clone)]
struct ChildRootSpatialLowPrecision;
impl ValidHierarchyNode for ChildRootSpatialLowPrecision {
fn name(&self) -> &'static str {
"Root of a low-precision Transform hierarchy, within a BigSpace"
}
fn match_self(&self, query: &mut QueryBuilder<(Entity, Option<&Children>)>) {
query
.with::<Transform>()
.with::<GlobalTransform>()
.with::<ChildOf>()
.with::<crate::grid::propagation::LowPrecisionRoot>()
.without::<CellCoord>()
.without::<BigSpace>()
.without::<Grid>()
.without::<FloatingOrigin>();
}
fn allowed_child_nodes(&self) -> Vec<Box<dyn ValidHierarchyNode>> {
vec![
Box::<ChildSpatialLowPrecision>::default(),
Box::<AnyNonSpatial>::default(),
]
}
}
#[derive(Default, Clone)]
struct ChildSpatialLowPrecision;
impl ValidHierarchyNode for ChildSpatialLowPrecision {
fn name(&self) -> &'static str {
"Non-root low-precision spatial entity"
}
fn match_self(&self, query: &mut QueryBuilder<(Entity, Option<&Children>)>) {
query
.with::<Transform>()
.with::<GlobalTransform>()
.with::<ChildOf>()
.without::<CellCoord>()
.without::<BigSpace>()
.without::<Grid>()
.without::<FloatingOrigin>();
}
fn allowed_child_nodes(&self) -> Vec<Box<dyn ValidHierarchyNode>> {
vec![
Box::<ChildSpatialLowPrecision>::default(),
Box::<AnyNonSpatial>::default(),
]
}
}
#[derive(Default, Clone)]
struct ChildSpatialHighPrecision;
impl ValidHierarchyNode for ChildSpatialHighPrecision {
fn name(&self) -> &'static str {
"Non-root high precision spatial entity"
}
fn match_self(&self, query: &mut QueryBuilder<(Entity, Option<&Children>)>) {
query
.with::<CellCoord>()
.with::<Transform>()
.with::<GlobalTransform>()
.with::<ChildOf>()
.without::<BigSpace>()
.without::<Grid>();
}
fn allowed_child_nodes(&self) -> Vec<Box<dyn ValidHierarchyNode>> {
vec![
Box::<ChildRootSpatialLowPrecision>::default(),
Box::<AnyNonSpatial>::default(),
]
}
}