Skip to main content

ifc_model/
lazy.rs

1//! Lazily decoded entities: a slot per id, filled on first access.
2//!
3//! # Why
4//!
5//! Decoding every attribute of every record into owned `Value`s is most of
6//! the cost of reading a file: on real IFC exports the allocator alone was
7//! 41% of the read and `Model::insert` another 12%. A viewer that opens a
8//! file to show a type census, or the properties of a few elements, pays for
9//! millions of values it never looks at.
10//!
11//! A codec may instead register each entity as a byte span of its source
12//! plus its type name, and hand the model an [`EntitySource`] that decodes a
13//! span on demand. [`Model::get`](crate::Model::get) then decodes an entity
14//! the first time anyone asks for it and keeps the result, so every later
15//! access is a plain lookup and the returned reference is stable.
16//!
17//! # Contract
18//!
19//! The codec validates every record when it builds the model -- syntax and
20//! every conversion that could fail -- so decoding a registered span cannot
21//! fail while the source is unchanged. A source that changes underneath the
22//! model (a memory-mapped file edited by another process) breaks that
23//! promise; [`EntitySource::decode`] then panics with a message saying so
24//! rather than returning a different entity.
25
26use std::ops::Range;
27use std::sync::OnceLock;
28
29use crate::entity::Entity;
30
31/// Decodes the entities of a lazily loaded [`Model`](crate::Model).
32///
33/// Implemented by codecs; the model never knows what format a span is in.
34pub trait EntitySource: Send + Sync + std::fmt::Debug {
35    /// Decodes the entity whose record occupies `span` of the source.
36    ///
37    /// # Panics
38    ///
39    /// When the record no longer decodes, which means the source changed
40    /// after the codec validated it. A codec must never register a span it
41    /// has not validated.
42    fn decode(&self, span: Range<usize>) -> Entity;
43}
44
45/// One entity: decoded already, or a span still to decode.
46#[derive(Debug, Clone)]
47pub(crate) struct Slot {
48    entity: OnceLock<Entity>,
49    /// The record's bytes in the model's source; empty for an entity that
50    /// was inserted decoded.
51    span: Range<usize>,
52}
53
54impl Slot {
55    pub(crate) fn decoded(entity: Entity) -> Self {
56        Self {
57            entity: OnceLock::from(entity),
58            span: 0..0,
59        }
60    }
61
62    pub(crate) fn lazy(span: Range<usize>) -> Self {
63        Self {
64            entity: OnceLock::new(),
65            span,
66        }
67    }
68
69    /// The entity, decoding it on first access.
70    pub(crate) fn get(&self, source: Option<&dyn EntitySource>) -> &Entity {
71        self.entity.get_or_init(|| {
72            source
73                .expect("a lazy slot exists only in a model with a source")
74                .decode(self.span.clone())
75        })
76    }
77
78    /// The entity for editing, decoding it first when needed.
79    pub(crate) fn get_mut(&mut self, source: Option<&dyn EntitySource>) -> &mut Entity {
80        self.get(source);
81        self.entity
82            .get_mut()
83            .expect("`get` just initialised the slot")
84    }
85
86    /// The entity by value, decoding it first when needed.
87    pub(crate) fn into_entity(self, source: Option<&dyn EntitySource>) -> Entity {
88        self.get(source);
89        self.entity
90            .into_inner()
91            .expect("`get` just initialised the slot")
92    }
93
94    /// Whether the entity has been decoded.
95    pub(crate) fn is_decoded(&self) -> bool {
96        self.entity.get().is_some()
97    }
98}