feat(objects): async tracked abstraction

This commit is contained in:
Nova
2025-10-11 21:46:27 -07:00
parent a080560c9c
commit 38a0520299
4 changed files with 59 additions and 47 deletions

View File

@@ -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<ModelPart>,
capture_manager: CaptureManager,
datamap: ControllerDatamap,
tracked: ObjectHandle<Tracked>,
tracked: AsyncTracked,
space: Option<XrSpace>,
}
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,

View File

@@ -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<openxr::HandJointLocations> {
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<InputMethod>,
capture_manager: CaptureManager,
datamap: HandDatamap,
tracked: ObjectHandle<Tracked>,
tracked: AsyncTracked,
tracker: Option<openxr::HandTracker>,
captured: bool,
material: Handle<BevyMaterial>,
@@ -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,

View File

@@ -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<I: Interface> Drop for ObjectHandle<I> {
}
}
/// A wrapper around ObjectHandle<Tracked> that batches async updates
/// instead of spawning a tokio task for each state change
pub struct AsyncTracked {
pub sender: mpsc::UnboundedSender<bool>,
pub _handle: ObjectHandle<Tracked>,
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::<bool>();
// 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<Spatial>, ObjectHandle<SpatialRef>) {

View File

@@ -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<DbusConnection>, 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>,
_spatial_handle: ObjectHandle<SpatialRef>,
tracked_handle: ObjectHandle<Tracked>,
tracked_handle: AsyncTracked,
bounds: Arc<RwLock<Vec<(f64, f64)>>>,
}
pub struct PlaySpaceBounds(Arc<RwLock<Vec<(f64, f64)>>>);