use crate::generated::IfcType;
use crate::parser::{report_scan_diagnostics, EntityScanner};
use futures_core::Stream;
use futures_util::stream;
use std::pin::Pin;
#[derive(Debug, Clone)]
pub enum ParseEvent {
Started {
file_size: usize,
timestamp: f64,
},
EntityScanned {
id: u32,
ifc_type: IfcType,
position: usize,
},
GeometryReady {
id: u32,
vertex_count: usize,
triangle_count: usize,
},
Progress {
phase: String,
percent: f32,
entities_processed: usize,
total_entities: usize,
},
Completed {
duration_ms: f64,
entity_count: usize,
triangle_count: usize,
},
Error {
message: String,
position: Option<usize>,
},
}
#[derive(Debug, Clone)]
pub struct StreamConfig {
pub progress_interval: usize,
pub skip_types: Vec<IfcType>,
pub only_types: Option<Vec<IfcType>>,
}
impl Default for StreamConfig {
fn default() -> Self {
Self {
progress_interval: 100,
skip_types: vec![
IfcType::IfcOwnerHistory,
IfcType::IfcPerson,
IfcType::IfcOrganization,
IfcType::IfcApplication,
],
only_types: None,
}
}
}
pub fn parse_stream<T>(
content: &T,
config: StreamConfig,
) -> Pin<Box<dyn Stream<Item = ParseEvent> + '_>>
where
T: AsRef<[u8]> + ?Sized,
{
let content = content.as_ref();
Box::pin(stream::unfold(
ParserState::new(content, config),
|mut state| async move { state.next_event().map(|event| (event, state)) },
))
}
struct ParserState<'a> {
content: &'a [u8],
scanner: EntityScanner<'a>,
config: StreamConfig,
started: bool,
completed: bool,
start_time: f64,
entities_scanned: usize,
total_entities: usize,
triangles_generated: usize,
scan_reported: bool,
}
impl<'a> ParserState<'a> {
fn new(content: &'a [u8], config: StreamConfig) -> Self {
Self {
content,
scanner: EntityScanner::new(content),
config,
started: false,
completed: false,
start_time: 0.0,
entities_scanned: 0,
total_entities: 0,
triangles_generated: 0,
scan_reported: false,
}
}
fn report_scan_once(&mut self) {
if self.scan_reported {
return;
}
self.scan_reported = true;
report_scan_diagnostics(
self.scanner.skipped_oversized_ids(),
self.scanner.malformed_record_start().is_some(),
);
}
fn next_event(&mut self) -> Option<ParseEvent> {
if self.completed {
return None;
}
if !self.started {
self.started = true;
self.start_time = get_timestamp();
return Some(ParseEvent::Started {
file_size: self.content.len(),
timestamp: self.start_time,
});
}
loop {
let Some((id, type_name, start, _end)) = self.scanner.next_entity() else {
self.report_scan_once();
self.completed = true;
let duration_ms = get_timestamp() - self.start_time;
return Some(ParseEvent::Completed {
duration_ms,
entity_count: self.entities_scanned,
triangle_count: self.triangles_generated,
});
};
let ifc_type = IfcType::from_str(type_name);
if self.config.skip_types.contains(&ifc_type) {
continue; }
if let Some(ref only_types) = self.config.only_types {
if !only_types.contains(&ifc_type) {
continue; }
}
self.entities_scanned += 1;
let event = ParseEvent::EntityScanned {
id,
ifc_type,
position: start,
};
if self
.entities_scanned
.is_multiple_of(self.config.progress_interval)
{
return Some(ParseEvent::Progress {
phase: "Scanning entities".to_string(),
percent: 0.0, entities_processed: self.entities_scanned,
total_entities: self.total_entities,
});
}
return Some(event);
}
}
}
impl Drop for ParserState<'_> {
fn drop(&mut self) {
self.report_scan_once();
}
}
fn get_timestamp() -> f64 {
#[cfg(not(target_arch = "wasm32"))]
{
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs_f64()
* 1000.0
}
#[cfg(target_arch = "wasm32")]
{
0.0
}
}
#[cfg(test)]
#[path = "streaming_tests.rs"]
mod streaming_tests;