mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-31 08:56:34 +00:00
ifcviewer-web: mint session model ids when a load is requested
A federated pick could be attributed to the wrong file. The model slot a host sees — ElementRef::model_index, modelProgress's index — is a rank in session_model_id order, and on web that id was minted at the END of the sidecar read chain, after three network round trips. So the ranking was the order the models' reads happened to finish in, not the order the host added them. With ~40 similarly-sized models over HTTP, adjacent models swapped and a click reported its neighbour's file; the host page then asked for a GUID the file does not contain. Mint the id at the top of loadSidecarMetadataWeb instead, which runs synchronously from load_sidecar_from_source_c and therefore in the order the host asked for its models. A load that fails partway just abandons its id, and the ranks compact over the surviving models as before. Positions are still positions, though: if one model fails to load, every later index shifts down one and a host mapping index into its own list silently drifts again. So also carry the source id — the handle the host minted itself when it registered the file — through ElementRef into the pick payload and getObjects rows, and document it as the way to attribute an object to a file. ModelGpuData::web_source_id defaults to -1 now, since 0 is a real source id and cannot double as "none". The test server grows a ?delay=<ms> knob so a test can force the losing interleaving: georef-a is added first and served slowly, and its objects must still come back as model 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3898,10 +3898,24 @@ void ViewportCore::loadSidecarMetadataWeb(int source_id, std::string source_labe
|
||||
return;
|
||||
}
|
||||
|
||||
// Mint the session model id HERE, synchronously, rather than at the end of
|
||||
// the read chain below. Session ids are what orders the scene's models —
|
||||
// modelIdsInLoadOrder sorts by them, and every per-model slot a host sees
|
||||
// (modelProgress's index, ElementRef::model_index) is a rank in that order.
|
||||
// Minting on completion made that rank the order the models' network reads
|
||||
// happened to finish in, so with several federated models in flight the
|
||||
// slots came out shuffled against the order the host added them and a pick
|
||||
// was attributed to the wrong file. Requesting order is the order the host
|
||||
// asked for, which is the order it can reason about. A load that fails
|
||||
// partway simply abandons its id — the ranks compact over whatever models
|
||||
// made it into the scene, exactly as before.
|
||||
const std::uint32_t session_model_id = next_session_model_id_++;
|
||||
|
||||
// Head (v16): [header 12][geom_bytes 8]. The two compressed metadata blocks
|
||||
// follow the compressed geometry at SIDECAR_HEAD_BYTES + geom_bytes.
|
||||
webReadRangesAsync(source_id, 0, {{0, SIDECAR_HEAD_BYTES}},
|
||||
[this, fsize, source_id, source_label, on_loaded = std::move(on_loaded)]
|
||||
[this, fsize, source_id, source_label, session_model_id,
|
||||
on_loaded = std::move(on_loaded)]
|
||||
(bool ok, std::vector<std::uint8_t>&& head) mutable {
|
||||
std::uint64_t geom_bytes = 0;
|
||||
if (!ok || !parseSidecarHead(head.data(), head.size(), geom_bytes)) {
|
||||
@@ -3915,7 +3929,7 @@ void ViewportCore::loadSidecarMetadataWeb(int source_id, std::string source_labe
|
||||
}
|
||||
// Geometry metadata block on disk: [comp u64][raw u64][zstd frame].
|
||||
webReadRangesAsync(source_id, 0, {{meta_off, 16}},
|
||||
[this, fsize, meta_off, source_id, source_label,
|
||||
[this, fsize, meta_off, source_id, source_label, session_model_id,
|
||||
on_loaded = std::move(on_loaded)]
|
||||
(bool ok2, std::vector<std::uint8_t>&& h) {
|
||||
if (!ok2 || h.size() < 16) {
|
||||
@@ -3934,7 +3948,7 @@ void ViewportCore::loadSidecarMetadataWeb(int source_id, std::string source_labe
|
||||
{{geometry_metadata_off, geometry_metadata_comp}},
|
||||
[this, geometry_metadata_off, geometry_metadata_comp,
|
||||
geometry_metadata_raw, source_id, source_label,
|
||||
on_loaded = std::move(on_loaded)]
|
||||
session_model_id, on_loaded = std::move(on_loaded)]
|
||||
(bool ok3, std::vector<std::uint8_t>&& cz) {
|
||||
if (!ok3) {
|
||||
Log::warn() << "loadSidecarMetadataWeb: geometry metadata read failed";
|
||||
@@ -3971,7 +3985,7 @@ void ViewportCore::loadSidecarMetadataWeb(int source_id, std::string source_labe
|
||||
geometry_metadata_off + geometry_metadata_comp;
|
||||
webReadRangesAsync(source_id, 0, {{element_metadata_hdr_off, 16}},
|
||||
[this, sc = std::move(sc), element_metadata_hdr_off,
|
||||
source_id, source_label,
|
||||
source_id, source_label, session_model_id,
|
||||
on_loaded = std::move(on_loaded)]
|
||||
(bool ok4, std::vector<std::uint8_t>&& dh) mutable {
|
||||
if (ok4 && dh.size() >= 16) {
|
||||
@@ -3989,7 +4003,6 @@ void ViewportCore::loadSidecarMetadataWeb(int source_id, std::string source_labe
|
||||
|
||||
const std::size_t n_meshes = sc.meta.meshes.size();
|
||||
const std::size_t n_instances = sc.meta.instances.size();
|
||||
const std::uint32_t session_model_id = next_session_model_id_++;
|
||||
applyCachedModel(session_model_id, std::move(sc));
|
||||
// Mark web-streamed + set the source IMMEDIATELY — the
|
||||
// model now has non-resident chunks and the RAF loop's
|
||||
@@ -4091,11 +4104,14 @@ void ViewportCore::logSelectedObjectGuidWeb(std::uint32_t object_id) {
|
||||
}
|
||||
Log::info() << "pick: object " << object_id << " GUID " << e.guid;
|
||||
// Surface the selection to JS so host pages can react (e.g. show the
|
||||
// GUID + model). Fires Module.__ifcvOnSelect(object_id, guid, modelIndex);
|
||||
// model_index is the load-order slot, matching the JS model list.
|
||||
// GUID + model). Fires
|
||||
// Module.__ifcvOnSelect(object_id, guid, modelIndex, sourceId).
|
||||
// modelIndex is the load-order slot; sourceId is the byte-source the
|
||||
// host added the model from, which is the one that cannot shift.
|
||||
EM_ASM({
|
||||
if (Module.__ifcvOnSelect) Module.__ifcvOnSelect($0, UTF8ToString($1), $2);
|
||||
}, object_id, e.guid.c_str(), e.model_index);
|
||||
if (Module.__ifcvOnSelect)
|
||||
Module.__ifcvOnSelect($0, UTF8ToString($1), $2, $3);
|
||||
}, object_id, e.guid.c_str(), e.model_index, e.source_id);
|
||||
});
|
||||
}
|
||||
#endif // __EMSCRIPTEN__
|
||||
@@ -4157,6 +4173,7 @@ ViewportCore::ElementRef makeElementRef(const ModelGpuData& m, int model_index,
|
||||
ViewportCore::ElementRef ref;
|
||||
ref.object_id = e.object_id;
|
||||
ref.model_index = model_index;
|
||||
ref.source_id = m.web_source_id;
|
||||
ref.guid = str(e.guid_offset, e.guid_length);
|
||||
ref.name = str(e.name_offset, e.name_length);
|
||||
ref.type = str(e.type_offset, e.type_length);
|
||||
|
||||
Reference in New Issue
Block a user