use super::output_cap_types::{SymbolicTruncation, SymbolicTruncationReason};
use super::output_cap_validate::all_finite;
use super::primitives::{
SymbolicCircle, SymbolicData, SymbolicFillArea, SymbolicGridAxis, SymbolicPolyline,
SymbolicText,
};
pub const MAX_SYMBOLIC_ELEMENTS: usize = 2_000_000;
pub const MAX_SYMBOLIC_BYTES: usize = 256 * 1024 * 1024;
const PRIMITIVE_OVERHEAD_BYTES: usize = 64;
const BYTES_PER_COORD: usize = 8;
pub(super) struct SymbolicAccumulator {
data: SymbolicData,
fill_items: Vec<Option<u32>>,
pub(super) limit: usize,
#[cfg(test)]
pub(super) refusals: usize,
bytes: usize,
pub(super) byte_limit: usize,
pub(super) revisit_budget: u32,
reason: Option<SymbolicTruncationReason>,
exhausted: bool,
}
impl SymbolicAccumulator {
pub(super) fn new() -> Self {
Self {
data: SymbolicData::default(),
fill_items: Vec::new(),
limit: MAX_SYMBOLIC_ELEMENTS,
bytes: 0,
byte_limit: MAX_SYMBOLIC_BYTES,
revisit_budget: super::item_walk::MAX_ITEM_REVISITS,
reason: None,
exhausted: false,
#[cfg(test)]
refusals: 0,
}
}
pub(super) fn is_exhausted(&self) -> bool {
self.exhausted
}
pub(super) fn note_item_bound(&mut self, reason: SymbolicTruncationReason) {
self.record(reason);
}
pub(super) fn charge_revisit(&mut self) -> bool {
self.revisit_budget
.checked_sub(1)
.map(|left| self.revisit_budget = left)
.is_some()
}
fn record(&mut self, reason: SymbolicTruncationReason) {
let severity = |r: SymbolicTruncationReason| match r {
SymbolicTruncationReason::ElementCount | SymbolicTruncationReason::OutputBytes => 1,
SymbolicTruncationReason::ItemDepth
| SymbolicTruncationReason::ItemRevisits
| SymbolicTruncationReason::ItemCycle => 0,
};
match self.reason {
Some(existing) if severity(existing) >= severity(reason) => {}
_ => self.reason = Some(reason),
}
}
fn len(&self) -> usize {
self.data.len()
}
fn exceeded_by(&self, payload: usize) -> Option<SymbolicTruncationReason> {
if self.len() >= self.limit {
return Some(SymbolicTruncationReason::ElementCount);
}
if self.bytes + PRIMITIVE_OVERHEAD_BYTES + payload * BYTES_PER_COORD > self.byte_limit {
return Some(SymbolicTruncationReason::OutputBytes);
}
None
}
fn charge(&mut self, payload: usize) {
self.bytes += PRIMITIVE_OVERHEAD_BYTES + payload * BYTES_PER_COORD;
}
fn try_push<F>(&mut self, payload: usize, valid: bool, push: F)
where
F: FnOnce(&mut SymbolicData),
{
if valid {
if let Some(reason) = self.exceeded_by(payload) {
self.record(reason);
self.exhausted = true;
#[cfg(test)]
{
self.refusals += 1;
}
} else {
self.charge(payload);
push(&mut self.data);
}
}
}
pub(super) fn push_grid_axis(&mut self, axis: SymbolicGridAxis) {
let payload = axis.tag.len();
self.try_push(payload, all_finite(&axis.endpoints), |data| {
data.grid_axes.push(axis)
});
}
pub(super) fn push_polyline(&mut self, polyline: SymbolicPolyline) {
let payload =
polyline.points.len() + polyline.ifc_type.len() + polyline.representation.len();
self.try_push(payload, all_finite(&polyline.points), |data| {
data.polylines.push(polyline)
});
}
pub(super) fn push_circle(&mut self, circle: SymbolicCircle) {
let valid = all_finite(&[circle.center_x, circle.center_y, circle.radius]);
let payload = 8 + circle.ifc_type.len() + circle.representation.len();
self.try_push(payload, valid, |data| data.circles.push(circle));
}
pub(super) fn push_text(&mut self, text: SymbolicText) {
let payload = text.content.len()
+ text.alignment.len()
+ text.ifc_type.len()
+ text.representation.len();
self.try_push(payload, all_finite(&[text.x, text.y]), |data| {
data.texts.push(text)
});
}
#[cfg(test)]
pub(super) fn push_fill(&mut self, fill: SymbolicFillArea) {
self.push_fill_with_provenance(fill, None);
}
pub(super) fn push_fill_with_provenance(&mut self, fill: SymbolicFillArea, item: Option<u32>) {
let before = self.data.fills.len();
let payload = std::mem::size_of::<Option<u32>>() + fill.points.len()
+ fill.holes_offsets.len()
+ fill.ifc_type.len()
+ fill.representation.len();
self.try_push(payload, all_finite(&fill.points), |data| {
data.fills.push(fill)
});
if self.data.fills.len() > before { self.fill_items.push(item); }
}
pub(super) fn into_provenance(mut self) -> super::SymbolicDataWithProvenance {
let items = std::mem::take(&mut self.fill_items);
super::SymbolicDataWithProvenance::new(self.into_data(), items)
}
pub(super) fn into_data(mut self) -> SymbolicData {
if let Some(reason) = self.reason {
let emitted = self.data.len();
let limit = match reason {
SymbolicTruncationReason::ElementCount => Some(self.limit),
SymbolicTruncationReason::OutputBytes => Some(self.byte_limit),
SymbolicTruncationReason::ItemDepth
| SymbolicTruncationReason::ItemRevisits
| SymbolicTruncationReason::ItemCycle => None,
};
self.data.truncated = Some(SymbolicTruncation {
reason,
emitted,
limit,
});
}
self.data
}
}