Skip to main content

bevy_wisp/
error.rs

1//! Live error reporting for wisp shaders.
2//!
3//! Wisp is built for live-coding: when a shader edit fails to load or compile, the
4//! previous working version keeps rendering and the error is surfaced here (and via
5//! the log). The `ui` feature's panel displays [`WispErrors`] on screen.
6//!
7//! Load errors (parse/validation/schema) never replace the loaded asset, so
8//! last-good rendering comes for free. Pipeline errors are mirrored from the
9//! render world each frame.
10
11use crate::asset::Wisp;
12use bevy_asset::AssetLoadFailedEvent;
13use bevy_asset::prelude::{AssetEvent, AssetServer};
14use bevy_ecs::prelude::*;
15use bevy_log::error;
16use std::collections::BTreeMap;
17
18/// The current wisp errors, for display and logging.
19#[derive(Resource, Clone, Debug, Default, PartialEq)]
20pub struct WispErrors {
21    /// Asset load errors (parse/validation/schema), keyed by asset path.
22    pub load: BTreeMap<String, String>,
23    /// Pipeline compilation errors, keyed by pass entry point name.
24    pub pipeline: BTreeMap<String, String>,
25}
26
27impl WispErrors {
28    pub fn is_empty(&self) -> bool {
29        self.load.is_empty() && self.pipeline.is_empty()
30    }
31}
32
33/// Record failed wisp loads and clear them once the asset (re)loads.
34pub(crate) fn collect_load_errors(
35    mut failed: MessageReader<AssetLoadFailedEvent<Wisp>>,
36    mut events: MessageReader<AssetEvent<Wisp>>,
37    asset_server: Res<AssetServer>,
38    mut errors: ResMut<WispErrors>,
39) {
40    for event in failed.read() {
41        let path = event.path.to_string();
42        let message = event.error.to_string();
43        if errors.load.get(&path) != Some(&message) {
44            error!("failed to load wisp `{path}`:\n{message}");
45            errors.load.insert(path, message);
46        }
47    }
48    for event in events.read() {
49        if let AssetEvent::LoadedWithDependencies { id } | AssetEvent::Modified { id } = event
50            && let Some(path) = asset_server.get_path(*id)
51        {
52            errors.load.remove(&path.to_string());
53        }
54    }
55}