use super::*;
pub(super) type WorkerPointCaches = Vec<std::sync::Mutex<FxHashMap<u32, (f64, f64, f64)>>>;
pub(super) type WorkerPlacementCaches = Vec<std::sync::Mutex<FxHashMap<u32, [f64; 16]>>>;
pub(super) fn new_worker_point_caches() -> WorkerPointCaches {
(0..rayon::current_num_threads().max(1))
.map(|_| std::sync::Mutex::new(FxHashMap::default()))
.collect()
}
pub(super) fn new_worker_placement_caches() -> WorkerPlacementCaches {
(0..rayon::current_num_threads().max(1))
.map(|_| std::sync::Mutex::new(FxHashMap::default()))
.collect()
}
struct WorkerCacheGuard<'c, 's> {
decoder: EntityDecoder<'c>,
slot: &'s mut FxHashMap<u32, (f64, f64, f64)>,
placement_slot: &'s mut FxHashMap<u32, [f64; 16]>,
}
impl Drop for WorkerCacheGuard<'_, '_> {
fn drop(&mut self) {
*self.slot = self.decoder.take_point_cache();
*self.placement_slot = self.decoder.take_placement_transform_cache();
}
}
impl<'c> std::ops::Deref for WorkerCacheGuard<'c, '_> {
type Target = EntityDecoder<'c>;
fn deref(&self) -> &Self::Target {
&self.decoder
}
}
impl std::ops::DerefMut for WorkerCacheGuard<'_, '_> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.decoder
}
}
#[allow(clippy::too_many_arguments)]
pub(super) fn process_entity_job(
job: &EntityJob,
content: &[u8],
entity_index_arc: &Arc<EntityIndex>,
unit_scale: f64,
rtc_offset: (f64, f64, f64),
seed_plane_angle_to_radians: f64,
tessellation_quality: TessellationQuality,
void_index: &FxHashMap<u32, Vec<u32>>,
skipped_entity_ids: &HashSet<u32>,
geometry_style_index: &FxHashMap<u32, GeometryStyleInfo>,
indexed_colour_full: &FxHashMap<u32, crate::style::FullIndexedColourMap>,
element_material_colors: &FxHashMap<u32, Vec<[f32; 4]>>,
texture_index: &FxHashMap<u32, ifc_lite_geometry::ResolvedTextureMap>,
site_local_rotation: Option<&Vec<f64>>,
csg_failure_collector: &std::sync::Mutex<FxHashMap<u32, Vec<ifc_lite_geometry::BoolFailure>>>,
classification_collector: &std::sync::Mutex<ifc_lite_geometry::ClassificationStats>,
host_diag_collector: &std::sync::Mutex<FxHashMap<u32, ifc_lite_geometry::HostOpeningDiagnostic>>,
rect_fast_collector: &std::sync::Mutex<ifc_lite_geometry::RectFastStats>,
backstop_collector: &std::sync::atomic::AtomicU64,
item_dedup_cache: &ifc_lite_geometry::ItemDedupCache,
mapped_item_cache: &ifc_lite_geometry::SharedMappedItemCache,
instancing_plan: Option<&ifc_lite_geometry::MappedInstancePlan>,
indexed_colour_split_ids: Option<&std::sync::Arc<rustc_hash::FxHashSet<u32>>>,
raw_instance_collector: &std::sync::Mutex<Vec<crate::RawInstanceOccurrence>>,
worker_point_cache: &mut FxHashMap<u32, (f64, f64, f64)>,
worker_placement_cache: &mut FxHashMap<u32, [f64; 16]>,
point_cache_hits_collector: &std::sync::atomic::AtomicU64,
point_cache_misses_collector: &std::sync::atomic::AtomicU64,
faceted_brep_ns_collector: &std::sync::atomic::AtomicU64,
) -> Vec<MeshData> {
if skipped_entity_ids.contains(&job.id) {
return Vec::new();
}
let mut local_decoder = EntityDecoder::with_arc_index(content, entity_index_arc.clone());
local_decoder.set_point_cache(std::mem::take(worker_point_cache));
local_decoder.set_placement_transform_cache(std::mem::take(worker_placement_cache));
local_decoder.seed_unit_scales(unit_scale, seed_plane_angle_to_radians);
let mut decoder = WorkerCacheGuard {
decoder: local_decoder,
slot: worker_point_cache,
placement_slot: worker_placement_cache,
};
let entity = match decoder.decode_at(job.start, job.end) {
Ok(entity) => entity,
Err(_) => return Vec::new(),
};
let mut local_router = GeometryRouter::with_scale_and_quality(unit_scale, tessellation_quality);
local_router.set_rtc_offset(rtc_offset);
local_router.enable_content_dedup_shared(item_dedup_cache.clone());
local_router.enable_shared_mapped_item_cache(mapped_item_cache.clone());
if let Some(plan) = instancing_plan {
local_router.enable_output_instancing(plan.clone());
if let Some(ids) = indexed_colour_split_ids {
local_router.enable_indexed_colour_split_guard(ids.clone());
}
}
let local_router = local_router;
let metadata = crate::element::ElementMeshMetadata {
global_id: job.global_id.clone(),
name: job.name.clone(),
presentation_layer: job.presentation_layer.clone(),
space_zone_properties: job.space_zone_properties.clone(),
};
let kind = match job.representation_map_id {
Some(rep_map_id) => crate::element::ElementJobKind::TypeProduct {
rep_maps: vec![(rep_map_id, 1)],
},
None => crate::element::ElementJobKind::Product,
};
let ctx = crate::element::MeshProductionContext {
void_index,
geometry_style_index,
indexed_colour_full,
element_material_colors,
texture_index,
site_local_rotation,
};
#[cfg(all(feature = "observability", not(target_arch = "wasm32")))]
let brep_timer = std::time::Instant::now();
let produced = crate::element::produce_element_meshes(
&crate::element::ElementMeshJob {
id: job.id,
ifc_type: job.ifc_type,
entity: &entity,
kind,
element_color: Some(job.element_color),
metadata: Some(&metadata),
},
&ctx,
&crate::element::MeshProductionOptions::default(),
&mut decoder,
&local_router,
);
let (part_hits, part_misses) = decoder.point_cache_stats();
if part_hits + part_misses > 0 {
use std::sync::atomic::Ordering::Relaxed;
point_cache_hits_collector.fetch_add(part_hits, Relaxed);
point_cache_misses_collector.fetch_add(part_misses, Relaxed);
#[cfg(all(feature = "observability", not(target_arch = "wasm32")))]
{
let elapsed_ns = brep_timer.elapsed().as_nanos() as u64;
faceted_brep_ns_collector.fetch_add(elapsed_ns, Relaxed);
tracing::debug!(
element_id = job.id,
point_cache_hits = part_hits,
point_cache_misses = part_misses,
faceted_brep_us = elapsed_ns / 1000,
"faceted-brep part meshed"
);
}
#[cfg(not(all(feature = "observability", not(target_arch = "wasm32"))))]
faceted_brep_ns_collector.fetch_add(0, Relaxed);
}
if produced.degenerate_triangles_dropped > 0 {
backstop_collector.fetch_add(
produced.degenerate_triangles_dropped,
std::sync::atomic::Ordering::Relaxed,
);
}
if !produced.csg_failures.is_empty() {
if let Ok(mut collector) = csg_failure_collector.lock() {
for (product_id, fails) in produced.csg_failures {
collector.entry(product_id).or_default().extend(fails);
}
}
}
let cls = local_router.take_classification_stats();
if cls.rectangular != 0 || cls.diagonal != 0 || cls.non_rectangular != 0 {
if let Ok(mut acc) = classification_collector.lock() {
acc.rectangular += cls.rectangular;
acc.diagonal += cls.diagonal;
acc.non_rectangular += cls.non_rectangular;
}
}
let host_diags = local_router.take_host_opening_diagnostics();
if !host_diags.is_empty() {
if let Ok(mut acc) = host_diag_collector.lock() {
acc.extend(host_diags);
}
}
let rf = local_router.take_rect_fast_stats();
if rf.fired != 0
|| rf.openings_cut != 0
|| rf.defer_host_not_box != 0
|| rf.defer_not_through != 0
|| rf.defer_off_face != 0
|| rf.defer_near_edge != 0
|| rf.defer_no_openings != 0
{
if let Ok(mut acc) = rect_fast_collector.lock() {
acc.fired += rf.fired;
acc.openings_cut += rf.openings_cut;
acc.defer_host_not_box += rf.defer_host_not_box;
acc.defer_not_through += rf.defer_not_through;
acc.defer_off_face += rf.defer_off_face;
acc.defer_near_edge += rf.defer_near_edge;
acc.defer_no_openings += rf.defer_no_openings;
}
}
if !produced.instance_occurrences.is_empty() {
if let Ok(mut acc) = raw_instance_collector.lock() {
acc.extend(produced.instance_occurrences);
}
}
produced.meshes
}
pub(super) fn build_color_updates_for_jobs(
jobs: &[EntityJob],
geometry_styles: &FxHashMap<u32, GeometryStyleInfo>,
content: &[u8],
entity_index: &Arc<EntityIndex>,
) -> Vec<(u32, [f32; 4])> {
let mut decoder = EntityDecoder::with_arc_index(content, entity_index.clone());
let mut updates: Vec<(u32, [f32; 4])> = Vec::new();
for job in jobs {
if let Some(rep_map_id) = job.representation_map_id {
if let Some(color) = crate::element::resolve_color_for_representation_map(
rep_map_id,
geometry_styles,
&mut decoder,
) {
if color != job.element_color {
updates.push((job.id, color));
}
}
continue;
}
let Ok(entity) = decoder.decode_at(job.start, job.end) else {
continue;
};
let Some(product_definition_shape_id) = entity.get_ref(6) else {
continue;
};
let Some(color) = resolve_element_color_for_product_definition_shape(
product_definition_shape_id,
geometry_styles,
&mut decoder,
) else {
continue;
};
if color != job.element_color {
updates.push((job.id, color));
}
}
updates
}