mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-12 02:23:34 +00:00
ifcviewer: v15 — defer property metadata off the first-paint path
First-paint over a network is metadata-bound: the whole post-index metadata (~10 MB on a 118 MB model) had to download before any geometry. But ~25% of it — elements + string_table, the IFC element tree (names/GUIDs/hierarchy) — is used only for UI/picking, never for rendering (ViewportCore never touches it). v15 splits the post-index metadata into a render-CRITICAL block (meshes, instances, georef, chunk TOC) preceded by its byte length, then a DEFERRED block (elements + string_table). The web loader reads only the critical block before painting; the deferred block sits at a known, self-describing offset ([critical end, EOF)) and is fetched on demand. Desktop reads both (local). Web on-demand path is wired and complete (not yet called — no UI consumer): loadDeferredMetadataWeb(model_id) range-fetches + parses the deferred block into ModelGpuData.elements/string_table, at most once; the first consumer will be "show the selected object's name" on pick. No background prefetch — view-only sessions never download the property data (saves 2.64 MB on this model). parseSidecarTail split into parseSidecarCritical + parseSidecarDeferred (pure, unit-tested); StreamingSidecar gains the critical-block locator. Measured (118 MB model): critical metadata 10.35 -> 7.71 MB, deferred 2.64 MB off the path; first paint 10.5 -> 9.6 s @ 24 Mbps. (Instances still dominate the critical block — the next metadata lever.) Format -> v15, no back-compat; regenerate sidecars. 111/111 unit + 6/6 web smoke pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -3491,43 +3491,66 @@ void ViewportCore::loadSidecarMetadataWeb(std::string source_label) {
|
||||
}
|
||||
std::uint32_t num_indices = 0;
|
||||
std::memcpy(&num_indices, cnt.data(), 4);
|
||||
const std::uint64_t isec = idx_count_off + 4;
|
||||
const std::uint64_t tail_off = isec + std::uint64_t(num_indices) * 4u;
|
||||
if (double(tail_off) > fsize) {
|
||||
Log::warn() << "loadSidecarMetadataWeb: tail offset past EOF";
|
||||
const std::uint64_t isec = idx_count_off + 4;
|
||||
const std::uint64_t crit_size_off = isec + std::uint64_t(num_indices) * 4u;
|
||||
if (double(crit_size_off + 8) > fsize) {
|
||||
Log::warn() << "loadSidecarMetadataWeb: critical size past EOF";
|
||||
return;
|
||||
}
|
||||
const std::uint64_t tail_len = std::uint64_t(fsize) - tail_off;
|
||||
|
||||
webReadRangesAsync(0, {{tail_off, tail_len}},
|
||||
[this, vsec, nvb, isec, num_indices, source_label]
|
||||
(bool ok3, std::vector<std::uint8_t>&& tail) {
|
||||
if (!ok3) {
|
||||
Log::warn() << "loadSidecarMetadataWeb: tail read failed";
|
||||
// v15: read the 8-byte critical-block length, then the
|
||||
// render-critical metadata only. The deferred block
|
||||
// (elements/strings) is fetched on demand later — first
|
||||
// paint no longer waits on the property tree.
|
||||
webReadRangesAsync(0, {{crit_size_off, 8}},
|
||||
[this, vsec, nvb, isec, num_indices, source_label, crit_size_off, fsize]
|
||||
(bool okc, std::vector<std::uint8_t>&& cb) {
|
||||
if (!okc || cb.size() < 8) {
|
||||
Log::warn() << "loadSidecarMetadataWeb: short critical size";
|
||||
return;
|
||||
}
|
||||
StreamingSidecar sc;
|
||||
sc.file_path = source_label;
|
||||
sc.vertex_section_offset = vsec;
|
||||
sc.vertex_total_bytes = nvb;
|
||||
sc.index_section_offset = isec;
|
||||
sc.index_total_count = num_indices;
|
||||
if (!parseSidecarTail(tail.data(), tail.size(), sc.meta)) {
|
||||
Log::warn() << "loadSidecarMetadataWeb: bad metadata tail";
|
||||
std::uint64_t crit_bytes = 0;
|
||||
std::memcpy(&crit_bytes, cb.data(), 8);
|
||||
const std::uint64_t crit_off = crit_size_off + 8;
|
||||
if (double(crit_off + crit_bytes) > fsize) {
|
||||
Log::warn() << "loadSidecarMetadataWeb: critical block past EOF";
|
||||
return;
|
||||
}
|
||||
const std::size_t n_meshes = sc.meta.meshes.size();
|
||||
const std::size_t n_instances = sc.meta.instances.size();
|
||||
const std::uint32_t mid = next_model_id_++;
|
||||
applyCachedModel(mid, std::move(sc));
|
||||
auto mit = models_gpu_.find(mid);
|
||||
if (mit != models_gpu_.end())
|
||||
mit->second.streaming_from_web = true;
|
||||
viewAll();
|
||||
host_->requestFrame();
|
||||
Log::info() << "ifcviewer-web: loaded sidecar (" << source_label
|
||||
<< ", id " << mid << ", " << n_meshes << " meshes, "
|
||||
<< n_instances << " instances)";
|
||||
webReadRangesAsync(0, {{crit_off, crit_bytes}},
|
||||
[this, vsec, nvb, isec, num_indices, source_label,
|
||||
crit_off, crit_bytes, fsize]
|
||||
(bool ok3, std::vector<std::uint8_t>&& crit) {
|
||||
if (!ok3) {
|
||||
Log::warn() << "loadSidecarMetadataWeb: critical read failed";
|
||||
return;
|
||||
}
|
||||
StreamingSidecar sc;
|
||||
sc.file_path = source_label;
|
||||
sc.vertex_section_offset = vsec;
|
||||
sc.vertex_total_bytes = nvb;
|
||||
sc.index_section_offset = isec;
|
||||
sc.index_total_count = num_indices;
|
||||
if (!parseSidecarCritical(crit.data(), crit.size(), sc.meta)) {
|
||||
Log::warn() << "loadSidecarMetadataWeb: bad critical metadata";
|
||||
return;
|
||||
}
|
||||
const std::size_t n_meshes = sc.meta.meshes.size();
|
||||
const std::size_t n_instances = sc.meta.instances.size();
|
||||
const std::uint32_t mid = next_model_id_++;
|
||||
applyCachedModel(mid, std::move(sc));
|
||||
auto mit = models_gpu_.find(mid);
|
||||
if (mit != models_gpu_.end()) {
|
||||
mit->second.streaming_from_web = true;
|
||||
// Deferred block: [end of critical, EOF).
|
||||
mit->second.deferred_meta_offset = crit_off + crit_bytes;
|
||||
mit->second.deferred_meta_bytes =
|
||||
std::uint64_t(fsize) - (crit_off + crit_bytes);
|
||||
}
|
||||
viewAll();
|
||||
host_->requestFrame();
|
||||
Log::info() << "ifcviewer-web: loaded sidecar (" << source_label
|
||||
<< ", id " << mid << ", " << n_meshes << " meshes, "
|
||||
<< n_instances << " instances)";
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -3549,6 +3572,40 @@ void ViewportCore::loadSidecarFromUrlWeb(std::string url) {
|
||||
g_url_ready_core = this;
|
||||
ifcvBeginUrlSource(url.c_str());
|
||||
}
|
||||
|
||||
void ViewportCore::loadDeferredMetadataWeb(std::uint32_t model_id,
|
||||
std::function<void(bool)> done) {
|
||||
// On-demand fetch of the v15 deferred block (element tree + string table)
|
||||
// for a web-streamed model — the property data a UI needs (tree, selected-
|
||||
// object name, search) but rendering doesn't. Fetches at most once. Valid
|
||||
// only for the currently-loaded model, since the JS byte-source
|
||||
// (Module.__ifcvUrl/File) tracks the latest load.
|
||||
auto it = models_gpu_.find(model_id);
|
||||
if (it == models_gpu_.end()) { if (done) done(false); return; }
|
||||
ModelGpuData& m = it->second;
|
||||
if (m.deferred_meta_loaded || m.deferred_meta_bytes == 0) {
|
||||
m.deferred_meta_loaded = true;
|
||||
if (done) done(true);
|
||||
return;
|
||||
}
|
||||
webReadRangesAsync(0, {{m.deferred_meta_offset, m.deferred_meta_bytes}},
|
||||
[this, model_id, done](bool ok, std::vector<std::uint8_t>&& buf) {
|
||||
auto mit = models_gpu_.find(model_id);
|
||||
if (mit == models_gpu_.end()) { if (done) done(false); return; }
|
||||
SidecarData tmp;
|
||||
if (!ok || !parseSidecarDeferred(buf.data(), buf.size(), tmp)) {
|
||||
Log::warn() << "loadDeferredMetadataWeb: read/parse failed";
|
||||
if (done) done(false);
|
||||
return;
|
||||
}
|
||||
mit->second.elements = std::move(tmp.elements);
|
||||
mit->second.string_table = std::move(tmp.string_table);
|
||||
mit->second.deferred_meta_loaded = true;
|
||||
Log::info() << "ifcviewer-web: loaded deferred metadata ("
|
||||
<< mit->second.elements.size() << " elements)";
|
||||
if (done) done(true);
|
||||
});
|
||||
}
|
||||
#endif // __EMSCRIPTEN__
|
||||
|
||||
void ViewportCore::finalizeModel(std::uint32_t model_id) {
|
||||
|
||||
Reference in New Issue
Block a user