Initial bevy 0.15 support

This commit is contained in:
Patrick Dobbs 2024-10-24 23:04:23 +01:00
parent 0e51ff893a
commit ac406eaad7
17 changed files with 95 additions and 77 deletions

View File

@ -1,6 +1,6 @@
[package] [package]
name = "blenvy" name = "blenvy"
version = "0.1.0-alpha.1" version = "0.1.0-alpha.2"
authors = ["Mark 'kaosat-dev' Moissette"] authors = ["Mark 'kaosat-dev' Moissette"]
description = "Allows you to define Bevy components direclty inside gltf files and instanciate the components on the Bevy side." description = "Allows you to define Bevy components direclty inside gltf files and instanciate the components on the Bevy side."
homepage = "https://github.com/kaosat-dev/Blenvy" homepage = "https://github.com/kaosat-dev/Blenvy"
@ -14,12 +14,12 @@ license = "MIT OR Apache-2.0"
workspace = true workspace = true
[dependencies] [dependencies]
bevy = { version = "0.14", default-features = false, features = ["bevy_asset", "bevy_scene", "bevy_gltf", "animation"] } bevy = { version = "0.15.0-rc.1", default-features = false, features = ["bevy_asset", "bevy_scene", "bevy_gltf", "animation"] }
serde = "1.0.188" serde = "1.0.188"
ron = "0.8.1" ron = "0.8.1"
serde_json = "1.0.108" serde_json = "1.0.108"
bevy_common_assets = {version = "0.11", features = ["ron"]} bevy_common_assets = {version = "0.12.0-rc.1", features = ["ron"]}
[dev-dependencies] [dev-dependencies]
bevy = { version = "0.14", default-features = false, features = ["dynamic_linking"] } bevy = { version = "0.15.0-rc.1", default-features = false, features = ["dynamic_linking"] }

View File

@ -32,8 +32,8 @@ pub(crate) fn inject_materials(
(), (),
( (
With<Parent>, With<Parent>,
With<Handle<StandardMaterial>>, With<MeshMaterial3d<StandardMaterial>>,
With<Handle<Mesh>>, With<Mesh3d>,
), ),
>, >,
assets_gltf: Res<Assets<Gltf>>, assets_gltf: Res<Assets<Gltf>>,
@ -95,7 +95,7 @@ pub(crate) fn inject_materials(
material_info.path.clone() material_info.path.clone()
); );
commands.entity(*child).insert(material.clone()); commands.entity(*child).insert(MeshMaterial3d(material.clone()));
} }
} }
} }

View File

