diff --git a/examples/demo/src/lib.rs b/examples/demo/src/lib.rs index ad92cd8..2867a23 100644 --- a/examples/demo/src/lib.rs +++ b/examples/demo/src/lib.rs @@ -50,7 +50,6 @@ fn input_stuff( match status.into_inner() { XrEnableStatus::Enabled => request.send(XrEnableRequest::TryDisable), XrEnableStatus::Disabled => request.send(XrEnableRequest::TryEnable), - XrEnableStatus::Waiting => (), } } } diff --git a/examples/globe.rs b/examples/globe.rs index cf58bea..192916b 100644 --- a/examples/globe.rs +++ b/examples/globe.rs @@ -172,8 +172,7 @@ fn pull_to_ground( let (globe_pos, globe) = globe.single(); // Get player position (position of playground + position within playground) - let v = views.lock().unwrap(); - let Some(view) = v.get(0) else { return }; + let Some(view) = views.first() else { return }; let mut hmd_translation = view.pose.position.to_vec3(); hmd_translation.y = 0.0; let local = root.translation; diff --git a/examples/xr.rs b/examples/xr.rs index b38d79a..6f8d2de 100644 --- a/examples/xr.rs +++ b/examples/xr.rs @@ -167,8 +167,6 @@ fn prototype_interaction_input( >, action_sets: Res, ) { - //lock frame - let frame_state = *frame_state.lock().unwrap(); //get controller let controller = oculus_controller.get_ref(&session, &frame_state, &xr_input, &action_sets); //get controller triggers diff --git a/src/graphics/mod.rs b/src/graphics/mod.rs index 49baa88..24e80ff 100644 --- a/src/graphics/mod.rs +++ b/src/graphics/mod.rs @@ -1,14 +1,19 @@ pub mod extensions; mod vulkan; -use bevy::render::renderer::{RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue}; -use bevy::window::RawHandleWrapper; +use bevy::ecs::query::With; +use bevy::ecs::system::{Query, SystemState}; +use bevy::ecs::world::World; +use bevy::render::renderer::{ + RenderAdapter, RenderAdapterInfo, RenderDevice, RenderInstance, RenderQueue, +}; +use bevy::window::{PrimaryWindow, RawHandleWrapper}; use wgpu::Instance; use crate::input::XrInput; use crate::resources::{ - XrEnvironmentBlendMode, XrFormat, XrFrameState, XrInstance, XrResolution, XrSession, - XrSessionRunning, XrSwapchain, XrViews, XrFrameWaiter, + OXrSessionSetupInfo, XrEnvironmentBlendMode, XrFormat, XrFrameState, XrFrameWaiter, XrInstance, + XrResolution, XrSession, XrSessionRunning, XrSwapchain, XrViews, }; use openxr as xr; @@ -39,20 +44,15 @@ impl Default for XrAppInfo { } } -pub fn initialize_xr_graphics( +pub fn start_xr_session( window: Option, - reqeusted_extensions: XrExtensions, - prefered_blend_mode: XrPreferdBlendMode, - app_info: XrAppInfo, + session_setup_data: &OXrSessionSetupInfo, + xr_instance: &XrInstance, + render_device: &RenderDevice, + render_adapter: &RenderAdapter, + wgpu_instance: &Instance, ) -> eyre::Result<( - RenderDevice, - RenderQueue, - RenderAdapterInfo, - RenderAdapter, - Instance, - XrInstance, XrSession, - XrEnvironmentBlendMode, XrResolution, XrFormat, XrSessionRunning, @@ -62,8 +62,107 @@ pub fn initialize_xr_graphics( XrViews, XrFrameState, )> { - // vulkan::initialize_xr_graphics(window, reqeusted_extensions, prefered_blend_mode, app_info) - todo!() + vulkan::start_xr_session( + window, + session_setup_data, + xr_instance, + render_device, + render_adapter, + wgpu_instance, + ) +} +pub fn initialize_xr_instance( + window: Option, + reqeusted_extensions: XrExtensions, + prefered_blend_mode: XrPreferdBlendMode, + app_info: XrAppInfo, +) -> eyre::Result<( + XrInstance, + OXrSessionSetupInfo, + XrEnvironmentBlendMode, + RenderDevice, + RenderQueue, + RenderAdapterInfo, + RenderAdapter, + Instance, +)> { + vulkan::initialize_xr_instance(window, reqeusted_extensions, prefered_blend_mode, app_info) +} + +pub fn try_full_init( + world: &mut World, + reqeusted_extensions: XrExtensions, + prefered_blend_mode: XrPreferdBlendMode, + app_info: XrAppInfo, +) -> eyre::Result<( + RenderDevice, + RenderQueue, + RenderAdapterInfo, + RenderAdapter, + RenderInstance, +)> { + let mut system_state: SystemState>> = + SystemState::new(world); + let primary_window = system_state.get(&world).get_single().ok().cloned(); + let ( + xr_instance, + setup_info, + blend_mode, + render_device, + render_queue, + render_adapter_info, + render_adapter, + wgpu_instance, + ) = initialize_xr_instance( + primary_window.clone(), + reqeusted_extensions, + prefered_blend_mode, + app_info, + )?; + world.insert_resource(xr_instance); + world.insert_non_send_resource(setup_info); + // TODO: move BlendMode the session init? + world.insert_resource(blend_mode); + let setup_info = world + .get_non_send_resource::() + .unwrap(); + let xr_instance = world.get_resource::().unwrap(); + + let ( + xr_session, + xr_resolution, + xr_format, + xr_session_running, + xr_frame_waiter, + xr_swapchain, + xr_input, + xr_views, + xr_frame_state, + ) = start_xr_session( + primary_window, + setup_info, + xr_instance, + &render_device, + &render_adapter, + &wgpu_instance, + )?; + world.insert_resource(xr_session); + world.insert_resource(xr_resolution); + world.insert_resource(xr_format); + world.insert_resource(xr_session_running); + world.insert_resource(xr_frame_waiter); + world.insert_resource(xr_swapchain); + world.insert_resource(xr_input); + world.insert_resource(xr_views); + world.insert_resource(xr_frame_state); + + Ok(( + render_device, + render_queue, + render_adapter_info, + render_adapter, + RenderInstance(wgpu_instance.into()), + )) } pub fn xr_entry() -> eyre::Result { diff --git a/src/graphics/vulkan.rs b/src/graphics/vulkan.rs index 1411491..ac0948d 100644 --- a/src/graphics/vulkan.rs +++ b/src/graphics/vulkan.rs @@ -306,7 +306,7 @@ pub fn initialize_xr_instance( )) } -pub fn initialize_xr_graphics( +pub fn start_xr_session( window: Option, ptrs: &OXrSessionSetupInfo, xr_instance: &XrInstance, @@ -314,7 +314,6 @@ pub fn initialize_xr_graphics( render_adapter: &RenderAdapter, wgpu_instance: &Instance, ) -> eyre::Result<( - XrInstance, XrSession, XrResolution, XrFormat, @@ -436,7 +435,6 @@ pub fn initialize_xr_graphics( .collect(); Ok(( - xr_instance.clone().into(), session.clone().into_any_graphics().into(), resolution.into(), swapchain_format.into(), diff --git a/src/input.rs b/src/input.rs index cf81c0e..8a98a7e 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,10 +1,10 @@ use std::sync::Arc; -use bevy::prelude::*; +use bevy::{prelude::*, render::extract_resource::ExtractResource}; use openxr as xr; use xr::{FrameState, FrameWaiter, ViewConfigurationType}; -#[derive(Clone, Resource)] +#[derive(Clone, Resource, ExtractResource)] pub struct XrInput { //pub action_set: xr::ActionSet, //pub hand_pose: xr::Action, diff --git a/src/lib.rs b/src/lib.rs index 6f9a591..ea1e496 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,11 +15,13 @@ use bevy::app::{AppExit, PluginGroupBuilder}; use bevy::ecs::system::SystemState; use bevy::prelude::*; use bevy::render::camera::{ManualTextureView, ManualTextureViewHandle, ManualTextureViews}; +use bevy::render::extract_resource::ExtractResourcePlugin; use bevy::render::pipelined_rendering::PipelinedRenderingPlugin; use bevy::render::renderer::{render_system, RenderInstance}; use bevy::render::settings::RenderCreation; use bevy::render::{Render, RenderApp, RenderPlugin, RenderSet}; use bevy::window::{PresentMode, PrimaryWindow, RawHandleWrapper}; +use eyre::anyhow; use graphics::extensions::XrExtensions; use graphics::{XrAppInfo, XrPreferdBlendMode}; use input::XrInput; @@ -57,7 +59,6 @@ pub struct FutureXrResources( XrResolution, XrFormat, XrSessionRunning, - // XrFrameWaiter, XrSwapchain, XrInput, XrViews, @@ -66,68 +67,20 @@ pub struct FutureXrResources( >, >, ); -// fn mr_test(mut commands: Commands, passthrough_layer: Option>) { -// commands.insert_resource(ClearColor(Color::rgba(0.0, 0.0, 0.0, 0.0))); -// } impl Plugin for OpenXrPlugin { fn build(&self, app: &mut App) { - let mut system_state: SystemState>> = - SystemState::new(&mut app.world); - let primary_window = system_state.get(&app.world).get_single().ok().cloned(); - #[cfg(not(target_arch = "wasm32"))] - match graphics::initialize_xr_graphics( - primary_window.clone(), + match graphics::try_full_init( + &mut app.world, self.reqeusted_extensions.clone(), self.prefered_blend_mode, self.app_info.clone(), ) { - Ok(( - device, - queue, - adapter_info, - render_adapter, - instance, - xr_instance, - session, - blend_mode, - resolution, - format, - session_running, - frame_waiter, - swapchain, - input, - views, - frame_state, - )) => { - // std::thread::sleep(Duration::from_secs(5)); + Ok((device, queue, adapter_info, render_adapter, instance)) => { debug!("Configured wgpu adapter Limits: {:#?}", device.limits()); debug!("Configured wgpu adapter Features: {:#?}", device.features()); - app.insert_resource(xr_instance.clone()); - app.insert_resource(session.clone()); - app.insert_resource(blend_mode.clone()); - app.insert_resource(resolution.clone()); - app.insert_resource(format.clone()); - app.insert_resource(session_running.clone()); - app.insert_resource(frame_waiter); - app.insert_resource(swapchain.clone()); - app.insert_resource(input.clone()); - app.insert_resource(views.clone()); - app.insert_resource(frame_state.clone()); - let xr_data = XrRenderData { - xr_instance, - xr_session: session, - xr_blend_mode: blend_mode, - xr_resolution: resolution, - xr_format: format, - xr_session_running: session_running, - xr_swapchain: swapchain, - xr_input: input, - xr_views: views, - xr_frame_state: frame_state, - }; - app.insert_resource(xr_data); + warn!("Starting in Xr"); app.insert_resource(ActionSets(vec![])); app.add_plugins(RenderPlugin { render_creation: RenderCreation::Manual( @@ -135,14 +88,16 @@ impl Plugin for OpenXrPlugin { queue, adapter_info, render_adapter, - RenderInstance(Arc::new(instance)), + instance, ), }); + app.add_plugins(ExtractResourcePlugin::::default()); app.insert_resource(XrEnableStatus::Enabled); } Err(err) => { warn!("OpenXR Failed to initialize: {}", err); app.add_plugins(RenderPlugin::default()); + app.add_plugins(ExtractResourcePlugin::::default()); app.insert_resource(XrEnableStatus::Disabled); } } @@ -154,58 +109,48 @@ impl Plugin for OpenXrPlugin { } } - fn ready(&self, app: &App) -> bool { - app.world - .get_resource::() - .map(|frr| *frr != XrEnableStatus::Waiting) - .unwrap_or(true) - } - fn finish(&self, app: &mut App) { // TODO: Split this up into the indevidual resources - if let Some(data) = app.world.get_resource::().cloned() { - let hands = data.xr_instance.exts().ext_hand_tracking.is_some() - && data - .xr_instance + if app.world.get_resource::() == Some(&XrEnableStatus::Enabled) { + warn!("finished xr init"); + let xr_instance = app + .world + .get_resource::() + .expect("should exist"); + let xr_session = app.world.get_resource::().expect("should exist"); + let hands = xr_instance.exts().ext_hand_tracking.is_some() + && xr_instance .supports_hand_tracking( - data.xr_instance + xr_instance .system(FormFactor::HEAD_MOUNTED_DISPLAY) .unwrap(), ) .is_ok_and(|v| v); if hands { - app.insert_resource(HandTrackingData::new(&data.xr_session).unwrap()); + app.insert_resource(HandTrackingData::new(xr_session).unwrap()); } else { app.insert_resource(DisableHandTracking::Both); } - // let passthrough = data.xr_instance.exts().fb_passthrough.is_some() - // && supports_passthrough( - // &data.xr_instance, - // data.xr_instance - // .system(FormFactor::HEAD_MOUNTED_DISPLAY) - // .unwrap(), - // ) - // .is_ok_and(|v| v); - // if passthrough { - // info!("Passthrough!"); - // let (pl, p) = start_passthrough(&data); - // app.insert_resource(pl); - // app.insert_resource(p); - // // if !app.world.contains_resource::() { - // // info!("ClearColor!"); - // // } - // } + let xr_swapchain = app + .world + .get_resource::() + .expect("should exist"); + let xr_resolution = app + .world + .get_resource::() + .expect("should exist"); + let xr_format = app.world.get_resource::().expect("should exist"); - let (left, right) = data.xr_swapchain.get_render_views(); + let (left, right) = xr_swapchain.get_render_views(); let left = ManualTextureView { texture_view: left.into(), - size: *data.xr_resolution, - format: *data.xr_format, + size: **xr_resolution, + format: **xr_format, }; let right = ManualTextureView { texture_view: right.into(), - size: *data.xr_resolution, - format: *data.xr_format, + size: **xr_resolution, + format: **xr_format, }; app.add_systems(PreUpdate, xr_begin_frame.run_if(xr_only())); let mut manual_texture_views = app.world.resource_mut::(); @@ -213,19 +158,6 @@ impl Plugin for OpenXrPlugin { manual_texture_views.insert(RIGHT_XR_TEXTURE_HANDLE, right); drop(manual_texture_views); let render_app = app.sub_app_mut(RenderApp); - - render_app.insert_resource(data.xr_instance.clone()); - render_app.insert_resource(data.xr_session.clone()); - render_app.insert_resource(data.xr_blend_mode.clone()); - render_app.insert_resource(data.xr_resolution.clone()); - render_app.insert_resource(data.xr_format.clone()); - render_app.insert_resource(data.xr_session_running.clone()); - // render_app.insert_resource(data.xr_frame_waiter.clone()); - render_app.insert_resource(data.xr_swapchain.clone()); - render_app.insert_resource(data.xr_input.clone()); - render_app.insert_resource(data.xr_views.clone()); - render_app.insert_resource(data.xr_frame_state.clone()); - render_app.insert_resource(XrEnableStatus::Enabled); render_app.add_systems( Render, ( @@ -262,6 +194,8 @@ impl PluginGroup for DefaultXrPlugins { .add_before::(RenderRestartPlugin) .add(HandEmulationPlugin) .add(HandTrackingPlugin) + .add(XrResourcePlugin) + // .add(xr_init::RenderRestartPlugin) .set(WindowPlugin { #[cfg(not(target_os = "android"))] primary_window: Some(Window { @@ -302,7 +236,6 @@ pub fn xr_begin_frame( // find quit messages! info!("entered XR state {:?}", e.state()); match e.state() { - // xr_frame_waiter: frame_waiter, xr::SessionState::READY => { session.begin(VIEW_TYPE).unwrap(); session_running.store(true, std::sync::atomic::Ordering::Relaxed); @@ -310,7 +243,6 @@ pub fn xr_begin_frame( xr::SessionState::STOPPING => { session.end().unwrap(); session_running.store(false, std::sync::atomic::Ordering::Relaxed); - app_exit.send(AppExit); } xr::SessionState::EXITING | xr::SessionState::LOSS_PENDING => { app_exit.send(AppExit); @@ -384,14 +316,20 @@ pub fn post_frame( } pub fn end_frame( - xr_frame_state: Res, - views: Res, - input: Res, - swapchain: Res, - resolution: Res, - environment_blend_mode: Res, + xr_frame_state: Option>, + views: Option>, + input: Option>, + swapchain: Option>, + resolution: Option>, + environment_blend_mode: Option>, // passthrough_layer: Option>, ) { + let xr_frame_state = xr_frame_state.unwrap(); + let views = views.unwrap(); + let input = input.unwrap(); + let swapchain = swapchain.unwrap(); + let resolution = resolution.unwrap(); + let environment_blend_mode = environment_blend_mode.unwrap(); { let _span = info_span!("xr_release_image").entered(); swapchain.release_image().unwrap(); diff --git a/src/resource_macros.rs b/src/resource_macros.rs index 933ce20..735ec50 100644 --- a/src/resource_macros.rs +++ b/src/resource_macros.rs @@ -1,7 +1,13 @@ #[macro_export] macro_rules! xr_resource_wrapper { ($wrapper_type:ident, $xr_type:ty) => { - #[derive(Clone, bevy::prelude::Resource, bevy::prelude::Deref, bevy::prelude::DerefMut)] + #[derive( + Clone, + bevy::prelude::Resource, + bevy::prelude::Deref, + bevy::prelude::DerefMut, + bevy::render::extract_resource::ExtractResource, + )] pub struct $wrapper_type($xr_type); impl $wrapper_type { @@ -29,7 +35,13 @@ macro_rules! xr_resource_wrapper { #[macro_export] macro_rules! xr_arc_resource_wrapper { ($wrapper_type:ident, $xr_type:ty) => { - #[derive(Clone, bevy::prelude::Resource, bevy::prelude::Deref, bevy::prelude::DerefMut)] + #[derive( + Clone, + bevy::prelude::Resource, + bevy::prelude::Deref, + bevy::prelude::DerefMut, + bevy::render::extract_resource::ExtractResource, + )] pub struct $wrapper_type(std::sync::Arc<$xr_type>); impl $wrapper_type { diff --git a/src/resources.rs b/src/resources.rs index 62f2855..b73cb1c 100644 --- a/src/resources.rs +++ b/src/resources.rs @@ -2,11 +2,13 @@ use std::ffi::c_void; use std::sync::atomic::AtomicBool; use std::sync::Mutex; +use crate::input::XrInput; // use crate::passthrough::XrPassthroughLayer; use crate::resource_macros::*; +use crate::xr_init::XrEnableStatus; use bevy::prelude::*; +use bevy::render::extract_resource::ExtractResourcePlugin; use openxr as xr; -use xr::ViewConfigurationView; xr_resource_wrapper!(XrInstance, xr::Instance); xr_resource_wrapper!(XrSession, xr::Session); @@ -32,6 +34,20 @@ pub(crate) enum OXrSessionSetupInfo { Vulkan(VulkanOXrSessionSetupInfo), } +pub struct XrResourcePlugin; + +impl Plugin for XrResourcePlugin { + fn build(&self, app: &mut App) { + app.add_plugins(ExtractResourcePlugin::::default()); + app.add_plugins(ExtractResourcePlugin::::default()); + app.add_plugins(ExtractResourcePlugin::::default()); + app.add_plugins(ExtractResourcePlugin::::default()); + app.add_plugins(ExtractResourcePlugin::::default()); + app.add_plugins(ExtractResourcePlugin::::default()); + app.add_plugins(ExtractResourcePlugin::::default()); + } +} + pub enum Swapchain { Vulkan(SwapchainInner), } @@ -153,7 +169,7 @@ impl SwapchainInner { }, }; let swapchain = self.handle.lock().unwrap(); - if views.len() == 0 { + if views.is_empty() { warn!("views are len of 0"); return Ok(()); } diff --git a/src/xr_init.rs b/src/xr_init.rs index f60b0ba..698835d 100644 --- a/src/xr_init.rs +++ b/src/xr_init.rs @@ -11,16 +11,17 @@ use bevy::{ renderer::{self, RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue}, settings::WgpuSettings, }, - window::{RawHandleWrapper, PrimaryWindow}, + window::{PrimaryWindow, RawHandleWrapper}, }; use wgpu::Instance; use crate::{ + graphics, input::XrInput, resources::{ XrEnvironmentBlendMode, XrFormat, XrFrameState, XrInstance, XrResolution, XrSession, XrSessionRunning, XrSwapchain, XrViews, - }, graphics, + }, }; #[derive(Resource, Clone)] @@ -52,14 +53,13 @@ pub enum XrEnableRequest { TryEnable, TryDisable, } -#[derive(Resource, Event, Copy, Clone, PartialEq, Eq)] +#[derive(Resource, Event, Copy, Clone, PartialEq, Eq, Reflect, ExtractResource)] pub enum XrEnableStatus { Enabled, Disabled, - Waiting, } -#[derive(Resource, Event, Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Resource, Event, Copy, Clone, PartialEq, Eq, Debug, ExtractResource)] pub enum XrNextEnabledState { Enabled, Disabled, @@ -67,29 +67,33 @@ pub enum XrNextEnabledState { pub struct RenderRestartPlugin; -#[derive(Resource)] -pub struct ForceMain; - #[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)] pub struct XrPreSetup; + #[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)] pub struct XrSetup; + #[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)] pub struct XrPrePostSetup; + #[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)] pub struct XrPostSetup; #[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)] pub struct XrPreCleanup; + #[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)] pub struct XrCleanup; + #[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)] pub struct XrPostCleanup; #[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)] pub struct XrPreRenderUpdate; + #[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)] pub struct XrRenderUpdate; + #[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)] pub struct XrPostRenderUpdate; @@ -99,9 +103,9 @@ pub fn xr_only() -> impl FnMut(Option>) -> bool { impl Plugin for RenderRestartPlugin { fn build(&self, app: &mut App) { + info!("build RenderRestartPlugin"); add_schedules(app); app.add_plugins(ExtractResourcePlugin::::default()) - .insert_resource(ForceMain) .add_event::() .add_event::() .add_systems(PostStartup, setup_xr.run_if(xr_only())) @@ -174,13 +178,9 @@ pub fn update_xr_stuff(world: &mut World) { world.run_schedule(XrPostRenderUpdate); } -fn setup_xr_graphics() { +fn setup_xr_graphics() {} -} - -fn enable_xr( - -) {} +fn enable_xr() {} // fn handle_xr_enable_requests( // primary_window: Query<&RawHandleWrapper, With>, @@ -301,10 +301,10 @@ fn decide_next_xr_state( info!("Xr Already Disabled! ignoring request"); return; } - (_, Some(XrEnableStatus::Waiting)) => { - info!("Already Handling Request! ignoring request"); - return; - } + // (_, Some(XrEnableStatus::Waiting)) => { + // info!("Already Handling Request! ignoring request"); + // return; + // } _ => {} } let r = match request { diff --git a/src/xr_input/trackers.rs b/src/xr_input/trackers.rs index eea28c1..e607c16 100644 --- a/src/xr_input/trackers.rs +++ b/src/xr_input/trackers.rs @@ -1,3 +1,4 @@ +use bevy::hierarchy::Parent; use bevy::log::{debug, info}; use bevy::prelude::{ Added, BuildChildren, Commands, Component, Entity, Query, Res, Transform, Vec3, With, Without, @@ -30,17 +31,17 @@ pub struct OpenXRController; pub struct AimPose(pub Transform); pub fn adopt_open_xr_trackers( - query: Query>, + query: Query, Without)>, mut commands: Commands, - tracking_root_query: Query<(Entity, With)>, + tracking_root_query: Query>, ) { let root = tracking_root_query.get_single(); match root { - Ok(thing) => { + Ok(root) => { // info!("root is"); for tracker in query.iter() { info!("we got a new tracker"); - commands.entity(thing.0).add_child(tracker); + commands.entity(root).add_child(tracker); } } Err(_) => info!("root isnt spawned yet?"),