mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-17 05:52:33 +00:00
ifcviewer: extract pure buffer-based sidecar parse + read-plan helpers
Splits the v13 metadata wire-format knowledge out of the FILE*-bound streaming reader into pure, buffer-based functions so the web byte-range path (#88) can reuse it without loading the whole sidecar into the wasm heap: - parseSidecarHead — validates the 16-byte head, yields num_vertex_bytes - parseSidecarTail — parses meshes/instances/georef/elements/strings from an in-memory tail buffer, bounds-checked - planSidecarReadRanges + SidecarReadPlan — the range-coalescing / scatter planner, promoted out of the anonymous namespace readSidecarMetadataOnly and the range readers now call these; desktop behaviour is unchanged (head + tail are small, the bulk is still skipped via seek). The metadata tail is split from the head around the bulk sections, so a blob-backed loader just slices those two regions and hands the bytes to the same parsers. Closes a coverage gap: StreamingLoader had no unit tests. Adds test_streaming_loader.cpp (7 cases: metadata round-trip, corrupt/truncated rejection, vertex+index range scatter, head validation, tail truncation, read-plan coalescing). 107/107 unit tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -22,9 +22,11 @@
|
||||
|
||||
#include "SidecarCache.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
// Metadata-only sidecar load — the foundation for streaming. Reads the v13
|
||||
@@ -65,6 +67,56 @@ struct StreamingSidecar {
|
||||
// before return — callers re-open for per-chunk reads.
|
||||
std::optional<StreamingSidecar> readSidecarMetadataOnly(const std::string& ifc_path);
|
||||
|
||||
// --- Pure, buffer-based building blocks ------------------------------------
|
||||
//
|
||||
// The metadata lives in two disjoint regions of the file: a small fixed
|
||||
// "head" (12-byte header + the 4-byte vertex-byte count) that precedes the
|
||||
// bulk vertex/index sections, and a "tail" (mesh dict, instance dict, georef,
|
||||
// element table, string table) that follows them. Both desktop (FILE*) and
|
||||
// web (Blob.slice / fetch Range) readers slice those two regions out of the
|
||||
// source and hand the bytes to these parsers, so the wire-format knowledge
|
||||
// lives in exactly one place and is unit-testable without touching a file.
|
||||
|
||||
// Bytes the head spans: SidecarHeader (12) + uint32 num_vertex_bytes (4).
|
||||
inline constexpr std::size_t SIDECAR_HEAD_BYTES = 16;
|
||||
|
||||
// Parse the 16-byte head. Validates magic / version / endian and, on success,
|
||||
// writes the vertex-section byte count (which locates the index-count field at
|
||||
// SIDECAR_HEAD_BYTES + out_num_vertex_bytes). Returns false if `n` is short or
|
||||
// the header is wrong. `data` must point at the start of the file.
|
||||
bool parseSidecarHead(const std::uint8_t* data, std::size_t n,
|
||||
std::uint32_t& out_num_vertex_bytes);
|
||||
|
||||
// Parse the metadata tail (everything after the index section): mesh dict,
|
||||
// instance dict, georef block, element table, string table. `data` points at
|
||||
// the first tail byte; `n` is the tail length (read to EOF). Returns false on
|
||||
// any bounds overrun (truncated buffer), leaving out_meta partially filled.
|
||||
bool parseSidecarTail(const std::uint8_t* data, std::size_t n,
|
||||
SidecarData& out_meta);
|
||||
|
||||
// A coalesced read plan: a single contiguous source read whose bytes are
|
||||
// scattered into the destination at the recorded offsets. Merging adjacent
|
||||
// (or near-adjacent, within max_gap_bytes) ranges into one read amortises seek
|
||||
// cost on disk and request count over the network / Blob boundary.
|
||||
struct SidecarReadPlan {
|
||||
std::uint64_t file_offset; // absolute source offset of this read
|
||||
std::uint64_t read_size; // bytes to read
|
||||
struct Slice {
|
||||
std::uint64_t src_offset; // offset within the read buffer
|
||||
std::uint64_t dst_offset; // offset within the destination buffer
|
||||
std::uint64_t bytes;
|
||||
};
|
||||
std::vector<Slice> slices;
|
||||
};
|
||||
|
||||
// Build read plans for `ranges` (section-relative (offset, size) pairs) that
|
||||
// land in a destination laid out in input order. `section_offset` is added to
|
||||
// turn section-relative offsets into absolute source offsets. Pure — no I/O.
|
||||
std::vector<SidecarReadPlan> planSidecarReadRanges(
|
||||
std::uint64_t section_offset,
|
||||
const std::vector<std::pair<std::uint64_t, std::uint64_t>>& ranges,
|
||||
std::uint64_t max_gap_bytes);
|
||||
|
||||
// Read a byte range from a sidecar's vertex section. `chunk_byte_offset` is
|
||||
// RELATIVE to vertex_section_offset (i.e. 0 = first vertex byte). Returns
|
||||
// false on I/O error or out-of-range request.
|
||||
|
||||
Reference in New Issue
Block a user