diff --git a/src/objects/input/oxr_controller.rs b/src/objects/input/oxr_controller.rs index 64a5a5a..8661bac 100644 --- a/src/objects/input/oxr_controller.rs +++ b/src/objects/input/oxr_controller.rs @@ -12,7 +12,7 @@ use crate::{ input::{INPUT_HANDLER_REGISTRY, InputDataType, InputHandler, InputMethod, Tip}, spatial::Spatial, }, - objects::{ObjectHandle, SpatialRef, Tracked}, + objects::{AsyncTracked, ObjectHandle, SpatialRef, Tracked}, }; use bevy::{asset::Handle, ecs::resource::Resource}; use bevy::{math::Affine3, prelude::*}; @@ -268,7 +268,7 @@ pub struct OxrControllerInput { model_part: Arc, capture_manager: CaptureManager, datamap: ControllerDatamap, - tracked: ObjectHandle, + tracked: AsyncTracked, space: Option, } impl OxrControllerInput { @@ -279,7 +279,7 @@ impl OxrControllerInput { HandSide::Right => "right", }; let (spatial, object_handle) = SpatialRef::create(connection, &path); - let tracked = Tracked::new(connection, &path); + let tracked = AsyncTracked::new(connection, &path); let tip = InputDataType::Tip(Tip::default()); let node = spatial.node().unwrap(); node.set_enabled(false); @@ -314,13 +314,7 @@ impl OxrControllerInput { if let Some(node) = self.input.spatial.node() { node.set_enabled(enabled); } - tokio::spawn({ - // this is suboptimal since it probably allocates a fresh string every frame - let handle = self.tracked.clone(); - async move { - handle.set_tracked(enabled).await; - } - }); + self.tracked.set_tracked(enabled); } fn update( &mut self, diff --git a/src/objects/input/oxr_hand.rs b/src/objects/input/oxr_hand.rs index 1641c8f..366a74b 100644 --- a/src/objects/input/oxr_hand.rs +++ b/src/objects/input/oxr_hand.rs @@ -7,7 +7,7 @@ use crate::nodes::{ input::{Hand, InputMethod, Joint}, spatial::Spatial, }; -use crate::objects::{ObjectHandle, SpatialRef, Tracked}; +use crate::objects::{AsyncTracked, ObjectHandle, SpatialRef, Tracked}; use crate::{BevyMaterial, DbusConnection, ObjectRegistryRes, PreFrameWait}; use bevy::prelude::Transform as BevyTransform; use bevy::prelude::*; @@ -52,23 +52,14 @@ fn update_hands( joints_query: Query<&XrHandBoneEntities>, ) { let (Some(session), Some(state), Some(ref_space)) = (session, state, ref_space) else { - tokio::task::spawn({ - let left = hands.left.tracked.clone(); - let right = hands.right.tracked.clone(); - async move { - left.set_tracked(false); - right.set_tracked(false); - } - }); + hands.left.tracked.set_tracked(false); + hands.right.tracked.set_tracked(false); return; }; let get_joints = |hand: &mut OxrHandInput| -> Option { let Some(tracker) = hand.tracker.as_ref() else { hand.input.spatial.node().unwrap().set_enabled(false); - let handle = hand.tracked.clone(); - tokio::task::spawn(async move { - handle.set_tracked(false); - }); + hand.tracked.set_tracked(false); return None; }; // this won't be correct with pipelined rendering @@ -182,7 +173,7 @@ pub struct OxrHandInput { input: Arc, capture_manager: CaptureManager, datamap: HandDatamap, - tracked: ObjectHandle, + tracked: AsyncTracked, tracker: Option, captured: bool, material: Handle, @@ -201,7 +192,7 @@ impl OxrHandInput { HandSide::Right => "right", } + "/palm"), ); - let tracked = Tracked::new( + let tracked = AsyncTracked::new( connection, &("/org/stardustxr/Hand/".to_string() + match side { @@ -243,13 +234,7 @@ impl OxrHandInput { if let Some(node) = self.input.spatial.node() { node.set_enabled(enabled); } - tokio::spawn({ - // this is suboptimal since it probably allocates a fresh string every frame - let handle = self.tracked.clone(); - async move { - handle.set_tracked(enabled).await; - } - }); + self.tracked.set_tracked(enabled); } fn update( &mut self, diff --git a/src/objects/mod.rs b/src/objects/mod.rs index 550e209..81f519c 100644 --- a/src/objects/mod.rs +++ b/src/objects/mod.rs @@ -20,6 +20,7 @@ use std::{ marker::PhantomData, sync::{Arc, atomic::Ordering}, }; +use tokio::{sync::mpsc, task::AbortHandle}; use zbus::{Connection, interface, object_server::Interface, zvariant::OwnedObjectPath}; pub mod hmd; @@ -43,6 +44,48 @@ impl Drop for ObjectHandle { } } +/// A wrapper around ObjectHandle that batches async updates +/// instead of spawning a tokio task for each state change +pub struct AsyncTracked { + pub sender: mpsc::UnboundedSender, + pub _handle: ObjectHandle, + pub _abort_handle: AbortHandle, +} + +impl AsyncTracked { + pub fn new(connection: &Connection, path: &str) -> Self { + let handle = Tracked::new(connection, path); + let (sender, mut receiver) = mpsc::unbounded_channel::(); + + // Spawn a single long-running task that processes state updates + let task = tokio::task::spawn({ + let handle = handle.clone(); + async move { + while let Some(is_tracked) = receiver.recv().await { + let _ = handle.set_tracked(is_tracked).await; + } + } + }); + + Self { + sender, + _handle: handle, + _abort_handle: task.abort_handle(), + } + } + + pub fn set_tracked(&self, is_tracked: bool) { + // Just send over channel instead of spawning a task + let _ = self.sender.send(is_tracked); + } +} + +impl Drop for AsyncTracked { + fn drop(&mut self) { + self._abort_handle.abort(); + } +} + pub struct SpatialRef(u64, OwnedNode); impl SpatialRef { pub fn create(connection: &Connection, path: &str) -> (Arc, ObjectHandle) { diff --git a/src/objects/play_space.rs b/src/objects/play_space.rs index ee29293..df66942 100644 --- a/src/objects/play_space.rs +++ b/src/objects/play_space.rs @@ -16,7 +16,7 @@ use zbus::{Connection, ObjectServer, interface}; use crate::{DbusConnection, PreFrameWait, nodes::spatial::Spatial}; -use super::{ObjectHandle, SpatialRef, Tracked}; +use super::{AsyncTracked, ObjectHandle, SpatialRef, Tracked}; pub struct PlaySpacePlugin; impl Plugin for PlaySpacePlugin { @@ -31,7 +31,7 @@ impl Plugin for PlaySpacePlugin { fn setup(connection: Res, mut cmds: Commands) { let (spatial, spatial_handle) = SpatialRef::create(&connection, "/org/stardustxr/PlaySpace"); // the OpenXR session might not exist quite yet - let tracked = Tracked::new(&connection, "/org/stardustxr/PlaySpace"); + let tracked = AsyncTracked::new(&connection, "/org/stardustxr/PlaySpace"); let dbus_connection = connection.clone(); let play_space_data = Arc::new(RwLock::default()); tokio::task::spawn({ @@ -80,12 +80,7 @@ fn update( (session, stage, ref_space, state) else { play_space.bounds.write().drain(..); - tokio::task::spawn({ - let handle = play_space.tracked_handle.clone(); - async move { - handle.set_tracked(false).await; - } - }); + play_space.tracked_handle.set_tracked(false); play_space .spatial @@ -103,12 +98,7 @@ fn update( | SpaceLocationFlags::ORIENTATION_VALID | SpaceLocationFlags::ORIENTATION_TRACKED, ); - tokio::task::spawn({ - let handle = play_space.tracked_handle.clone(); - async move { - handle.set_tracked(is_tracked).await; - } - }); + play_space.tracked_handle.set_tracked(is_tracked); if is_tracked { play_space .spatial @@ -140,7 +130,7 @@ fn update( pub struct PlaySpace { spatial: Arc, _spatial_handle: ObjectHandle, - tracked_handle: ObjectHandle, + tracked_handle: AsyncTracked, bounds: Arc>>, } pub struct PlaySpaceBounds(Arc>>);