rusting_engine 2.7.0

Vulkan 3D game engine with GPU-accelerated physics for massive physics-heavy scenes
//! `GameScene` calls for shader packs; the pack logic lives in
//! `rendering::shader_pack`.

use bevy_ecs::prelude::World;

use crate::project_runner::{GameScene, ProjectFolder};
use crate::rendering::shader_pack::{
    builtin_info, PackOptionValue, ShaderPackInfo, ShaderPackState, ShaderWorld,
};
use crate::runtime::{KeyCode, RuntimeInput};

/// The world's shader pack state, set up from the project on first use.
pub(crate) fn state(
    world: &mut World,
) -> bevy_ecs::world::Mut<'_, ShaderPackState> {
    let project = world.get_resource::<ProjectFolder>().map(|f| f.0.clone());
    let mut state = world.get_resource_or_insert_with(ShaderPackState::default);
    state.init(project.as_deref());
    state
}

/// Once a frame before extraction: F3+T reloads the pack, and debug
/// builds reload it when one of its files changes.
pub(crate) fn sync(world: &mut World) {
    let reload = world.get_resource::<RuntimeInput>().is_some_and(|input| {
        input.key_held(KeyCode::F3) && input.key_just_pressed(KeyCode::KeyT)
    });
    let mut state = state(world);
    if reload {
        state.reload();
    } else if cfg!(debug_assertions) {
        state.poll();
    }
}

impl GameScene<'_> {
    /// Every shader pack the game can use: the built-in shading (id `""`)
    /// first, then the game's packs under `assets/shaderpacks/` by id, then
    /// the player's packs (`user/<folder>`) under the user data folder.
    pub fn shader_packs(&mut self) -> Vec<ShaderPackInfo> {
        state(self.world).list()
    }

    /// The active pack's info; the built-in entry when none is active.
    pub fn active_shader_pack(&mut self) -> ShaderPackInfo {
        let state = state(self.world);
        state
            .active
            .as_ref()
            .map_or_else(builtin_info, |active| active.pack.info.clone())
    }

    /// Switches to pack `id` (`""` for the built-in shading) at its default
    /// preset. An unknown id, or a pack that does not parse or compile,
    /// returns the error as `file:line: message` and keeps the current pack.
    pub fn set_shader_pack(&mut self, id: &str) -> Result<(), String> {
        state(self.world).set_pack(id)
    }

    /// The active preset: `Low`, `Medium`, `High`, `Ultra`, or `Custom`
    /// after an option change no preset matches; empty with no pack.
    pub fn shader_preset(&mut self) -> String {
        state(self.world).preset.clone()
    }

    /// Applies one of the active pack's presets (`Low` to `Ultra`).
    pub fn set_shader_preset(&mut self, preset: &str) -> Result<(), String> {
        state(self.world).set_preset(preset)
    }

    /// Current value of option `id` of the active pack.
    pub fn shader_option(&mut self, id: &str) -> Option<PackOptionValue> {
        state(self.world)
            .active
            .as_ref()
            .and_then(|active| active.values.get(id).cloned())
    }

    /// Sets option `id` of the active pack and rebuilds its shaders. The
    /// preset becomes `Custom` unless the options now equal a preset.
    pub fn set_shader_option(
        &mut self,
        id: &str,
        value: PackOptionValue,
    ) -> Result<(), String> {
        state(self.world).set_option(id, value)
    }

    /// Named numbers from the active pack's `values`, else from the
    /// project's default pack (`project.json` `shader_pack`).
    pub fn shader_pack_value(&mut self, name: &str) -> Option<Vec<f32>> {
        state(self.world).value(name)
    }

    /// Grows by one on every pack switch, reload, preset or option change.
    pub fn shader_pack_revision(&mut self) -> u64 {
        state(self.world).revision
    }

    /// Reads the active pack's files again (F3+T does the same).
    pub fn reload_shader_pack(&mut self) {
        state(self.world).reload();
    }

    /// The last pack error as `file:line: message`, until a pack loads.
    pub fn shader_pack_error(&mut self) -> Option<String> {
        state(self.world).error.clone()
    }

    /// Hands shader packs the world state for the `frame` block: time of
    /// day, sun and moon, weather and what the camera is in. Call it every
    /// frame the values change; until the first call the sun comes from the
    /// first directional light and the rest is 0.
    pub fn set_shader_world(&mut self, world: ShaderWorld) {
        state(self.world).world = Some(world);
    }
}

/// Per frame: while a pack has failed, a red box at the top left names the
/// pack, the error and the fallback in use.
#[cfg(feature = "ui")]
pub(crate) fn draw_banner(
    ui: bevy_ecs::system::Res<super::RuntimeUi>,
    state: Option<bevy_ecs::system::Res<ShaderPackState>>,
) {
    let Some(text) = state
        .as_ref()
        .and_then(|state| state.failed_banner.as_ref())
    else {
        return;
    };
    egui::Area::new(egui::Id::new("rusting.shader_pack_banner"))
        .anchor(egui::Align2::LEFT_TOP, egui::vec2(8.0, 8.0))
        .fade_in(false)
        .interactable(false)
        .order(egui::Order::Foreground)
        .show(ui.context(), |ui| {
            egui::Frame::new()
                .fill(egui::Color32::from_rgb(170, 20, 20))
                .inner_margin(egui::Margin::symmetric(8, 4))
                .corner_radius(4.0)
                .show(ui, |ui| {
                    ui.add(
                        egui::Label::new(
                            egui::RichText::new(text)
                                .color(egui::Color32::WHITE),
                        )
                        .selectable(false),
                    );
                });
        });
}