use super::IfcAPI;
use ifc_lite_core::{EntityScanner, ParseEvent};
use js_sys::{Function, Promise};
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::spawn_local;
#[wasm_bindgen]
impl IfcAPI {
#[wasm_bindgen(js_name = parseStreaming)]
pub fn parse_streaming(&self, content: String, callback: Function) -> Promise {
use futures_util::StreamExt;
use ifc_lite_core::StreamConfig;
let mut content = Some(content);
let mut callback = Some(callback);
let promise = Promise::new(&mut |resolve, reject| {
let content = content.take().expect("content already taken");
let callback = callback.take().expect("callback already taken");
let reject = reject.clone();
spawn_local(async move {
let config = StreamConfig::default();
let mut stream = ifc_lite_core::parse_stream(&content, config);
while let Some(event) = stream.next().await {
let event_obj = parse_event_to_js(&event);
if let Err(e) = callback.call1(&JsValue::NULL, &event_obj) {
let _ = reject.call1(&JsValue::NULL, &e);
return;
}
if matches!(event, ParseEvent::Completed { .. }) {
if let Err(e) = resolve.call0(&JsValue::NULL) {
let _ = reject.call1(&JsValue::NULL, &e);
}
return;
}
}
if let Err(e) = resolve.call0(&JsValue::NULL) {
let _ = reject.call1(&JsValue::NULL, &e);
}
});
});
promise
}
#[wasm_bindgen]
pub fn parse(&self, content: String) -> Promise {
let mut content = Some(content);
let promise = Promise::new(&mut |resolve, reject| {
let content = content.take().expect("content already taken");
let reject = reject.clone();
spawn_local(async move {
let mut scanner = EntityScanner::new(&content);
let counts = scanner.count_by_type();
let total_entities: usize = counts.values().sum();
let result = js_sys::Object::new();
super::set_js_prop(&result, "entityCount", &JsValue::from_f64(total_entities as f64));
super::set_js_prop(&result, "entityTypes", &super::counts_to_js(&counts));
if let Err(e) = resolve.call1(&JsValue::NULL, &result) {
let _ = reject.call1(&JsValue::NULL, &e);
}
});
});
promise
}
#[wasm_bindgen(js_name = scanEntitiesFast)]
pub fn scan_entities_fast(&self, content: &str) -> JsValue {
Self::scan_entities_fast_inner(content)
}
#[wasm_bindgen(js_name = scanEntitiesFastBytes)]
pub fn scan_entities_fast_bytes(&self, data: &[u8]) -> JsValue {
let content = unsafe { std::str::from_utf8_unchecked(data) };
Self::scan_entities_fast_inner(content)
}
fn scan_entities_fast_inner(content: &str) -> JsValue {
use serde::{Deserialize, Serialize};
use serde_wasm_bindgen::to_value;
#[derive(Serialize, Deserialize)]
struct EntityRefJs {
express_id: u32,
entity_type: String,
byte_offset: usize,
byte_length: usize,
line_number: usize,
}
let mut scanner = EntityScanner::new(content);
let mut refs = Vec::new();
let bytes = content.as_bytes();
let mut last_position = 0;
let mut line_count = 1;
let mut type_cache: rustc_hash::FxHashMap<&str, String> =
rustc_hash::FxHashMap::default();
while let Some((id, type_name, start, end)) = scanner.next_entity() {
if start > last_position {
line_count += bytes[last_position..start]
.iter()
.filter(|&&b| b == b'\n')
.count();
}
let entity_type = type_cache
.entry(type_name)
.or_insert_with(|| type_name.to_string())
.clone();
refs.push(EntityRefJs {
express_id: id,
entity_type,
byte_offset: start,
byte_length: end - start,
line_number: line_count,
});
last_position = end;
}
to_value(&refs).unwrap_or_else(|_| js_sys::Array::new().into())
}
#[wasm_bindgen(js_name = scanGeometryEntitiesFast)]
pub fn scan_geometry_entities_fast(&self, content: &str) -> JsValue {
use serde::{Deserialize, Serialize};
use serde_wasm_bindgen::to_value;
#[derive(Serialize, Deserialize)]
struct GeometryEntityRefJs {
express_id: u32,
entity_type: String,
byte_offset: usize,
byte_length: usize,
}
let mut scanner = EntityScanner::new(content);
let mut refs = Vec::new();
while let Some((id, type_name, start, end)) = scanner.next_entity() {
if ifc_lite_core::has_geometry_by_name(type_name) {
refs.push(GeometryEntityRefJs {
express_id: id,
entity_type: type_name.to_string(),
byte_offset: start,
byte_length: end - start,
});
}
}
to_value(&refs).unwrap_or_else(|_| js_sys::Array::new().into())
}
}
fn parse_event_to_js(event: &ParseEvent) -> JsValue {
let obj = js_sys::Object::new();
match event {
ParseEvent::Started {
file_size,
timestamp,
} => {
super::set_js_prop(&obj, "type", &"started".into());
super::set_js_prop(&obj, "fileSize", &(*file_size as f64).into());
super::set_js_prop(&obj, "timestamp", &(*timestamp).into());
}
ParseEvent::EntityScanned {
id,
ifc_type,
position,
} => {
super::set_js_prop(&obj, "type", &"entityScanned".into());
super::set_js_prop(&obj, "id", &(*id as f64).into());
super::set_js_prop(&obj, "ifcType", &ifc_type.as_str().into());
super::set_js_prop(&obj, "position", &(*position as f64).into());
}
ParseEvent::GeometryReady {
id,
vertex_count,
triangle_count,
} => {
super::set_js_prop(&obj, "type", &"geometryReady".into());
super::set_js_prop(&obj, "id", &(*id as f64).into());
super::set_js_prop(&obj, "vertexCount", &(*vertex_count as f64).into());
super::set_js_prop(&obj, "triangleCount", &(*triangle_count as f64).into());
}
ParseEvent::Progress {
phase,
percent,
entities_processed,
total_entities,
} => {
super::set_js_prop(&obj, "type", &"progress".into());
super::set_js_prop(&obj, "phase", &phase.as_str().into());
super::set_js_prop(&obj, "percent", &(*percent as f64).into());
super::set_js_prop(&obj, "entitiesProcessed", &(*entities_processed as f64).into());
super::set_js_prop(&obj, "totalEntities", &(*total_entities as f64).into());
}
ParseEvent::Completed {
duration_ms,
entity_count,
triangle_count,
} => {
super::set_js_prop(&obj, "type", &"completed".into());
super::set_js_prop(&obj, "durationMs", &(*duration_ms).into());
super::set_js_prop(&obj, "entityCount", &(*entity_count as f64).into());
super::set_js_prop(&obj, "triangleCount", &(*triangle_count as f64).into());
}
ParseEvent::Error { message, position } => {
super::set_js_prop(&obj, "type", &"error".into());
super::set_js_prop(&obj, "message", &message.as_str().into());
if let Some(pos) = position {
super::set_js_prop(&obj, "position", &(*pos as f64).into());
}
}
}
obj.into()
}