From 0437c364638c828fb8099a1853f14fa86b08ec3a Mon Sep 17 00:00:00 2001 From: funman300 Date: Wed, 27 May 2026 19:36:05 -0700 Subject: [PATCH] fix(assets,theme): remove assert in svg_loader, log theme failures, fix default theme id (#58, #63, #64) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- solitaire_data/src/settings.rs | 2 +- solitaire_engine/src/assets/svg_loader.rs | 19 ++++++++------ solitaire_engine/src/theme/registry.rs | 31 +++++++++++++++++++---- 3 files changed, 38 insertions(+), 14 deletions(-) diff --git a/solitaire_data/src/settings.rs b/solitaire_data/src/settings.rs index b9f6d9b..70b4684 100644 --- a/solitaire_data/src/settings.rs +++ b/solitaire_data/src/settings.rs @@ -280,7 +280,7 @@ fn default_music_volume() -> f32 { } fn default_theme_id() -> String { - "classic".to_string() + "dark".to_string() } /// Default tooltip-hover dwell delay in seconds. Mirrors diff --git a/solitaire_engine/src/assets/svg_loader.rs b/solitaire_engine/src/assets/svg_loader.rs index c876cfd..c7a831a 100644 --- a/solitaire_engine/src/assets/svg_loader.rs +++ b/solitaire_engine/src/assets/svg_loader.rs @@ -24,6 +24,7 @@ use std::sync::{Arc, OnceLock}; use bevy::asset::io::Reader; use bevy::asset::{AssetLoader, LoadContext, RenderAssetUsages}; use bevy::image::Image; +use bevy::log::warn; use bevy::math::UVec2; use bevy::reflect::TypePath; use bevy::render::render_resource::{Extent3d, TextureDimension, TextureFormat}; @@ -156,7 +157,7 @@ pub fn rasterize_svg(svg_bytes: &[u8], target: UVec2) -> Result Arc { static DB: OnceLock> = OnceLock::new(); DB.get_or_init(|| { let mut db = fontdb::Database::new(); - db.load_font_data(BUNDLED_FONT_BYTES.to_vec()); - assert!( - db.faces().next().is_some(), - "bundled FiraMono failed to parse — binary is corrupt" - ); + let loaded_faces = db.load_font_source(fontdb::Source::Binary(Arc::new( + BUNDLED_FONT_BYTES.to_vec(), + ))); + if loaded_faces.is_empty() { + let e = "no faces loaded from bundled bytes"; + warn!("Failed to load bundled FiraMono font: {e}"); + } Arc::new(db) }) .clone() diff --git a/solitaire_engine/src/theme/registry.rs b/solitaire_engine/src/theme/registry.rs index 918a859..a197728 100644 --- a/solitaire_engine/src/theme/registry.rs +++ b/solitaire_engine/src/theme/registry.rs @@ -21,11 +21,12 @@ use std::path::Path; +use bevy::log::warn; use bevy::prelude::{App, Plugin, Resource, Startup}; use serde::Deserialize; use super::ThemeMeta; -use crate::assets::{user_theme_dir, DARK_THEME_MANIFEST_URL}; +use crate::assets::{DARK_THEME_MANIFEST_URL, user_theme_dir}; /// One entry in the [`ThemeRegistry`] — the data the picker UI needs /// to render a row and load the theme on selection. @@ -143,7 +144,7 @@ fn classic_entry() -> ThemeEntry { /// Walks `user_dir`, treating every immediate subdirectory as a /// candidate theme. A subdirectory contributes one entry if and only /// if it contains a `theme.ron` whose `meta` block parses cleanly and -/// passes `ThemeMeta::validate`. Failed candidates are silently +/// passes `ThemeMeta::validate`. Failed candidates are warned and /// skipped — broken themes don't poison discovery. fn discover_user_themes(user_dir: &Path) -> Vec { let mut out = Vec::new(); @@ -184,9 +185,29 @@ struct ManifestMetaOnly { /// [`ThemeEntry`]. Returns `None` for any I/O / parse / validation /// failure — discovery is best-effort. fn read_meta_only(manifest_path: &Path) -> Option { - let bytes = std::fs::read(manifest_path).ok()?; - let parsed: ManifestMetaOnly = ron::de::from_bytes(&bytes).ok()?; - parsed.meta.validate().ok()?; + let theme_id = manifest_path + .parent() + .and_then(Path::file_name) + .and_then(|name| name.to_str()) + .unwrap_or(""); + let bytes = match std::fs::read(manifest_path) { + Ok(bytes) => bytes, + Err(e) => { + warn!("Skipping theme '{}': {}", theme_id, e); + return None; + } + }; + let parsed: ManifestMetaOnly = match ron::de::from_bytes(&bytes) { + Ok(parsed) => parsed, + Err(e) => { + warn!("Skipping theme '{}': {}", theme_id, e); + return None; + } + }; + if let Err(e) = parsed.meta.validate() { + warn!("Skipping theme '{}': {}", theme_id, e); + return None; + } let id = parsed.meta.id.clone(); let display_name = parsed.meta.name.clone(); let manifest_url = format!("themes://{id}/theme.ron");