ifc_lite_server/services/
parquet_layout.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! The flat-Parquet mesh-table layout a request asked for.
6//!
7//! Its own module because it is the WIRE CONTRACT, not an implementation
8//! detail of the writer: the route reads it off the query, the cache key is
9//! built from it, the schema is chosen by it, and the client sends it. One
10//! definition keeps those four in step.
11
12/// Which flat-Parquet mesh-table layout a request asked for.
13///
14/// OPT-IN, defaulting to [`Self::Flat`], and that default is the whole point.
15/// The flat wire carries no version byte (unlike `/optimized`), so a client
16/// that predates the shared layout cannot fail loud on one — it decodes a
17/// shared blob without error and draws every occurrence of a shared shape at
18/// the template's placement. A server cannot tell those clients apart, so it
19/// must not produce the new layout unless the client says it understands it.
20/// Serving a smaller payload is not worth silently drawing the wrong building.
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Deserialize)]
22pub enum ParquetLayout {
23    /// `-parquet-v8`: one block of vertices per mesh row, no rotation columns.
24    /// Byte-identical to what the route emitted before issue #3888.
25    #[default]
26    #[serde(rename = "flat")]
27    Flat,
28    /// `-parquet-v9`: occurrences of one shape share a block of vertices,
29    /// placed by `world = origin + R * p` via the `rot0..rot8` columns. `v7` =
30    /// `v6`'s schema plus a second, content-hash sharing stage
31    /// (`ShapePlan::shared_shapes`, issue #5130): `v6` only ran the
32    /// rotation-aware collator, so a model whose repeats are bit-identical but
33    /// not `IfcMappedItem`/`IfcRepresentationMap` occurrences (the common case
34    /// on models with no instancing metadata at all) shared nothing and paid
35    /// for nine all-identity `rot*` columns on every row for nothing.
36    #[serde(rename = "shared-shapes")]
37    SharedShapes,
38}
39
40impl ParquetLayout {
41    /// Whether the mesh table carries `rot0..rot8`.
42    ///
43    /// Follows the LAYOUT, not the plan: the streaming route shares nothing
44    /// unless the client also sent [`StreamShapes::CrossBatch`], yet still
45    /// emits the columns as identity when the client asked for this layout,
46    /// so that everything stored under the v7 key is a v7 payload.
47    pub(crate) fn has_rotation(self) -> bool {
48        self == Self::SharedShapes
49    }
50
51    /// The cache-key suffix naming this layout.
52    pub(crate) fn cache_suffix(self) -> &'static str {
53        match self {
54            Self::Flat => "parquet-v8",
55            Self::SharedShapes => "parquet-v9",
56        }
57    }
58}
59
60/// How the streaming route's batches may reference shapes (#5407). Read only by
61/// `POST /api/v1/parse/parquet-stream`, and only meaningful on the
62/// shared-shapes layout.
63///
64/// OPT-IN for the same reason [`ParquetLayout`] is, one level down. A client
65/// that sends `parquet_layout=shared-shapes` today decodes every batch on its
66/// own, against that batch's own vertex table; a mesh row pointing into an
67/// EARLIER batch's vertices would read as out of range at best, or as a
68/// different shape's vertices at worst. So the stream shares across batches
69/// only for a client that says it keeps earlier batches' shapes, and every
70/// batch it sends that way states its `vertex_base` / `index_base` in the
71/// event, which is how the client knows the server honoured the request.
72///
73/// Not part of the cache identity: a cross-batch stream fills the SAME v7
74/// entry a batch-local one does, because both are valid whole-model v7 blobs
75/// (whole-stream offsets, one row group per batch). What differs is how a
76/// cached replay may split one back into batches; see `split_into_batches`.
77#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Deserialize)]
78pub enum StreamShapes {
79    /// Every batch is self-contained: its mesh rows index its own vertex and
80    /// index tables. The only behaviour before #5407.
81    #[default]
82    #[serde(rename = "batch-local")]
83    BatchLocal,
84    /// A batch carries only the shapes no earlier batch emitted, and its mesh
85    /// rows index the whole stream's vertex/index tables.
86    #[serde(rename = "cross-batch")]
87    CrossBatch,
88}