status bar
git clone https://git.lucas.co/cce-status-interface.git
fix(icons): the glyph cache belongs to the renderer, not the process
A lost Wayland transport is repaired by opening a new session around the
same Application, which rebuilds the renderer and its image table — so
every id `tinted_icon` had cached named an image that no longer existed.
Draws for an unknown id are skipped rather than reported, which is why a
reconnected bar came back with its numbers and not one glyph, and stayed
that way until the process was restarted. The stats segment reconnected
at 22:53 last night and lost all five.
`renderer_init` now drops the cache on every renderer after the first
(the first is the one `new()`'s uploads are queued for) and asks for a
rebuild, which re-uploads into the renderer just created.
Verified in a shadow session with CCE_UI_FAULT_RECONNECT: before the
fix, the injected drop left the numbers alone in the bubble; after it,
the row comes back whole.
Co-Authored-By: Claude Opus 5 <[email protected]>
CLAUDE.md | 9 ++++++++-
src/icons.rs | 30 ++++++++++++++++++++++++++++--
src/main.rs | 20 ++++++++++++++++++++
3 files changed, 56 insertions(+), 3 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
index 46d3479..7a21e7e 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -118,7 +118,14 @@ The glyphs come from the **cce-icons** crate via `cce_ui::icons_dir()`
`cce_ui::upload_icon`: a `Prim::Image` has alpha and no color, and the
artwork is white, so `icons.rs::tinted_icon` rasterizes the SVG itself
(`cce_ui::rasterize_svg`), multiplies it by the readout's raw-sRGB color and
-uploads it, cached per `(name, px, color)` for the life of the process. A
+uploads it, cached per `(name, px, color)` — for the life of the RENDERER,
+not the process: the cache holds renderer image ids, and a reconnect
+(cce-ui repairs a lost transport by opening a new session around the same
+`Application`) rebuilds the renderer and its image table, leaving every
+cached id naming nothing. A draw for an unknown id is skipped rather than
+reported, so a reconnected bar came back with its numbers and no glyphs at
+all; `renderer_init` now calls `icons::drop_textures()` on every renderer
+after the first, and the rebuild it forces re-uploads them. A
glyph that fails to load falls back to the old text readout ("Cpu 45%"), so
a bar started without the icon set is still attributable; **a shadow session
needs `CCE_ICONS_DIR` exported into the spawn**, its HOME being elsewhere,
diff --git a/src/icons.rs b/src/icons.rs
index 39a3c73..13d4af0 100644
--- a/src/icons.rs
+++ b/src/icons.rs
@@ -18,13 +18,16 @@
use std::collections::HashMap;
use std::sync::Mutex;
+/// `(name, px, tint)` → the uploaded texture, or `None` for a glyph that could
+/// not be loaded (the miss is cached too, so the warning is logged once).
+type Key = (String, u32, [u8; 3]);
+static CACHE: Mutex<Option<HashMap<Key, Option<(u32, u32, u32)>>>> = Mutex::new(None);
+
/// Rasterize `<name>.svg` from cce-icons at `px` on its longer side, tinted
/// to `rgb` (raw sRGB, like every text color here — uploaded images are
/// sampled as sRGB), and upload it as a renderer texture. Returns the image
/// id plus the pixel size for `PaintCtx::image`.
pub(crate) fn tinted_icon(name: &str, px: u32, rgb: [u8; 3]) -> Option<(u32, u32, u32)> {
- type Key = (String, u32, [u8; 3]);
- static CACHE: Mutex<Option<HashMap<Key, Option<(u32, u32, u32)>>>> = Mutex::new(None);
let key = (name.to_string(), px, rgb);
let mut guard = CACHE.lock().unwrap();
let cache = guard.get_or_insert_with(HashMap::new);
@@ -67,3 +70,26 @@ pub(crate) fn tint_of(color: [f32; 4]) -> [u8; 3] {
(color[2] * 255.0).round() as u8,
]
}
+
+/// Forget every uploaded glyph, freeing its texture.
+///
+/// The cache holds **renderer** image ids, and a renderer does not outlive its
+/// session: `cce-ui`'s `window_runner` repairs a lost Wayland transport by
+/// opening a new session around the same `Application`, which rebuilds the
+/// renderer and with it the image table. The cached ids then name images that
+/// no longer exist, and a draw for an unknown id is skipped rather than
+/// reported — so a bar that reconnected came back with its numbers and no
+/// glyphs at all, until the process was restarted.
+///
+/// Called from `Application::renderer_init` when the renderer it is handed is a
+/// *replacement*; the first renderer of the process is the one the uploads
+/// queued from `new()` are waiting for, so dropping them there would only
+/// upload, destroy and re-upload the same five glyphs before the first frame.
+pub(crate) fn drop_textures() {
+ let mut guard = CACHE.lock().unwrap();
+ let Some(cache) = guard.as_mut() else { return };
+ for (id, _, _) in cache.values().flatten() {
+ cce_ui::vk::free_image(*id);
+ }
+ cache.clear();
+}
diff --git a/src/main.rs b/src/main.rs
index 5dd3a80..f6a08aa 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -562,6 +562,11 @@ struct StatusApp {
selected_module_side: Option<Side>,
status_hide_mode: bool,
adjust_position_mode: bool,
+ /// Whether a renderer has been handed to this app yet. The first one is
+ /// the one `new()`'s glyph uploads are queued for; every later one is a
+ /// reconnect, and the ids cached in `icons` name images that died with
+ /// the renderer being replaced — see `renderer_init`.
+ seen_renderer: bool,
}
/// The Wayland `app_id` a segment presents — the compositor places segments
@@ -1534,6 +1539,7 @@ impl cce_ui::engine::Application for StatusApp {
selected_module_side: selected_module.as_ref().map(|(_, s)| s.clone()),
status_hide_mode: false,
adjust_position_mode: false,
+ seen_renderer: false,
};
app.rebuild_layout();
@@ -1953,6 +1959,20 @@ impl cce_ui::engine::Application for StatusApp {
Some(pc.finish())
}
+ /// A reconnect is a new session around the SAME app (cce-ui's
+ /// `window_runner` repairs a lost transport rather than restarting the
+ /// process), and the renderer is rebuilt with it — so the glyph textures
+ /// `icons::tinted_icon` cached ids for no longer exist. A draw for an
+ /// unknown image id is skipped silently, which is why a reconnected bar
+ /// kept its numbers and lost every glyph. Drop the cache and rebuild, so
+ /// the next layout uploads into the renderer just created.
+ fn renderer_init(&mut self, _renderer: &mut cce_ui::vk::VkRenderer) {
+ if std::mem::replace(&mut self.seen_renderer, true) {
+ crate::icons::drop_textures();
+ self.needs_rebuild = true;
+ }
+ }
+
fn display_list_text(&self) -> bool {
true
}