use super::context::XrContext;
use super::renderer::XrRenderer;
use crate::ecs::world::World;
use web_time::Instant;
pub fn launch_xr<S: crate::run::State + 'static>(
mut state: S,
) -> Result<(), Box<dyn std::error::Error>> {
#[cfg(feature = "tracing")]
let (log_config, log_file_name) = {
let config = state.log_config();
let file_name = crate::run::log_file_name(state.title(), &config);
(config, file_name)
};
#[cfg(not(feature = "tracing"))]
tracing_subscriber::fmt::init();
#[cfg(all(feature = "tracing", not(feature = "tracy"), not(feature = "chrome")))]
let _guard = {
use tracing_subscriber::EnvFilter;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
let file_appender = match log_config.rotation {
crate::run::LogRotation::Daily => {
tracing_appender::rolling::daily(&log_config.directory, &log_file_name)
}
_ => tracing_appender::rolling::never(&log_config.directory, &log_file_name),
};
let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);
let env_filter = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new(&log_config.default_filter));
tracing_subscriber::registry()
.with(env_filter)
.with(tracing_subscriber::fmt::layer())
.with(
tracing_subscriber::fmt::layer()
.with_writer(non_blocking)
.with_ansi(false),
)
.init();
guard
};
#[cfg(all(feature = "tracy", not(feature = "chrome")))]
let _guard = {
use tracing_subscriber::EnvFilter;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
let file_appender = match log_config.rotation {
crate::run::LogRotation::Daily => {
tracing_appender::rolling::daily(&log_config.directory, &log_file_name)
}
_ => tracing_appender::rolling::never(&log_config.directory, &log_file_name),
};
let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);
let env_filter = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new(&log_config.default_filter));
tracing_subscriber::registry()
.with(env_filter)
.with(tracing_subscriber::fmt::layer())
.with(
tracing_subscriber::fmt::layer()
.with_writer(non_blocking)
.with_ansi(false),
)
.with(tracing_tracy::TracyLayer::default())
.init();
guard
};
#[cfg(all(feature = "chrome", not(feature = "tracy")))]
let _guard = {
use tracing_subscriber::EnvFilter;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
let file_appender = match log_config.rotation {
crate::run::LogRotation::Daily => {
tracing_appender::rolling::daily(&log_config.directory, &log_file_name)
}
_ => tracing_appender::rolling::never(&log_config.directory, &log_file_name),
};
let (non_blocking, file_guard) = tracing_appender::non_blocking(file_appender);
let env_filter = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new(&log_config.default_filter));
let (chrome_layer, chrome_guard) = tracing_chrome::ChromeLayerBuilder::new()
.file("logs/trace.json")
.build();
tracing_subscriber::registry()
.with(env_filter)
.with(tracing_subscriber::fmt::layer())
.with(
tracing_subscriber::fmt::layer()
.with_writer(non_blocking)
.with_ansi(false),
)
.with(chrome_layer)
.init();
(file_guard, chrome_guard)
};
#[cfg(all(feature = "tracy", feature = "chrome"))]
let _guard = {
use tracing_subscriber::EnvFilter;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
let file_appender = match log_config.rotation {
crate::run::LogRotation::Daily => {
tracing_appender::rolling::daily(&log_config.directory, &log_file_name)
}
_ => tracing_appender::rolling::never(&log_config.directory, &log_file_name),
};
let (non_blocking, file_guard) = tracing_appender::non_blocking(file_appender);
let env_filter = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new(&log_config.default_filter));
let (chrome_layer, chrome_guard) = tracing_chrome::ChromeLayerBuilder::new()
.file("logs/trace.json")
.build();
tracing_subscriber::registry()
.with(env_filter)
.with(tracing_subscriber::fmt::layer())
.with(
tracing_subscriber::fmt::layer()
.with_writer(non_blocking)
.with_ansi(false),
)
.with(tracing_tracy::TracyLayer::default())
.with(chrome_layer)
.init();
(file_guard, chrome_guard)
};
tracing::info!("Initializing OpenXR mode: {}", state.title());
let mut xr_context = XrContext::new()?;
let mut world = World::default();
let resolution = xr_context.resolution();
let surface_format = xr_context.surface_format();
tracing::info!(
"Creating XR renderer with resolution {}x{}",
resolution.0,
resolution.1
);
let mut renderer = XrRenderer::new(
xr_context.device(),
xr_context.queue(),
resolution,
surface_format,
)?;
tracing::info!("XR renderer created successfully");
renderer.initialize_fonts(&mut world);
world.resources.xr.locomotion_enabled = true;
world.resources.frame_schedule = crate::schedule::build_default_frame_schedule();
tracing::info!("Calling state.initialize()");
state.initialize(&mut world);
tracing::info!("State initialized successfully");
if let Some(initial_yaw) = world.resources.xr.initial_player_yaw {
xr_context.set_player_yaw(initial_yaw);
}
if let Some(initial_position) = world.resources.xr.initial_player_position {
xr_context.set_player_position(initial_position);
} else {
let character_controllers: Vec<_> = world
.core
.query_entities(
crate::ecs::world::CHARACTER_CONTROLLER | crate::ecs::world::LOCAL_TRANSFORM,
)
.collect();
if let Some(entity) = character_controllers.first()
&& let Some(transform) = world.core.get_local_transform(*entity)
{
let floor_offset = if let Some(cc) = world.core.get_character_controller(*entity) {
match &cc.shape {
crate::ecs::physics::ColliderShape::Capsule {
half_height,
radius,
} => half_height + radius,
crate::ecs::physics::ColliderShape::Cylinder { half_height, .. } => {
*half_height
}
crate::ecs::physics::ColliderShape::Cuboid { hy, .. } => *hy,
crate::ecs::physics::ColliderShape::Ball { radius } => *radius,
_ => 1.0,
}
} else {
1.0
};
let floor_position = nalgebra_glm::vec3(
transform.translation.x,
transform.translation.y - floor_offset,
transform.translation.z,
);
xr_context.set_player_position(floor_position);
}
}
renderer.process_commands(xr_context.device(), xr_context.queue(), &mut world);
let mut last_render_time = Instant::now();
let initial_time = Instant::now();
tracing::info!("Starting XR render loop");
loop {
if !xr_context.poll_events()? {
tracing::info!("XR session ended, exiting");
break;
}
let Some(mut frame_ctx) = xr_context.begin_frame()? else {
std::thread::sleep(std::time::Duration::from_millis(10));
continue;
};
let now = Instant::now();
let delta_time = (now - last_render_time).as_secs_f32();
last_render_time = now;
world.resources.window.timing.delta_time = delta_time;
world.resources.window.timing.raw_delta_time = delta_time;
world.resources.window.timing.uptime_milliseconds = (now - initial_time).as_millis() as u64;
world.resources.window.cached_viewport_size = Some(resolution);
let locomotion_enabled = world.resources.xr.locomotion_enabled;
let locomotion_speed = world.resources.xr.locomotion_speed;
let xr_input = xr_context.update_input(
delta_time,
frame_ctx.frame_state.predicted_display_time,
locomotion_enabled,
locomotion_speed,
)?;
frame_ctx.player_yaw = xr_input.player_yaw;
frame_ctx.player_position = xr_input.player_position;
world.resources.xr.input = Some(xr_input);
crate::ecs::graphics::systems::day_night_cycle_system(&mut world);
state.run_systems(&mut world);
#[cfg(all(feature = "mcp", not(target_arch = "wasm32")))]
crate::run::process_mcp_commands_with_state(&mut world, &mut state);
crate::run::run_frame_systems(&mut world);
let character_controllers: Vec<_> = world
.core
.query_entities(
crate::ecs::world::CHARACTER_CONTROLLER | crate::ecs::world::LOCAL_TRANSFORM,
)
.collect();
if let Some(entity) = character_controllers.first()
&& let Some(transform) = world.core.get_local_transform(*entity)
{
let floor_offset = if let Some(cc) = world.core.get_character_controller(*entity) {
match &cc.shape {
crate::ecs::physics::ColliderShape::Capsule {
half_height,
radius,
} => half_height + radius,
crate::ecs::physics::ColliderShape::Cylinder { half_height, .. } => {
*half_height
}
crate::ecs::physics::ColliderShape::Cuboid { hy, .. } => *hy,
crate::ecs::physics::ColliderShape::Ball { radius } => *radius,
_ => 1.0,
}
} else {
1.0
};
let floor_position = nalgebra_glm::vec3(
transform.translation.x,
transform.translation.y - floor_offset,
transform.translation.z,
);
xr_context.set_player_position(floor_position);
}
renderer.process_commands(xr_context.device(), xr_context.queue(), &mut world);
renderer.update_per_frame_passes(&mut world);
for eye_index in 0..frame_ctx.eye_count() {
let view_matrix = frame_ctx.view_matrix(eye_index);
let projection_matrix = frame_ctx.projection_matrix(eye_index);
let camera_position = frame_ctx.camera_position(eye_index);
world.resources.xr.camera_override =
Some(crate::ecs::camera::queries::CameraMatrices {
camera_position,
projection: projection_matrix,
view: view_matrix,
});
let eye_texture_view = xr_context.get_eye_texture_view(&frame_ctx, eye_index);
renderer.render_eye(
xr_context.device(),
xr_context.queue(),
&mut world,
eye_texture_view,
resolution,
)?;
}
world.resources.xr.camera_override = None;
world.resources.xr.input = None;
xr_context.end_frame(frame_ctx)?;
}
Ok(())
}