From f698cc218b287ad6bfc560426fec2468e94e0c2b Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 25 Apr 2026 02:47:34 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Cache=20wasm=20manifest=20i?= =?UTF-8?q?n=20memory?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: himattm <6266621+himattm@users.noreply.github.com> --- .jules/bolt.md | 3 ++ .../halogen/engine/LocalStorageThemeCache.kt | 36 +++++++++---------- 2 files changed, 19 insertions(+), 20 deletions(-) create mode 100644 .jules/bolt.md diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..39600c7 --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2025-02-18 - KMP WasmJs JSON Decoding Overhead +**Learning:** JSON decoding in Kotlin Multiplatform for WasmJs targets carries significant overhead. Repeatedly parsing the `localStorage` manifest JSON string in `LocalStorageThemeCache.kt` on every read/write was causing unnecessary bottlenecks. +**Action:** Use an in-memory cache initialized via `by lazy` to parse the `localStorage` JSON only once upon the first access, and perform subsequent reads/writes directly from/to this in-memory cache to avoid redundant parsing. diff --git a/halogen-engine/src/wasmJsMain/kotlin/halogen/engine/LocalStorageThemeCache.kt b/halogen-engine/src/wasmJsMain/kotlin/halogen/engine/LocalStorageThemeCache.kt index aba1f32..620ef84 100644 --- a/halogen-engine/src/wasmJsMain/kotlin/halogen/engine/LocalStorageThemeCache.kt +++ b/halogen-engine/src/wasmJsMain/kotlin/halogen/engine/LocalStorageThemeCache.kt @@ -51,17 +51,17 @@ public class LocalStorageThemeCache( // ── Manifest helpers ──────────────────────────────────────────────── - private fun readManifest(): MutableSet { - val raw = jsGetItem(manifestKey.toJsString())?.toString() ?: return mutableSetOf() - return try { + private val manifestCache: MutableSet by lazy { + val raw = jsGetItem(manifestKey.toJsString())?.toString() ?: return@lazy mutableSetOf() + try { json.decodeFromString>(raw).toMutableSet() } catch (_: Exception) { mutableSetOf() } } - private fun writeManifest(keys: Set) { - val encoded = json.encodeToString(keys) + private fun writeManifest() { + val encoded = json.encodeToString(manifestCache) jsSetItem(manifestKey.toJsString(), encoded.toJsString()) } @@ -110,9 +110,8 @@ public class LocalStorageThemeCache( sizeBytes = specJson.encodeToByteArray().size, ) writeEntry(storageKey, entry) - val manifest = readManifest() - manifest.add(key) - writeManifest(manifest) + manifestCache.add(key) + writeManifest() _changes.tryEmit(CacheEvent.Inserted(key, source)) } @@ -124,9 +123,8 @@ public class LocalStorageThemeCache( val storageKey = "$prefix$key" val existed = readEntry(storageKey) != null removeEntry(storageKey) - val manifest = readManifest() - manifest.remove(key) - writeManifest(manifest) + manifestCache.remove(key) + writeManifest() if (existed) { _changes.tryEmit(CacheEvent.Evicted(key)) } @@ -134,37 +132,35 @@ public class LocalStorageThemeCache( override suspend fun evict(keys: Set) { val removed = mutableSetOf() - val manifest = readManifest() for (k in keys) { val storageKey = "$prefix$k" if (readEntry(storageKey) != null) { removeEntry(storageKey) - manifest.remove(k) + manifestCache.remove(k) removed.add(k) } } - writeManifest(manifest) + writeManifest() if (removed.isNotEmpty()) { _changes.tryEmit(CacheEvent.EvictedBatch(removed)) } } override suspend fun clear() { - val manifest = readManifest() - for (key in manifest) { + for (key in manifestCache.toList()) { removeEntry("$prefix$key") } + manifestCache.clear() jsRemoveItem(manifestKey.toJsString()) _changes.tryEmit(CacheEvent.Cleared) } - override suspend fun keys(): Set = readManifest() + override suspend fun keys(): Set = manifestCache.toSet() - override suspend fun size(): Int = readManifest().size + override suspend fun size(): Int = manifestCache.size override suspend fun entries(): List { - val manifest = readManifest() - return manifest.mapNotNull { key -> + return manifestCache.toList().mapNotNull { key -> val entry = readEntry("$prefix$key") ?: return@mapNotNull null ThemeCacheEntry( key = key,