use anyhow::Result;
use bevy::audio::DefaultSpatialScale;
use bevy::ecs::query::QueryFilter;
use bevy::pbr::DefaultOpaqueRendererMethod;
use bevy::pbr::DirectionalLightShadowMap;
use bevy::pbr::PointLightShadowMap;
use bevy::prelude::*;
use bevy::time::TimeUpdateStrategy;
pub fn get_save_entities<Q: QueryFilter>(world: &mut World) -> Vec<Entity> {
world.query_filtered::<Entity, Q>().iter(world).collect()
}
#[derive(Debug, Clone)]
pub struct DynamicSceneChecks {
pub resource_checks: bool,
pub entity_checks: bool,
pub component_checks: bool,
pub num_ignored_resources: usize,
pub allowed_ignores: Vec<String>,
pub on_build: fn(DynamicSceneBuilder) -> DynamicSceneBuilder,
}
impl Default for DynamicSceneChecks {
fn default() -> Self {
Self {
resource_checks: true,
entity_checks: true,
component_checks: true,
num_ignored_resources: 176,
allowed_ignores: vec![
"bevy_text::text::CosmicBuffer".to_string(),
"beet_flow::observers::action_observer_map::ActionObserverMap"
.to_string(),
"bevy_ecs::observer::entity_observer::ObservedBy".to_string(),
],
on_build: |builder| {
builder
.deny_resource::<ClearColor>()
.deny_resource::<AmbientLight>()
.deny_resource::<DirectionalLightShadowMap>()
.deny_resource::<PointLightShadowMap>()
.deny_resource::<GlobalVolume>()
.deny_resource::<DefaultOpaqueRendererMethod>()
.deny_resource::<Time>()
.deny_resource::<Time<Real>>()
.deny_resource::<Time<Virtual>>()
.deny_resource::<Time<Fixed>>()
.deny_resource::<TimeUpdateStrategy>()
.deny_resource::<DefaultSpatialScale>()
.deny_resource::<GizmoConfigStore>()
.deny_resource::<UiScale>()
.deny_resource::<PointerInputPlugin>()
.deny_resource::<PickingPlugin>()
.deny_resource::<bevy::a11y::Focus>()
.deny_resource::<MeshPickingSettings>()
},
}
}
}
impl DynamicSceneChecks {
pub fn new() -> Self { Self::default() }
pub fn on_build(
mut self,
on_build: fn(DynamicSceneBuilder) -> DynamicSceneBuilder,
) -> Self {
self.on_build = on_build;
self
}
pub fn with_asset_checks(mut self, checks: bool) -> Self {
self.resource_checks = checks;
self
}
pub fn with_entity_checks(mut self, checks: bool) -> Self {
self.entity_checks = checks;
self
}
pub fn with_component_checks(mut self, checks: bool) -> Self {
self.component_checks = checks;
self
}
pub fn with_allowed_ignores(mut self, ignores: Vec<String>) -> Self {
self.allowed_ignores = ignores;
self
}
pub fn with_num_ignored_resources(mut self, num: usize) -> Self {
self.num_ignored_resources = num;
self
}
pub fn filtered_builder<'w>(
&self,
world: &'w mut World,
) -> DynamicSceneBuilder<'w> {
let builder = DynamicSceneBuilder::from_world(world);
(self.on_build)(builder)
}
pub fn assert_scene_match<Q: QueryFilter>(
&self,
name: &str,
world: &mut World,
scene: &DynamicScene,
) -> Result<()> {
let mut issues = Vec::<String>::new();
if self.entity_checks {
issues.extend(self.check_entities::<Q>(world, scene));
}
if self.resource_checks {
issues.extend(self.check_resources(world, scene));
}
if self.component_checks {
issues.extend(self.check_components(world, scene));
}
if issues.len() > 0 {
anyhow::bail!(
"{}\n{}: {} issues found",
issues.join("\n"),
name,
issues.len(),
)
} else {
Ok(())
}
}
fn check_entities<Q: QueryFilter>(
&self,
world: &mut World,
scene: &DynamicScene,
) -> Vec<String> {
let mut issues = Vec::new();
let num_entities_world = get_save_entities::<Q>(world).len();
let num_entities_scene = scene.entities.len();
if num_entities_world != num_entities_scene {
issues.push(
format!("Entity count mismatch: Expected {num_entities_world}, got {num_entities_scene}"));
}
issues
}
fn check_components(
&self,
world: &mut World,
scene: &DynamicScene,
) -> Vec<String> {
let mut issues = Vec::new();
for dyn_entity in scene.entities.iter() {
for component in world.inspect_entity(dyn_entity.entity) {
let num_components_world =
world.inspect_entity(dyn_entity.entity).count();
let num_components_scene = dyn_entity.components.len();
if num_components_world != num_components_scene {
}
let component_scene = dyn_entity.components.iter().find(|c| {
c.get_represented_type_info()
.expect("found component without typeinfo")
.type_id() == component
.type_id()
.expect("found component without typeid")
});
if component_scene.is_none()
&& !self
.allowed_ignores
.iter()
.any(|i| component.name().starts_with(i))
{
issues.push(format!(
"Unregistered component: {}",
component.name()
));
}
}
}
issues
}
fn check_resources(
&self,
world: &mut World,
scene: &DynamicScene,
) -> Vec<String> {
let mut issues = Vec::new();
let num_resources_world = world.iter_resources().count() as i32
- self.num_ignored_resources as i32;
let num_resources_scene = scene.resources.len() as i32;
if num_resources_world != num_resources_scene {
let delta = num_resources_world - num_resources_scene;
issues.push(
format!(r#"Resource count mismatch, received is off by {delta}.
If this was intentional add {delta} to `DynamicSceneChecks::num_ignored_resources`."#));
}
issues
}
}