nightshade 0.13.2

A cross-platform data-oriented game engine.
Documentation
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(())
}