perf(skins): skip redundant skin parsing, base reloads and theme writes
Skin handling did more work than it needed: - the bundled-skin scan fully parsed each of the 9 bundled skin files to read name, author, dark and images from their first 327-345 bytes. The build copies the rest of ui.toml into every skin, so each file is 51-54 KB; - setActiveSkin() reloaded ui.toml right after main() had loaded it, a second full parse with nothing merged in between; - in the Windows single-file exe, every getBundledSkinsDirectory() call (skin refresh, the dragonx entry, each image-dir lookup) re-extracted all embedded themes with a blind overwrite: more than once per launch, and again on skin changes. Now: - bundled skins are parsed only up to the first table header after [theme] other than [theme.images]. Anything unusual (no [theme], images defined later, a parse error in the slice) falls back to the full parse, and user skins are still parsed in full; - UISchema tracks whether its state is exactly the base (the last change was a base load and no hot-reload is pending), and setActiveSkin() skips reloadBase() in that case; - the bundled-skins directory is resolved once per process (re-checked with exists()), and theme extraction rewrites a file only when its bytes differ, through the new resources::writeFileIfDifferent(). It compares bytes, not sizes, so a same-length palette edit still updates. Measured on the Lite build (median of 5 runs): - bundled-skin scan: 15 -> 1 ms; - from "Initializing ObsidianDragon" to the activated skin: 29 -> 12 ms; - ui.toml is loaded once per launch instead of twice, and the light skin merges its overlay exactly once. A Python mirror of the header-slice parse agrees with a full parse on name, author, dark and images for all 18 source and build-expanded skin files, and full screenshot sweeps over all 10 skins match the previous build within noise. New unit tests cover writeFileIfDifferent(): missing file, identical bytes (mtime untouched), same-length change, and a longer file truncated. Behaviour changes: - a bundled skin with a TOML error after its header is now listed instead of skipped; the error surfaces only when it is selected; - deleting a single file inside the extracted bundled-themes dir mid-session is repaired only at the next launch; - the "Extracted N embedded bundled themes" count includes files that were already current. Not verifiable here: Linux has no embedded themes, so the new writeFileIfDifferent() ran only in the unit tests and the extraction path was only compiled, in the Windows cross-build. That a second Windows launch leaves the theme files' mtimes untouched is still to be checked on Windows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -122,6 +122,27 @@ const EmbeddedTheme* getEmbeddedThemes()
|
||||
return themes.data();
|
||||
}
|
||||
|
||||
int writeFileIfDifferent(const std::string& path, const uint8_t* data, size_t size)
|
||||
{
|
||||
{
|
||||
std::ifstream cur(path, std::ios::binary | std::ios::ate);
|
||||
if (cur.is_open()) {
|
||||
const std::streamoff len = cur.tellg();
|
||||
if (len >= 0 && static_cast<size_t>(len) == size) {
|
||||
std::string buf(size, '\0');
|
||||
cur.seekg(0);
|
||||
if (size == 0 || (cur.read(&buf[0], static_cast<std::streamsize>(size)) &&
|
||||
std::memcmp(buf.data(), data, size) == 0))
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
} // reader closed before the writer opens (Windows sharing)
|
||||
std::ofstream f(path, std::ios::binary | std::ios::trunc);
|
||||
if (!f.is_open()) return -1;
|
||||
f.write(reinterpret_cast<const char*>(data), static_cast<std::streamsize>(size));
|
||||
return f.good() ? 1 : -1;
|
||||
}
|
||||
|
||||
int extractBundledThemes(const std::string& destDir)
|
||||
{
|
||||
namespace fs = std::filesystem;
|
||||
@@ -140,14 +161,12 @@ int extractBundledThemes(const std::string& destDir)
|
||||
|
||||
for (const auto* t = themes; t->data != nullptr; ++t) {
|
||||
fs::path dest = fs::path(destDir) / t->filename;
|
||||
// Always overwrite — bundled themes should match the binary version
|
||||
std::ofstream f(dest, std::ios::binary);
|
||||
if (f.is_open()) {
|
||||
f.write(reinterpret_cast<const char*>(t->data), t->size);
|
||||
f.close();
|
||||
DEBUG_LOGF("[INFO] EmbeddedResources: Extracted theme: %s (%zu bytes)\n",
|
||||
t->filename, t->size);
|
||||
count++;
|
||||
// Bundled themes must match the binary version: rewrite only when the bytes differ (not size-only:
|
||||
// a same-length palette edit must still update).
|
||||
const int w = writeFileIfDifferent(dest.string(), t->data, t->size);
|
||||
if (w >= 0) {
|
||||
if (w == 1) DEBUG_LOGF("[INFO] EmbeddedResources: Extracted theme: %s (%zu bytes)\n", t->filename, t->size);
|
||||
count++; // present and current (caller only checks > 0)
|
||||
} else {
|
||||
DEBUG_LOGF("[ERROR] EmbeddedResources: Failed to write theme: %s\n", dest.string().c_str());
|
||||
}
|
||||
|
||||
@@ -131,6 +131,10 @@ const EmbeddedTheme* getEmbeddedThemes();
|
||||
// Returns number of themes extracted
|
||||
int extractBundledThemes(const std::string& destDir);
|
||||
|
||||
// Write `size` bytes to `path` unless the file already holds exactly those bytes.
|
||||
// Returns 1 = written, 0 = already identical (untouched), -1 = write failed.
|
||||
int writeFileIfDifferent(const std::string& path, const uint8_t* data, size_t size);
|
||||
|
||||
// Update stale bundled theme files in the given directory.
|
||||
// Compares on-disk file size to embedded size; overwrites on mismatch.
|
||||
// Returns number of themes updated.
|
||||
|
||||
Reference in New Issue
Block a user