ifc_lite_processing/shard_classes.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//! Per-record prepass classification for the sharded scan (split from
6//! `parallel_scan.rs` — the byte-identical shard/stitch protocol lives there;
7//! this module owns the class codes and the classified scan variant the
8//! browser's sharded pre-pass consumes).
9
10use crate::parallel_scan::ShardRecords;
11use ifc_lite_core::EntityScanner;
12
13/// Per-record prepass class emitted by [`scan_shard_classified`].
14///
15/// Only the codes a downstream consumer needs are defined; everything else is
16/// [`PREPASS_CLASS_NONE`]. Classification happens AT SCAN TIME from the same
17/// `type_name` string the serial pre-pass matches on, so a consumer that
18/// filters records by class reproduces the serial pre-pass's span collection
19/// byte-for-byte (same keyword compare, same file order).
20pub const PREPASS_CLASS_NONE: u8 = 0;
21/// `IFCSTYLEDITEM` — the styled-item spans the pre-pass resolver classifies
22/// into orphan (material appearance) vs geometry-attached styles.
23pub const PREPASS_CLASS_STYLED_ITEM: u8 = 4;
24/// `IFCINDEXEDCOLOURMAP` (#663/#858).
25pub const PREPASS_CLASS_INDEXED_COLOUR_MAP: u8 = 5;
26/// `IFCMATERIALDEFINITIONREPRESENTATION` (#407).
27pub const PREPASS_CLASS_MATERIAL_DEF_REPR: u8 = 6;
28/// `IFCRELASSOCIATESMATERIAL` (#407).
29pub const PREPASS_CLASS_REL_ASSOCIATES_MATERIAL: u8 = 7;
30/// `IFCRELVOIDSELEMENT`.
31pub const PREPASS_CLASS_REL_VOIDS: u8 = 8;
32/// `IFCRELFILLSELEMENT`.
33pub const PREPASS_CLASS_REL_FILLS: u8 = 9;
34/// `IFCRELAGGREGATES`.
35pub const PREPASS_CLASS_REL_AGGREGATES: u8 = 10;
36/// `IFCPROJECT`.
37pub const PREPASS_CLASS_PROJECT: u8 = 2;
38/// `IFCSITE` (also a geometry job — the pre-pass buffers it like one).
39pub const PREPASS_CLASS_SITE: u8 = 3;
40/// `IFCMATERIALLAYERSET` / `IFCMATERIALLAYERSETUSAGE` (arms the layer index).
41pub const PREPASS_CLASS_MATERIAL_LAYER_SET: u8 = 13;
42/// FLAG bit: geometry-bearing entity (`has_geometry_by_name`) — a pre-pass
43/// geometry job. Composes with the named codes' nibble range (2..=13) and
44/// with [`PREPASS_CLASS_FLAG_TYPE_CANDIDATE`].
45pub const PREPASS_CLASS_FLAG_GEOMETRY_JOB: u8 = 0x80;
46/// FLAG bit: `IfcTypeProduct` subtype candidate (name ends TYPE/STYLE) for the
47/// #957 orphan type-geometry pass.
48pub const PREPASS_CLASS_FLAG_TYPE_CANDIDATE: u8 = 0x40;
49/// `IFCMAPPEDITEM` (#957/#1623 repmap plans).
50pub const PREPASS_CLASS_MAPPED_ITEM: u8 = 11;
51/// `IFCRELDEFINESBYTYPE` (#957 instantiated-type ids).
52pub const PREPASS_CLASS_REL_DEFINES_BY_TYPE: u8 = 12;
53/// Mask extracting the named-arm code from a class byte (drops the flag bits).
54pub const PREPASS_CLASS_CODE_MASK: u8 = 0x3F;
55
56/// [`scan_shard`] plus a parallel per-record class column (see the
57/// `PREPASS_CLASS_*` codes). Same records, same handoff; the class byte lets
58/// the browser host extract pre-pass span lists (today: styled items) from the
59/// stitched shard columns WITHOUT waiting for the serial pre-pass scan.
60pub fn scan_shard_classified(
61 content: &[u8],
62 range_start: usize,
63 range_end: usize,
64) -> (ShardRecords, Vec<u8>, Option<usize>) {
65 let mut scanner = if range_start == 0 {
66 EntityScanner::new(content)
67 } else {
68 EntityScanner::new_at(content, range_start)
69 };
70 let mut records = Vec::new();
71 let mut classes = Vec::new();
72 let mut handoff = None;
73 while let Some((id, type_name, start, entity_end)) = scanner.next_entity() {
74 if start >= range_end {
75 handoff = Some(start);
76 break;
77 }
78 records.push((id, start, entity_end));
79 classes.push(classify_type_name(type_name));
80 }
81 (records, classes, handoff)
82}
83
84/// Classify a scanned STEP keyword into the prepass class byte: a named-arm
85/// code for the exact keywords the serial pre-pass matches, plus the
86/// geometry-job / type-candidate FLAG bits from the same helpers it calls
87/// (`has_geometry_by_name`, `IfcType::is_subtype_of`). Byte-identical span
88/// collection and job discovery follow from using the identical predicates at
89/// scan time.
90pub fn classify_type_name(type_name: &str) -> u8 {
91 use ifc_lite_core::{has_geometry_by_name, IfcType};
92 let named = match type_name {
93 "IFCPROJECT" => PREPASS_CLASS_PROJECT,
94 "IFCSITE" => return PREPASS_CLASS_SITE, // site is job + site-record; flags implied
95 "IFCSTYLEDITEM" => PREPASS_CLASS_STYLED_ITEM,
96 "IFCINDEXEDCOLOURMAP" => PREPASS_CLASS_INDEXED_COLOUR_MAP,
97 "IFCMATERIALDEFINITIONREPRESENTATION" => PREPASS_CLASS_MATERIAL_DEF_REPR,
98 "IFCRELASSOCIATESMATERIAL" => PREPASS_CLASS_REL_ASSOCIATES_MATERIAL,
99 "IFCRELVOIDSELEMENT" => PREPASS_CLASS_REL_VOIDS,
100 "IFCRELFILLSELEMENT" => PREPASS_CLASS_REL_FILLS,
101 "IFCRELAGGREGATES" => PREPASS_CLASS_REL_AGGREGATES,
102 "IFCMAPPEDITEM" => PREPASS_CLASS_MAPPED_ITEM,
103 "IFCRELDEFINESBYTYPE" => PREPASS_CLASS_REL_DEFINES_BY_TYPE,
104 "IFCMATERIALLAYERSET" | "IFCMATERIALLAYERSETUSAGE" => PREPASS_CLASS_MATERIAL_LAYER_SET,
105 _ => PREPASS_CLASS_NONE,
106 };
107 if named != PREPASS_CLASS_NONE {
108 // The named keywords are mutually exclusive with the flag predicates in
109 // the serial match (its arms return before the `_` arm runs them).
110 return named;
111 }
112 let mut class = PREPASS_CLASS_NONE;
113 if type_name.ends_with("TYPE") || type_name.ends_with("STYLE") {
114 let ty = IfcType::from_str(type_name);
115 if ty.is_subtype_of(IfcType::IfcTypeProduct) {
116 class |= PREPASS_CLASS_FLAG_TYPE_CANDIDATE;
117 }
118 }
119 if has_geometry_by_name(type_name) {
120 class |= PREPASS_CLASS_FLAG_GEOMETRY_JOB;
121 }
122 class
123}