ifcviewer-web: stream remote sidecars over HTTP Range (?model=URL)

Adds a network byte-source alongside the local Blob one. The async-chunk
infra is source-agnostic — only the two JS primitives knew it was a Blob —
so this generalises them and reuses everything else:

  - ifcvReadRangeInto: local → Blob.slice; remote → fetch() with a Range
    header (206). If a server ignores Range and returns 200, the requested
    window is sliced out so it still works (without the bandwidth saving).
  - ifcvFileSize: Blob size, or the URL's total length resolved up front.
  - ifcvBeginUrlSource: resolves total size (HEAD Content-Length, else a
    0-0 ranged GET's Content-Range) then fires _ifcv_source_ready.
  - The metadata bootstrap is extracted into a source-agnostic
    loadSidecarMetadataWeb(label); loadSidecarFromBlobWeb / FromUrlWeb are
    thin entries. streaming_from_blob → streaming_from_web (now covers both).

main_web exports load_sidecar_from_url_c(url); shell.html reads a
?model=URL query param and ccalls it once the app is live (same-origin
needs no CORS; cross-origin hosts must send CORS + Accept-Ranges).

Test: serve.mjs now answers HEAD + Range (206) and falls back to the
ifcviewer-web source dir for sample.ifcview (embedded in the wasm, not in
build-web). New smoke case loads ?model=/sample.ifcview and asserts it
renders via the Range path. 6/6 web smoke + 107/107 unit pass; desktop
unaffected (web-guarded; only the shared field rename touches it).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Dion Moult
2026-06-30 12:44:37 +10:00
parent bc31e91e35
commit 23dc5dac48
8 changed files with 260 additions and 66 deletions
+6 -5
View File
@@ -277,11 +277,12 @@ struct ModelGpuData {
std::string streaming_file_path;
uint64_t streaming_vertex_section_offset = 0;
uint64_t streaming_index_section_offset = 0;
// Web only: chunk byte ranges come from the JS-registered File via
// Blob.slice (async), not from a synchronous fopen on streaming_file_path.
// Set by loadSidecarFromBlobWeb so driveStreamingLoads routes this model
// through the async blob path instead of the MEMFS sync read.
bool streaming_from_blob = false;
// Web only: chunk byte ranges come from the JS-side source — a picked File
// (Blob.slice) or a remote URL (HTTP Range) — read asynchronously, not via
// a synchronous fopen on streaming_file_path. Set by loadSidecarMetadataWeb
// so driveStreamingLoads routes this model through the async web path
// instead of the MEMFS sync read.
bool streaming_from_web = false;
// For each mesh in meshes[], the chunk it lives in plus the chunk-local
// offsets into that chunk's vertex_storage and index_buffer. Populated