use std::borrow::Cow;
use std::ops::Range;
use std::sync::Arc;
use ifc_model::{Entity, EntityId, EntitySource, Model};
use openbim_step::{decode_record_borrowed, OnMalformed, ParseOptions, Span};
use crate::{parser, StepError};
pub(crate) enum Bytes {
Owned(Vec<u8>),
Mapped(memmap2::Mmap),
}
impl Bytes {
fn as_slice(&self) -> &[u8] {
match self {
Self::Owned(bytes) => bytes,
Self::Mapped(map) => map,
}
}
}
pub(crate) struct StepSource {
bytes: Bytes,
}
impl std::fmt::Debug for StepSource {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let kind = match self.bytes {
Bytes::Owned(_) => "owned",
Bytes::Mapped(_) => "mapped",
};
formatter
.debug_struct("StepSource")
.field("bytes", &kind)
.field("len", &self.bytes.as_slice().len())
.finish()
}
}
impl EntitySource for StepSource {
fn decode(&self, span: Range<usize>) -> Entity {
const CHANGED: &str = "a validated STEP record no longer decodes: \
the source changed after the model was read (a mapped file must \
not be modified while its model is alive)";
let record = decode_record_borrowed(self.bytes.as_slice(), Span::new(span.start, span.end))
.expect(CHANGED);
parser::convert(record).expect(CHANGED).1
}
}
pub(crate) fn is_lazy(options: ParseOptions) -> bool {
options.on_malformed_record == OnMalformed::Abort && !options.check_references
}
pub(crate) fn read(bytes: Bytes) -> Result<Model, StepError> {
let source = Arc::new(StepSource { bytes });
if let Some(model) = index(&source) {
return Ok(model);
}
parser::parse(source.bytes.as_slice(), ParseOptions::strict())
}
const PARALLEL_BYTES: usize = 4 << 20;
fn index(source: &Arc<StepSource>) -> Option<Model> {
let input = source.bytes.as_slice();
let scanned = openbim_step::scan(input).ok()?;
let mut frames = Vec::new();
for record in scanned.records() {
let record = record.ok()?;
frames.push(record.span);
}
let ids = validate_all(input, &frames, threads(input.len()))?;
let shared: Arc<dyn EntitySource> = source.clone();
let mut model = Model::with_source(shared);
model.reserve(frames.len());
parser::apply_header(model.header_mut(), scanned.header().standard());
for (span, (id, name)) in frames.iter().zip(ids) {
model.insert_lazy(id, &name, span.start..span.end);
}
Some(model)
}
fn validate_all<'a>(
input: &'a [u8],
frames: &[Span],
threads: usize,
) -> Option<Vec<(EntityId, Cow<'a, str>)>> {
let one = |span: &Span| -> Option<(EntityId, Cow<'a, str>)> {
let record = decode_record_borrowed(input, *span).ok()?;
let id = parser::validate(&record).ok()?;
let record = record.records.into_iter().next()?;
Some((id, record.name))
};
if threads <= 1 || frames.len() < 2 {
return frames.iter().map(one).collect();
}
let chunk = frames.len().div_ceil(threads);
std::thread::scope(|scope| {
let parts: Vec<_> = frames
.chunks(chunk)
.map(|part| scope.spawn(move || part.iter().map(one).collect::<Option<Vec<_>>>()))
.collect();
let mut all = Vec::with_capacity(frames.len());
for part in parts {
all.extend(part.join().ok()??);
}
Some(all)
})
}
fn threads(len: usize) -> usize {
if cfg!(target_family = "wasm") || len < PARALLEL_BYTES {
return 1;
}
std::thread::available_parallelism().map_or(1, |n| n.get().min(8))
}