ifcviewer-web: stream getObjects() per model instead of one scene-sized JSON

getObjects() is on every real host page's path — a click hands back an
object id, and resolving it to a GlobalId/name/type needs the element
tables (the JS layer also builds its GUID index from this call). The
implementation materialised a vector of ElementRef (three fresh
std::strings per element), serialised the entire scene into one JSON
string grown by +=, and UTF8ToString'd the whole thing — several
hundred MB simultaneously alive at ~600k elements. The wasm heap never
returns pages, so that transient became the session's permanent floor.

ViewportCore::visitModelElements hands out one model's elements as
slices into its string table (no per-element copies), and the export
serialises straight from those, one model per batch, reusing one string
whose capacity grows only to the largest model. The JS side accumulates
batches and resolves the same array as before — the page API is
unchanged. Peak is now one model's JSON instead of the scene's.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Dion Moult
2026-08-24 08:51:21 +10:00
parent 091b4d4113
commit 6f24133d35
4 changed files with 101 additions and 24 deletions
+14 -5
View File
@@ -257,13 +257,22 @@
// Completion side of ifcv_request_objects_c: the element tables have all
// landed and the scene's objects are ready as JSON. `token` matches the
// request to its pending Promise.
// token -> {rows, resolve}. The wasm side streams the objects array one
// model per batch (so the whole-scene JSON never exists in one string on
// its heap); Done resolves with the accumulated rows.
const pendingObjects = new Map();
let objectsToken = 0;
Module.__ifcvOnObjects = function (token, json) {
const resolve = pendingObjects.get(token);
if (!resolve) return;
Module.__ifcvOnObjectsBatch = function (token, json) {
const pending = pendingObjects.get(token);
if (!pending) return;
const rows = JSON.parse(json);
for (let i = 0; i < rows.length; ++i) pending.rows.push(rows[i]);
};
Module.__ifcvOnObjectsDone = function (token) {
const pending = pendingObjects.get(token);
if (!pending) return;
pendingObjects.delete(token);
resolve(JSON.parse(json));
pending.resolve(pending.rows);
};
// Some test harnesses / the fullscreen page want the raw module on window.
@@ -442,7 +451,7 @@
getObjects: function () {
const token = ++objectsToken;
return new Promise(function (resolve) {
pendingObjects.set(token, resolve);
pendingObjects.set(token, { rows: [], resolve: resolve });
Module._ifcv_request_objects_c(token);
}).then(function (objects) {
objectIndex = new Map();