@ -515,7 +515,7 @@ pub(crate) fn blueprints_assets_loaded(
commands.entity(entity).insert(( commands.entity(entity).insert((
SceneBundle { SceneBundle {
scene: scene.clone(), scene: SceneRoot(scene.clone()),
transform: transforms, transform: transforms,
..Default::default() ..Default::default()
}, },
@ -709,7 +709,7 @@ pub(crate) fn blueprints_cleanup_spawned_scene(
} }
// copy components into from blueprint instance's blueprint_root_entity to original entity // copy components into from blueprint instance's blueprint_root_entity to original entity
commands.add(CopyComponents { commands.queue(CopyComponents {
source: blueprint_root_entity, source: blueprint_root_entity,
destination: original, destination: original,
exclude: vec![TypeId::of::<Parent>(), TypeId::of::<Children>()], exclude: vec![TypeId::of::<Parent>(), TypeId::of::<Children>()],
@ -743,7 +743,7 @@ pub(crate) fn blueprints_cleanup_spawned_scene(
let transitions = AnimationTransitions::new(); let transitions = AnimationTransitions::new();
commands commands
.entity(entity_with_player) .entity(entity_with_player)
.insert((transitions, animations.graph.clone())); .insert((transitions, AnimationGraphHandle(animations.graph.clone())));
} }
} }
// FIXME VERY convoluted, but it works // FIXME VERY convoluted, but it works

View File

@ -9,7 +9,7 @@ use bevy::{
gltf::{GltfExtras, GltfMaterialExtras, GltfMeshExtras, GltfSceneExtras}, gltf::{GltfExtras, GltfMaterialExtras, GltfMeshExtras, GltfSceneExtras},
hierarchy::Parent, hierarchy::Parent,
log::{debug, warn}, log::{debug, warn},
reflect::{Reflect, TypeRegistration}, reflect::{Reflect, PartialReflect, TypeRegistration},
utils::HashMap, utils::HashMap,
}; };
@ -20,9 +20,9 @@ fn find_entity_components(
entity: Entity, entity: Entity,
name: Option<&Name>, name: Option<&Name>,
parent: Option<&Parent>, parent: Option<&Parent>,
reflect_components: Vec<(Box<dyn Reflect>, TypeRegistration)>, reflect_components: Vec<(Box<dyn PartialReflect>, TypeRegistration)>,
entity_components: &HashMap<Entity, Vec<(Box<dyn Reflect>, TypeRegistration)>>, entity_components: &HashMap<Entity, Vec<(Box<dyn PartialReflect>, TypeRegistration)>>,
) -> (Entity, Vec<(Box<dyn Reflect>, TypeRegistration)>) { ) -> (Entity, Vec<(Box<dyn PartialReflect>, TypeRegistration)>) {
// we assign the components specified /xxx_components objects to their parent node // we assign the components specified /xxx_components objects to their parent node
let mut target_entity = entity; let mut target_entity = entity;
// if the node contains "components" or ends with "_pa" (ie add to parent), the components will not be added to the entity itself but to its parent // if the node contains "components" or ends with "_pa" (ie add to parent), the components will not be added to the entity itself but to its parent
@ -40,10 +40,11 @@ fn find_entity_components(
// if there where already components set to be added to this entity (for example when entity_data was refering to a parent), update the vec of entity_components accordingly // if there where already components set to be added to this entity (for example when entity_data was refering to a parent), update the vec of entity_components accordingly
// this allows for example blender collection to provide basic ecs data & the instances to override/ define their own values // this allows for example blender collection to provide basic ecs data & the instances to override/ define their own values
if entity_components.contains_key(&target_entity) { if entity_components.contains_key(&target_entity) {
let mut updated_components: Vec<(Box<dyn Reflect>, TypeRegistration)> = Vec::new(); let mut updated_components: Vec<(Box<dyn PartialReflect>, TypeRegistration)> = Vec::new();
let current_components = &entity_components[&target_entity]; let current_components = &entity_components[&target_entity];
// first inject the current components // first inject the current components
for (component, type_registration) in current_components { for (component, type_registration) in current_components {
//updated_components.push((component.clone().downcast().unwrap(), type_registration.clone()));
updated_components.push((component.clone_value(), type_registration.clone())); updated_components.push((component.clone_value(), type_registration.clone()));
} }
// then inject the new components: this also enables overwrite components set in the collection // then inject the new components: this also enables overwrite components set in the collection
@ -62,7 +63,7 @@ pub fn add_components_from_gltf_extras(world: &mut World) {
let mut mesh_extras = world.query_filtered::<(Entity, Option<&Name>, &GltfMeshExtras, Option<&Parent>), (Added<GltfMeshExtras>, Without<GltfProcessed>)>(); let mut mesh_extras = world.query_filtered::<(Entity, Option<&Name>, &GltfMeshExtras, Option<&Parent>), (Added<GltfMeshExtras>, Without<GltfProcessed>)>();
let mut material_extras = world.query_filtered::<(Entity, Option<&Name>, &GltfMaterialExtras, Option<&Parent>), (Added<GltfMaterialExtras>, Without<GltfProcessed>)>(); let mut material_extras = world.query_filtered::<(Entity, Option<&Name>, &GltfMaterialExtras, Option<&Parent>), (Added<GltfMaterialExtras>, Without<GltfProcessed>)>();
let mut entity_components: HashMap<Entity, Vec<(Box<dyn Reflect>, TypeRegistration)>> = let mut entity_components: HashMap<Entity, Vec<(Box<dyn PartialReflect>, TypeRegistration)>> =
HashMap::new(); HashMap::new();
// let gltf_components_config = world.resource::<GltfComponentsConfig>(); // let gltf_components_config = world.resource::<GltfComponentsConfig>();
@ -150,7 +151,7 @@ pub fn add_components_from_gltf_extras(world: &mut World) {
entity_mut.insert(GltfProcessed); entity_mut.insert(GltfProcessed);
continue; continue;
}; };
reflected_component.insert(&mut entity_mut, &*component, &type_registry); reflected_component.insert(&mut entity_mut, &*component.into_partial_reflect(), &type_registry);
entity_mut.insert(GltfProcessed); // entity_mut.insert(GltfProcessed); //
} }

View File

@ -1,6 +1,6 @@
use bevy::log::{debug, warn}; use bevy::log::{debug, warn};
use bevy::reflect::serde::ReflectDeserializer; use bevy::reflect::serde::ReflectDeserializer;
use bevy::reflect::{Reflect, TypeRegistration, TypeRegistry}; use bevy::reflect::{Reflect, PartialReflect, TypeRegistration, TypeRegistry};
use bevy::utils::HashMap; use bevy::utils::HashMap;
use ron::Value; use ron::Value;
use serde::de::DeserializeSeed; use serde::de::DeserializeSeed;
@ -10,9 +10,9 @@ use super::capitalize_first_letter;
pub fn ronstring_to_reflect_component( pub fn ronstring_to_reflect_component(
ron_string: &str, ron_string: &str,
type_registry: &TypeRegistry, type_registry: &TypeRegistry,
) -> Vec<(Box<dyn Reflect>, TypeRegistration)> { ) -> Vec<(Box<dyn PartialReflect>, TypeRegistration)> {
let lookup: HashMap<String, Value> = ron::from_str(ron_string).unwrap(); let lookup: HashMap<String, Value> = ron::from_str(ron_string).unwrap();
let mut components: Vec<(Box<dyn Reflect>, TypeRegistration)> = Vec::new(); let mut components: Vec<(Box<dyn PartialReflect>, TypeRegistration)> = Vec::new();
// println!("ron_string {:?}", ron_string); // println!("ron_string {:?}", ron_string);
for (name, value) in lookup.into_iter() { for (name, value) in lookup.into_iter() {
let parsed_value: String = match value.clone() { let parsed_value: String = match value.clone() {
@ -40,7 +40,7 @@ fn components_string_to_components(
value: Value, value: Value,
parsed_value: String, parsed_value: String,
type_registry: &TypeRegistry, type_registry: &TypeRegistry,
components: &mut Vec<(Box<dyn Reflect>, TypeRegistration)>, components: &mut Vec<(Box<dyn PartialReflect>, TypeRegistration)>,
) { ) {
let type_string = name.replace("component: ", "").trim().to_string(); let type_string = name.replace("component: ", "").trim().to_string();
let capitalized_type_name = capitalize_first_letter(type_string.as_str()); let capitalized_type_name = capitalize_first_letter(type_string.as_str());
@ -97,7 +97,7 @@ fn components_string_to_components(
fn bevy_components_string_to_components( fn bevy_components_string_to_components(
parsed_value: String, parsed_value: String,
type_registry: &TypeRegistry, type_registry: &TypeRegistry,
components: &mut Vec<(Box<dyn Reflect>, TypeRegistration)>, components: &mut Vec<(Box<dyn PartialReflect>, TypeRegistration)>,
) { ) {
let lookup: HashMap<String, Value> = ron::from_str(&parsed_value).unwrap(); let lookup: HashMap<String, Value> = ron::from_str(&parsed_value).unwrap();
for (key, value) in lookup.into_iter() { for (key, value) in lookup.into_iter() {

View File

@ -152,26 +152,26 @@ pub fn export_type(reg: &TypeRegistration) -> (String, Value) {
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
"items": false, "items": false,
}), }),
TypeInfo::List(info) => { TypeInfo::List(info) => json!({
json!({ "long_name": t.type_path(),
"long_name": t.type_path(), "type": "array",
"type": "array", "typeInfo": "List",
"typeInfo": "List", "items": json!({
"items": json!({"type": typ(info.item_type_path_table().path())}), "type": typ(info.item_ty().path())
}) }),
} }),
TypeInfo::Array(info) => json!({ TypeInfo::Array(info) => json!({
"long_name": t.type_path(), "long_name": t.type_path(),
"type": "array", "type": "array",
"typeInfo": "Array", "typeInfo": "Array",
"items": json!({"type": typ(info.item_type_path_table().path())}), "items": json!({"type": typ(info.item_ty().path())}),
}), }),
TypeInfo::Map(info) => json!({ TypeInfo::Map(info) => json!({
"long_name": t.type_path(), "long_name": t.type_path(),
"type": "object", "type": "object",
"typeInfo": "Map", "typeInfo": "Map",
"valueType": json!({"type": typ(info.value_type_path_table().path())}), "valueType": json!({"type": typ(info.value_ty().path())}),
"keyType": json!({"type": typ(info.key_type_path_table().path())}), "keyType": json!({"type": typ(info.key_ty().path())}),
}), }),
TypeInfo::Tuple(info) => json!({ TypeInfo::Tuple(info) => json!({
"long_name": t.type_path(), "long_name": t.type_path(),
@ -184,7 +184,13 @@ pub fn export_type(reg: &TypeRegistration) -> (String, Value) {
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
"items": false, "items": false,
}), }),
TypeInfo::Value(info) => json!({ TypeInfo::Set(info) => json!({
"long_name": t.type_path(),
"type": "set",
"typeInfo": "Set",
"items": json!({"type": typ(info.value_ty().path())}),
}),
TypeInfo::Opaque(info) => json!({
"long_name": t.type_path(), "long_name": t.type_path(),
"type": map_json_type(info.type_path()), "type": map_json_type(info.type_path()),
"typeInfo": "Value", "typeInfo": "Value",

View File

@ -84,7 +84,7 @@ pub(crate) fn load_game(
let _dynamic_data = commands let _dynamic_data = commands
.spawn(( .spawn((
DynamicSceneBundle { DynamicSceneBundle {
scene: asset_server.load(load_request.path.clone()), scene: DynamicSceneRoot(asset_server.load(load_request.path.clone())),
..default() ..default()
}, },
bevy::prelude::Name::from("World_dynamic"), bevy::prelude::Name::from("World_dynamic"),

View File

@ -138,7 +138,7 @@ pub(crate) fn save_game(world: &mut World) {
// for default stuff // for default stuff
let scene_builder = DynamicSceneBuilder::from_world(world) let scene_builder = DynamicSceneBuilder::from_world(world)
.with_filter(filter.clone()) .with_component_filter(filter.clone())
.with_resource_filter(filter_resources.clone()); .with_resource_filter(filter_resources.clone());
let dyn_scene = scene_builder let dyn_scene = scene_builder
@ -149,7 +149,7 @@ pub(crate) fn save_game(world: &mut World) {
// for root entities // for root entities
let scene_builder_root = DynamicSceneBuilder::from_world(world) let scene_builder_root = DynamicSceneBuilder::from_world(world)
.with_filter(filter_root.clone()) .with_component_filter(filter_root.clone())
.with_resource_filter(filter_resources.clone()); .with_resource_filter(filter_resources.clone());
let mut __dyn_scene_root = scene_builder_root let mut __dyn_scene_root = scene_builder_root

View File

@ -5,6 +5,6 @@ edition = "2021"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
[dependencies] [dependencies]
bevy = { version = "0.14", features = ["dynamic_linking"] } bevy = { version = "0.15.0-rc.1", features = ["dynamic_linking"] }
blenvy = { path = "../../crates/blenvy" } blenvy = { path = "../../crates/blenvy" }
rand = "0.8.5" rand = "0.8.5"

View File

@ -5,6 +5,6 @@ edition = "2021"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
[dependencies] [dependencies]
bevy = { version = "0.14", features = ["dynamic_linking"] } bevy = { version = "0.15.0-rc.1", features = ["dynamic_linking"] }
blenvy = { path = "../../crates/blenvy" } blenvy = { path = "../../crates/blenvy" }
rand = "0.8.5" rand = "0.8.5"

View File

@ -5,5 +5,5 @@ edition = "2021"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
[dependencies] [dependencies]
bevy = { version = "0.14", features = ["dynamic_linking"] } bevy = { version = "0.15.0-rc.1", features = ["dynamic_linking"] }
blenvy = { path = "../../crates/blenvy" } blenvy = { path = "../../crates/blenvy" }

View File

@ -5,7 +5,7 @@ edition = "2021"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
[dependencies] [dependencies]
bevy = { version = "0.14", features = ["dynamic_linking"] } bevy = { version = "0.15.0-rc.1", features = ["dynamic_linking"] }
blenvy = { path = "../../crates/blenvy" } blenvy = { path = "../../crates/blenvy" }
rand = "0.8.5" rand = "0.8.5"
avian3d = "0.1.2" avian3d = "0.1.2"

View File

@ -4,15 +4,19 @@ version = "0.3.0"
edition = "2021" edition = "2021"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
[features]
default = []
bevy-inspector = ["bevy-inspector-egui"]
[dependencies] [dependencies]
bevy = { version = "0.14", features = ["dynamic_linking"] } bevy = { version = "0.15.0-rc.1", features = ["dynamic_linking"] }
blenvy = { path = "../../crates/blenvy" } blenvy = { path = "../../crates/blenvy" }
serde_json = "1.0.108" serde_json = "1.0.108"
serde = "1.0.193" serde = "1.0.193"
rand = "0.8.5" rand = "0.8.5"
bevy-inspector-egui = { version = "0.25.1"} bevy-inspector-egui = { version = "0.25.1", optional = true}
[dev-dependencies] [dev-dependencies]
bevy-inspector-egui = { version = "0.25.1"} #bevy-inspector-egui = { version = "0.25.1"}

View File

@ -11,6 +11,7 @@ use rand::Rng;
// use game::*; // use game::*;
mod component_examples; mod component_examples;
#[cfg(feature = "bevy-inspector")]
use bevy_inspector_egui::quick::WorldInspectorPlugin; use bevy_inspector_egui::quick::WorldInspectorPlugin;
use component_examples::*; use component_examples::*;
@ -18,6 +19,7 @@ fn main() {
App::new() App::new()
.add_plugins(( .add_plugins((
DefaultPlugins.set(AssetPlugin::default()), DefaultPlugins.set(AssetPlugin::default()),
#[cfg(feature = "bevy-inspector")]
WorldInspectorPlugin::new(), WorldInspectorPlugin::new(),
BlenvyPlugin { BlenvyPlugin {
save_component_filter: SceneFilter::Allowlist(HashSet::from([ save_component_filter: SceneFilter::Allowlist(HashSet::from([

View File

@ -5,11 +5,11 @@ edition = "2021"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
[dependencies] [dependencies]
bevy = { version = "0.14", features = ["dynamic_linking"] } bevy = { version = "0.15.0-rc.1", features = ["dynamic_linking"] }
blenvy = { path = "../../crates/blenvy" } blenvy = { path = "../../crates/blenvy" }
#bevy_editor_pls = { version = "0.8" } #bevy_editor_pls = { version = "0.8" }
rand = "0.8.5" rand = "0.8.5"
json-writer ="0.3" json-writer ="0.3"
[dev-dependencies] [dev-dependencies]
bevy-inspector-egui = { version = "0.25.1"} #bevy-inspector-egui = { version = "0.25.1"}

View File

@ -13,7 +13,9 @@ use blenvy::{
use crate::{AppState, GameState}; use crate::{AppState, GameState};
use bevy::{ use bevy::{
prelude::*, render::view::screenshot::ScreenshotManager, time::common_conditions::on_timer, prelude::*,
time::common_conditions::on_timer,
render::view::screenshot::{save_to_disk, Capturing, Screenshot},
window::PrimaryWindow, window::PrimaryWindow,
}; };
@ -116,12 +118,11 @@ fn validate_export(
} }
fn generate_screenshot( fn generate_screenshot(
main_window: Query<Entity, With<PrimaryWindow>>, mut commands: Commands,
mut screenshot_manager: ResMut<ScreenshotManager>,
) { ) {
screenshot_manager commands
.save_screenshot_to_disk(main_window.single(), "screenshot.png") .spawn(Screenshot::primary_window())
.unwrap(); .observe(save_to_disk("screenshot.png"));
} }
fn exit_game(mut app_exit_events: ResMut<Events<bevy::app::AppExit>>) { fn exit_game(mut app_exit_events: ResMut<Events<bevy::app::AppExit>>) {

View File

@ -11,26 +11,26 @@ pub struct HiearchyDebugTag;
pub fn setup_hierarchy_debug(mut commands: Commands) { pub fn setup_hierarchy_debug(mut commands: Commands) {
// a place to display the extras on screen // a place to display the extras on screen
commands.spawn(( commands.spawn((
TextBundle::from_section( Node {
"",
TextStyle {
color: LinearRgba {
red: 1.0,
green: 1.0,
blue: 1.0,
alpha: 1.0,
}
.into(),
font_size: 15.,
..default()
},
)
.with_style(Style {
position_type: PositionType::Absolute, position_type: PositionType::Absolute,
top: Val::Px(12.0), top: Val::Px(12.0),
left: Val::Px(12.0), left: Val::Px(12.0),
..default() ..default()
}), },
Text("".to_string()),
TextFont {
font_size: 15.,
..default()
},
TextColor(LinearRgba {
red: 1.0,
green: 1.0,
blue: 1.0,
alpha: 1.0,
}.into()),
TextLayout {
..default()
},
HiearchyDebugTag, HiearchyDebugTag,
)); ));
} }
@ -93,7 +93,8 @@ pub fn draw_hierarchy_debug(
all_global_transforms: Query<&GlobalTransform>, all_global_transforms: Query<&GlobalTransform>,
to_check: Query<&EnumTest>, //Query<(&BlueprintInstanceReady, &BlueprintAssets)>, to_check: Query<&EnumTest>, //Query<(&BlueprintInstanceReady, &BlueprintAssets)>,
mut display: Query<&mut Text, With<HiearchyDebugTag>>, display: Query<Entity, With<HiearchyDebugTag>>,
mut writer: TextUiWriter,
) { ) {
let mut hierarchy_display: Vec<String> = vec![]; let mut hierarchy_display: Vec<String> = vec![];
@ -118,8 +119,8 @@ pub fn draw_hierarchy_debug(
// //
} }
let mut display = display.single_mut(); let display = display.single();
display.sections[0].value = hierarchy_display.join("\n"); *writer.text(display, 0) = hierarchy_display.join("\n");
} }
////////:just some testing for gltf extras ////////:just some testing for gltf extras
@ -133,7 +134,8 @@ fn __check_for_gltf_extras(
Option<&GltfMeshExtras>, Option<&GltfMeshExtras>,
Option<&GltfMaterialExtras>, Option<&GltfMaterialExtras>,
)>, )>,
mut display: Query<&mut Text, With<HiearchyDebugTag>>, display: Query<Entity, With<HiearchyDebugTag>>,
mut writer: TextUiWriter,
) { ) {
let mut gltf_extra_infos_lines: Vec<String> = vec![]; let mut gltf_extra_infos_lines: Vec<String> = vec![];
@ -160,14 +162,15 @@ fn __check_for_gltf_extras(
); );
gltf_extra_infos_lines.push(formatted_extras); gltf_extra_infos_lines.push(formatted_extras);
} }
let mut display = display.single_mut(); let display = display.single();
display.sections[0].value = gltf_extra_infos_lines.join("\n"); *writer.text(display, 0) = gltf_extra_infos_lines.join("\n");
} }
} }
fn __check_for_component( fn __check_for_component(
specific_components: Query<(Entity, Option<&Name>, &RedirectPropHitImpulse)>, specific_components: Query<(Entity, Option<&Name>, &RedirectPropHitImpulse)>,
mut display: Query<&mut Text, With<HiearchyDebugTag>>, display: Query<Entity, With<HiearchyDebugTag>>,
mut writer: TextUiWriter,
) { ) {
let mut info_lines: Vec<String> = vec![]; let mut info_lines: Vec<String> = vec![];
for (__entiity, name, enum_complex) in specific_components.iter() { for (__entiity, name, enum_complex) in specific_components.iter() {
@ -178,8 +181,9 @@ fn __check_for_component(
info_lines.push(data); info_lines.push(data);
println!("yoho component"); println!("yoho component");
} }
let mut display = display.single_mut();
display.sections[0].value = info_lines.join("\n"); let display = display.single();
*writer.text(display, 0) = info_lines.join("\n");
} }
pub struct HiearchyDebugPlugin; pub struct HiearchyDebugPlugin;