[experiment] perf: pack icons into one flash blob with table-driven lookup (-4KB flash) - #154
Conversation
Not quite sure what you mean here; the literals are included even if not referenced? |
There was a problem hiding this comment.
🔵 Needs a closer look
It changes core icon lookup/storage behavior and introduces generated flash-packed assets with lazy caching, which should be validated via full build + on-device/sim UI smoke testing.
Pull request overview
This PR refactors MicroCode’s icon storage/lookup to reduce flash usage by replacing many per-icon bitmap constants and a large if/else dispatch chain with a generated, table-driven lookup over a single packed flash blob (icon-table.g.ts). This fits into the repo’s asset pipeline by moving most icon literals out of assets.ts and into generated data while keeping icons.get() as the stable public entry point.
Changes:
- Add generator + source-of-truth mapping files (
scripts/genicontable.js,scripts/icon-map.json) to produceicon-table.g.tswith packed icon data and lookup tables. - Update
microcode.icons.get()to resolve numeric TIDs via a 256-entry table and string icon names via a compact name table, materializingBitmaps lazily and caching them. - Register the generated file in
pxt.jsonso it is compiled into the MakeCode build.
File summaries
| File | Description |
|---|---|
| scripts/icon-map.json | Defines tid -> icon symbol and name -> icon symbol mappings used by the generator. |
| scripts/icon-blob-syms.json | Generated registry/order of icon symbols used to build index-based lookup. |
| scripts/genicontable.js | Node generator that dedupes/encodes bmp literals, packs the blob, and emits icon-table.g.ts. |
| pxt.json | Adds icon-table.g.ts to the compiled file list. |
| icon-table.g.ts | Generated packed icon blob + offset tables + lookup helpers (iconByIndex, name/tid tables). |
| assets.ts | Replaces the large icon if/else chain with table-driven lookup and keeps runtime-drawn operator icons. |
Review details
- Files reviewed: 6/7 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
I confirmed that most of the sample program icons in fact appear in the hex. The |
Pack icons into one flash blob with table-driven lookup (-4KB flash)
What this change is
Icon storage and lookup are restructured. Before: ~140 icons each lived in their own
export const x = bmp...in assets.ts, andicons.getresolved ids through a ~150-branch if/else chain. After: all mapped icons' pixel data is packed into a single flash buffer (ICON_BLOB) in a new generated file,icon-table.g.ts; lookup is a flat 256-bytetid -> icon indextable plus a small name table; andBitmapobjects are materialized lazily from blob slices and cached. The mapping and the pixel data live inscripts/icon-map.jsonandscripts/icon-literals.json, andscripts/genicontable.jsregenerates the whole thing.Result: 479,676 -> 475,700 bytes (-3,976) on the hex image.
Where the savings come from
Not from the images (they're not RLE'd yet) -- flash holds the same bytes of bitmap data before and after. The savings are deleted per-icon code:
icons.getif/else compare chain<main>Each
if (name == Tid.X) return icondb.ycompiled to ~16-20 bytes; each icon const also cost ~17 bytes of startup code and a 9-byte buffer object header. The mapping now costs ~3 bytes of data per icon instead of ~35 bytes of code. (Interesting finding: A first attempt that kept the consts and added a lookup array came out 1.5KB LARGER: pxt constructs array literals element by element at runtime, so code references are the expensive thing, not data.)Editing or adding an icon
Mapping lives in
scripts/icon-map.json; pixels live inscripts/icon-literals.json. A bmp literal still present in assets.ts wins over the store. To add/edit an icon:img\...output."<tid>": "your_icon"intidMap, or an entry innameMapfor string-keyed icons.node scripts/genicontable.jsand rebuild.Tradeoffs
iconByIndex.Where RLE would plug in
Every stored icon now flows through one line in
iconByIndex:To add compression: (1) in the generator, RLE-encode each icon's payload before packing, keeping the 8-byte header readable; (2) in
iconByIndex, decode into a RAM buffer and pass that toofBuffer-- RAM cost is unchanged, since the slice already copies today. Measured on this icon set, nibble RLE (4-bit color + 4-bit run length per byte) shrinks the pixel data ~47%, i.e. another ~6.8KB, for a one-time ~150-byte decoder. Encoding choice matters: a naive 2-byte-per-run RLE saves almost nothing on these small, detailed icons.Other potential size wins:
extraSamplesbody: unreachable on hardware, yet ~5.5KB of every hex.wordLogo/editorBackgroundimages.