use haply::{ compute_total_force, device_model::*, HaplyDevice, Sphere };
use std::io::Write;
const HTTP_BASE: &str = "http://localhost:10001";
const WS_URL: &str = "ws://localhost:10001/";
const LOOP_TARGET_PERIOD_US: u64 = 250;
#[cfg(feature = "tracy")]
fn init_tracy_bridge() {
use std::sync::Once;
use tracing_subscriber::prelude::*;
static ONCE: Once = Once::new();
ONCE.call_once(|| {
let _ = tracing_log::LogTracer::builder().with_max_level(log::LevelFilter::Trace).init();
let tracy_layer = tracing_tracy::TracyLayer::default();
let subscriber = tracing_subscriber
::registry()
.with(tracing_subscriber::filter::LevelFilter::TRACE)
.with(tracy_layer);
let _ = tracing::subscriber::set_global_default(subscriber);
});
}
#[cfg(not(feature = "tracy"))]
fn init_tracy_bridge() {
println!("Tracy feature is disabled; run with `--features tracy` to enable profiling.");
}
fn has_flag(args: &[String], flag: &str) -> bool {
args.iter().any(|a| a == flag)
}
fn print_help() {
println!("Tracy profiling demo (haptic loop + versegrip orientation)");
println!("Usage:");
println!(" cargo run --example test_tracy --features tracy");
println!(" cargo run --example test_tracy --features tracy -- --help");
println!(" cargo run --example test_tracy --features tracy -- full");
println!("Flags:");
println!(" full Disable console output and pacing sleeps for max-throughput profiling");
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let args: Vec<String> = std::env::args().skip(1).collect();
if has_flag(&args, "--help") || has_flag(&args, "-h") {
print_help();
return Ok(());
}
let full_throttle = has_flag(&args, "full");
init_tracy_bridge();
let mut device = HaplyDevice::new(HTTP_BASE, WS_URL).await?;
let configs = device.list_devices().await.unwrap_or_default();
if configs.is_empty() {
println!("No devices found. Exiting.");
return Ok(());
}
let inverse_id = match
configs.iter().find_map(|cfg| {
match cfg {
Config::DeviceConfig(dc) => Some(dc.device_info.id.clone()),
_ => None,
}
})
{
Some(id) => id,
None => {
println!("No Inverse3 device found. Exiting.");
return Ok(());
}
};
let versegrip_id = configs.iter().find_map(|cfg| {
match cfg {
Config::WVGConfig(wvg) => Some(wvg.id.clone()),
Config::VGConfig(vg) => Some(vg.id.clone()),
_ => None,
}
});
let _ = device.configure_session(
SessionConfigure {
profile: Some(ProfileConfig {
name: "test_tracy".to_string(),
required_version: Some(">=3.5".to_string()),
}),
basis: None,
serialization: None,
sdf: None,
},
Some(true)
).await;
let sphere = Sphere {
center: [0.0, -0.1, 0.18],
radius: 0.05,
stiffness: 500.0,
magnetism: 0.0,
damping: 0.1,
};
let objects: Vec<Box<dyn haply::physics_model::PhysicalObject>> = vec![Box::new(sphere)];
let _ = device.update_force(
vec![ForceInput {
device_id: inverse_id.clone(),
forces: Force { x: 0.0, y: 0.0, z: 0.0 },
}],
Some(false),
Some(true)
).await;
if !full_throttle {
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
}
if !full_throttle {
println!("Running Tracy haptic loop (Ctrl+C to stop)...");
}
let mut tick: u64 = 0u64;
let target_period = std::time::Duration::from_micros(LOOP_TARGET_PERIOD_US);
loop {
let loop_start = std::time::Instant::now();
#[cfg(feature = "tracy")]
let _loop_body_span = tracy_client::span!("example::loop_body");
tick += 1;
if let Some(vg_id) = &versegrip_id {
if tick % 4 == 0 {
#[cfg(feature = "tracy")]
let _probe_span = tracy_client::span!("example::probe_orientation");
let _ = device.probe_orientation(vec![vg_id.clone()], Some(false)).await;
}
}
let state = device.read_state().await?;
let pos = state.inverse3
.iter()
.find(|d| d.device_id == inverse_id)
.and_then(|d| d.state.cursor_position)
.unwrap_or_default();
let total_force = {
#[cfg(feature = "tracy")]
let _force_span = tracy_client::span!("example::compute_total_force");
compute_total_force(pos.into(), &objects)
};
let force = Force {
x: total_force[0],
y: total_force[1],
z: total_force[2],
};
{
#[cfg(feature = "tracy")]
let _update_force_span = tracy_client::span!("example::update_force");
let _ = device.update_force(
vec![ForceInput {
device_id: inverse_id.clone(),
forces: force,
}],
Some(true),
Some(true)
).await;
}
let orientation = state.wireless_verse_grip
.iter()
.find_map(|d| d.state.orientation)
.or_else(|| state.custom_verse_grip.iter().find_map(|d| d.state.orientation))
.or_else(|| state.verse_grip.iter().find_map(|d| d.state.orientation));
if !full_throttle {
let orientation_text = match orientation {
Some(o) => format!("({:.4},{:.4},{:.4},{:.4})", o.w, o.x, o.y, o.z),
None => "None".to_string(),
};
if tick % 400 == 0 {
print!(
"\rPos: ({:.4},{:.4},{:.4}) Force: ({:.4},{:.4},{:.4}) Ori: {} ",
pos.x,
pos.y,
pos.z,
total_force[0],
total_force[1],
total_force[2],
orientation_text
);
{
#[cfg(feature = "tracy")]
let _flush_span = tracy_client::span!("example::stdout_flush");
std::io::stdout().flush().ok();
}
}
}
#[cfg(feature = "tracy")]
drop(_loop_body_span);
#[cfg(feature = "tracy")]
{
let elapsed = loop_start.elapsed();
let sleep_remainder = if elapsed < target_period {
target_period - elapsed
} else {
std::time::Duration::ZERO
};
let overrun = if elapsed > target_period {
elapsed - target_period
} else {
std::time::Duration::ZERO
};
let loop_cycle_us = elapsed.as_micros() as f64;
let sleep_remainder_us = sleep_remainder.as_micros() as f64;
let overrun_us = overrun.as_micros() as f64;
tracy_client::plot!("loop_cycle_us", loop_cycle_us);
tracy_client::plot!("sleep_remainder_us", sleep_remainder_us);
tracy_client::plot!("overrun_us", overrun_us);
tracy_client::frame_mark();
}
if !full_throttle {
let elapsed = loop_start.elapsed();
if elapsed < target_period {
tokio::time::sleep(target_period - elapsed).await;
}
}
}
}