Serve the unchanging files from one table - #71
Merged
Conversation
The five files a clock hands out as they are - the two pages, the manifest and the two icons - had a handler each, in both backends. Ten functions that differed only in a blob, a content type, and whether a Content-Encoding header went out with it. That is data, so it is a table now: WebFrontend::AssetType, walked by each backend to register one route per entry. The bytes moved with it. WebPage.h is generated with internal linkage, so it is included by WebFrontend.cpp and by nothing else - a second includer would put a second copy of both pages in flash. Neither WebInterface.cpp includes it any more. Where the two servers differ is how a route carries which entry it answers for: the IDF gives a handler a user context per route, so the ESP32 registers an array of httpd_uri_t pointing into the table, while ESPAsyncWebServer takes a std::function and the RP2350 captures the entry instead. Both loops end at WebFrontend::MaxAssets, which a static_assert in the table's own source holds against the real count - adding a file without raising it fails the build rather than registering the first five of six. The ESP32's host test had to learn that a route is a handler *and* a context: with one shared asset handler, capturing the function pointer alone made every asset answer as whichever registered last. Measured, and the reason to measure was the duplicated-blob risk above: flash went *down* on both boards - 1 100 421 to 1 100 233 on the ESP32, 501 332 to 500 924 on the Pico - so nothing is stored twice and the table costs less than the handlers did. The ESP32 gains 120 bytes of RAM for the static route array. The AVR-Dx image is byte-identical at 48078/1700, all four targets build warning-free, both host suites pass, and the documented sizes still check out. The panel was driven in a browser again: 19 of 111 cells lit, "ES IST VIERTEL VOR EINS", brightness read back over the socket. Worth noting that this does not exercise the new table - serve.js answers the asset routes from disk itself - so what covers it is web_test.cpp on both backends. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stage C of lifting the web front end into
firmware/, cut deliberately narrow: the asset table, not the dispatch.What moved
The five files a clock hands out unchanged — the two pages, the manifest, the two icons — had a handler each, in both backends. Ten functions differing only in a blob, a content type, and whether a
Content-Encodingheader went with it. That is data, so it isWebFrontend::AssetTypenow, walked by each backend to register one route per entry.The bytes moved with it, and that part mattered:
WebPage.his generated with internal linkage, so a second includer means a second copy of both pages in flash. It is included byWebFrontend.cppand nothing else; neitherWebInterface.cppincludes it any more.Where the backends genuinely differ, and how each solves it
How a route carries which entry it answers for:
user_ctx, sobegin()fills a statichttpd_uri_t[]pointing into the table.ESPAsyncWebServer::on()takes astd::function, so the entry is captured instead.Both loops are bounded by
WebFrontend::MaxAssets, which astatic_assertbeside the table holds against the real count — adding a file without raising it fails the build rather than silently registering the first five of six.What I deliberately did not do
Route dispatch and authorisation stay per backend. Their mechanisms genuinely differ (a C table of
httpd_uri_tagainst.on()callbacks; a header decoded by hand against a library comparison), and moving them would have meant restructuring both 500-lineweb_test.cppfor little in return. The asset table is the part the simulator needs in stage D, and it is here.The duplicated-blob risk, measured
This is the check the change turns on, since moving a header with internal linkage is exactly how you accidentally ship two copies of a 70 KB page:
Flash went down on both. Nothing is stored twice, and the table costs less than the ten handlers did. The ESP32 gains 120 bytes of RAM for the static route array — five
httpd_uri_t.Verified
documented-sizes.pypasses on all three targets (the ESP32's RAM moved 15.9 → 16.0 %, inside the point of slack the check allows).One honest note on that last check: it does not exercise the new table.
serve.jsanswers the asset routes from disk itself, so what covershandleAssetisweb_test.cppon both backends — which does, and passes. The browser run covers the socket and JSON path.A second honest note: the first browser run after starting the server came back with an empty plate, and two immediate re-runs were clean. The first page load is slower than the script's 3 s wait; the server log showed the socket connected and a frame delivered throughout. A flake in the check, not in the code.
Test change worth a look
The ESP32's stub had to learn that a route is a handler and a context. With one shared asset handler, capturing the function pointer alone made every asset answer as whichever registered last — the tests would have passed while testing one file five times.
🤖 Generated with Claude Code