1use std::cell::RefCell;
2use std::ops::{Deref, DerefMut};
3
4use bitvec::vec::BitVec;
5
6use crate::decoder::Morton;
7use crate::encoder::model::{CurveParams, StagedLayer};
8use crate::encoder::property::group_string_properties;
9use crate::encoder::sort::{curve_params, sort_order};
10use crate::encoder::source::LayerSource;
11use crate::encoder::tile::stage_layer;
12use crate::encoder::{Codecs, Encoder, EncoderConfig, SortStrategy, encode01};
13#[cfg(feature = "unstable-v2")]
14use crate::encoder::{WireVersion, encode02};
15use crate::tile::TileLayer;
16use crate::{MltError, MltResult, PropValueRef};
17
18impl StagedLayer {
19 #[hotpath::measure]
26 pub fn encode_into(self, enc: Encoder, codecs: &mut Codecs) -> MltResult<Encoder> {
27 if self.name.is_empty() {
28 return Err(MltError::MissingLayerName);
29 }
30 #[cfg(feature = "unstable-v2")]
31 match enc.config().wire_version() {
32 WireVersion::V01 => encode01::encode_into01(self, enc, codecs),
33 WireVersion::V02 => encode02::encode_into02(self, enc, codecs),
34 }
35 #[cfg(not(feature = "unstable-v2"))]
36 encode01::encode_into01(self, enc, codecs)
37 }
38}
39
40const POOLED_CODECS_MAX_BYTES: usize = 16 << 20;
42
43thread_local! {
44 static CODECS_POOL: RefCell<Option<Codecs>> = const { RefCell::new(None) };
45}
46
47struct PooledCodecs(Option<Codecs>);
49
50impl PooledCodecs {
51 fn take() -> Self {
52 let pooled = CODECS_POOL.try_with(|pool| pool.borrow_mut().take());
53 Self(Some(pooled.ok().flatten().unwrap_or_default()))
54 }
55}
56
57impl Deref for PooledCodecs {
58 type Target = Codecs;
59
60 fn deref(&self) -> &Codecs {
61 self.0.as_ref().expect("codecs are only taken on drop")
62 }
63}
64
65impl DerefMut for PooledCodecs {
66 fn deref_mut(&mut self) -> &mut Codecs {
67 self.0.as_mut().expect("codecs are only taken on drop")
68 }
69}
70
71impl Drop for PooledCodecs {
72 fn drop(&mut self) {
73 if let Some(codecs) = self.0.take()
74 && codecs.scratch_bytes() <= POOLED_CODECS_MAX_BYTES
75 {
76 let _ = CODECS_POOL.try_with(|pool| *pool.borrow_mut() = Some(codecs));
77 }
78 }
79}
80
81fn seed_curve_caches(enc: &mut Encoder, curve_params: CurveParams) {
86 enc.hilbert_cache = Some(curve_params);
87 enc.morton_cache = Morton::new(curve_params.bits, curve_params.shift).ok();
88}
89
90impl TileLayer {
91 pub fn encode(&self, cfg: EncoderConfig) -> MltResult<Vec<u8>> {
101 encode_layer(self, cfg)
102 }
103}
104
105#[hotpath::measure]
107pub(crate) fn encode_layer(source: &impl LayerSource, cfg: EncoderConfig) -> MltResult<Vec<u8>> {
108 if source.name().is_empty() {
109 return Err(MltError::MissingLayerName);
110 }
111 let len = source.feature_count();
112 if len == 0 {
113 return Ok(Vec::new());
114 }
115
116 let mut sort_by = vec![SortStrategy::Unsorted];
117 if cfg.attempt_spatial_morton_sort() {
118 sort_by.push(SortStrategy::SpatialMorton);
119 }
120 if cfg.attempt_spatial_hilbert_sort() {
121 sort_by.push(SortStrategy::SpatialHilbert);
122 }
123 if cfg.attempt_id_sort() && !(0..len).map(|f| source.id(f)).is_sorted() {
125 sort_by.push(SortStrategy::Id);
126 }
127
128 let stats = analyze(source, cfg.allow_shared_dict())?;
129 let curve_params = curve_params(source);
132 let stage = |sort| {
133 let order = sort_order(source, sort, curve_params);
134 stage_layer(source, &order, &stats, cfg.into())
135 };
136
137 let mut enc = Encoder::new(cfg);
141 seed_curve_caches(&mut enc, curve_params);
142 let mut codecs = PooledCodecs::take();
143 if let [sort] = sort_by[..] {
144 return stage(sort)
145 .encode_into(enc, &mut codecs)?
146 .into_layer_bytes();
147 }
148 let mut best: Option<Encoder> = None;
149 for &sort in &sort_by {
150 enc = stage(sort).encode_into(enc, &mut codecs)?;
151 if best
152 .as_ref()
153 .is_none_or(|best| enc.total_len() < best.total_len())
154 {
155 best = Some(enc.preserve_results());
156 } else {
157 enc.clear_results();
159 }
160 }
161 best.expect("at least two trials ran").into_layer_bytes()
162}
163
164#[derive(Debug, Clone, Copy, PartialEq, Eq)]
166pub enum Presence {
167 AllNull,
169 AllPresent,
171 Mixed,
173 SameAsProp(usize),
180}
181impl Presence {
182 #[must_use]
184 pub fn from_bits(bits: &BitVec<u8>, existing: &[(BitVec<u8>, usize)]) -> Self {
185 if bits.not_any() {
186 Self::AllNull
187 } else if bits.all() {
188 Self::AllPresent
189 } else if let Some((_, idx)) = existing.iter().find(|(v, _)| v == bits) {
190 Self::SameAsProp(*idx)
191 } else {
192 Self::Mixed
193 }
194 }
195}
196
197#[derive(Debug, Clone, PartialEq, Eq)]
199pub enum SharedDictRole {
200 None,
202 Owner(String),
204 Member(usize),
206}
207
208#[derive(Debug, Clone, PartialEq, Eq)]
210pub struct PropertyStats {
211 pub presence: Presence,
212 pub stats: PropertyTypedStats,
213}
214
215#[derive(Debug, Clone, PartialEq, Eq)]
217pub struct LayerStats {
218 pub id: Option<PropertyStats>,
219 pub properties: Vec<PropertyStats>,
220}
221
222#[derive(Debug, Clone, Default, PartialEq, Eq)]
224pub enum PropertyTypedStats {
225 #[default]
227 None,
228 Bool,
229 Signed {
230 min: i64,
231 max: i64,
232 },
233 Unsigned {
234 min: u64,
235 max: u64,
236 },
237 F32,
238 F64,
239 String {
240 shared_dict: SharedDictRole,
241 },
242}
243
244impl PropertyTypedStats {
245 #[must_use]
246 pub fn values_fit_u32(&self) -> bool {
247 match self {
248 Self::None | Self::Bool | Self::F32 | Self::F64 | Self::String { .. } => false,
249 Self::Signed { min, max } => *min >= 0 && u32::try_from(*max).is_ok(),
250 Self::Unsigned { max, .. } => u32::try_from(*max).is_ok(),
251 }
252 }
253
254 #[must_use]
257 pub fn values_fit_i32(&self) -> bool {
258 match self {
259 Self::None | Self::Bool | Self::F32 | Self::F64 | Self::String { .. } => false,
260 Self::Signed { min, max } => i32::try_from(*min).is_ok() && i32::try_from(*max).is_ok(),
261 Self::Unsigned { max, .. } => i32::try_from(*max).is_ok(),
262 }
263 }
264
265 #[must_use]
266 pub fn shared_dict(&self) -> SharedDictRole {
267 match self {
268 Self::String { shared_dict, .. } => shared_dict.clone(),
269 Self::None
270 | Self::Bool
271 | Self::Signed { .. }
272 | Self::Unsigned { .. }
273 | Self::F32
274 | Self::F64 => SharedDictRole::None,
275 }
276 }
277
278 pub(crate) fn set_shared_dict(&mut self, role: SharedDictRole) {
279 match self {
280 Self::String { shared_dict, .. } => *shared_dict = role,
281 Self::None
282 | Self::Bool
283 | Self::Signed { .. }
284 | Self::Unsigned { .. }
285 | Self::F32
286 | Self::F64 => debug_assert_eq!(role, SharedDictRole::None),
287 }
288 }
289
290 pub(crate) fn push(
291 &mut self,
292 prop: PropValueRef<'_>,
293 column_idx: usize,
294 property_name: &str,
295 ) -> MltResult<()> {
296 match prop {
297 PropValueRef::Bool(_) => self.merge_same_kind(Self::Bool, column_idx, property_name),
298 PropValueRef::I8(v) => self.merge_signed(i64::from(v), column_idx, property_name),
299 PropValueRef::U8(v) => self.merge_unsigned(u64::from(v), column_idx, property_name),
300 PropValueRef::I32(v) => self.merge_signed(i64::from(v), column_idx, property_name),
301 PropValueRef::U32(v) => self.merge_unsigned(u64::from(v), column_idx, property_name),
302 PropValueRef::I64(v) => self.merge_signed(v, column_idx, property_name),
303 PropValueRef::U64(v) => self.merge_unsigned(v, column_idx, property_name),
304 PropValueRef::F32(_) => self.merge_same_kind(Self::F32, column_idx, property_name),
305 PropValueRef::F64(_) => self.merge_same_kind(Self::F64, column_idx, property_name),
306 PropValueRef::Str(_) => self.merge_string(column_idx, property_name),
307 }
308 }
309
310 fn merge_signed(
311 &mut self,
312 value: i64,
313 column_idx: usize,
314 property_name: &str,
315 ) -> MltResult<()> {
316 match self {
317 Self::None => {
318 *self = Self::Signed {
319 min: value,
320 max: value,
321 };
322 }
323 Self::Signed { min, max } => {
324 *min = (*min).min(value);
325 *max = (*max).max(value);
326 }
327 Self::Bool | Self::Unsigned { .. } | Self::F32 | Self::F64 | Self::String { .. } => {
328 return mixed_prop_err(column_idx, property_name);
329 }
330 }
331 Ok(())
332 }
333
334 fn merge_unsigned(
335 &mut self,
336 value: u64,
337 column_idx: usize,
338 property_name: &str,
339 ) -> MltResult<()> {
340 match self {
341 Self::None => {
342 *self = Self::Unsigned {
343 min: value,
344 max: value,
345 };
346 }
347 Self::Unsigned { min, max } => {
348 *min = (*min).min(value);
349 *max = (*max).max(value);
350 }
351 Self::Bool | Self::Signed { .. } | Self::F32 | Self::F64 | Self::String { .. } => {
352 return mixed_prop_err(column_idx, property_name);
353 }
354 }
355 Ok(())
356 }
357
358 fn merge_string(&mut self, column_idx: usize, property_name: &str) -> MltResult<()> {
359 match self {
360 Self::None => {
361 *self = Self::String {
362 shared_dict: SharedDictRole::None,
363 };
364 }
365 Self::String { .. } => {}
366 Self::Bool | Self::Signed { .. } | Self::Unsigned { .. } | Self::F32 | Self::F64 => {
367 return mixed_prop_err(column_idx, property_name);
368 }
369 }
370 Ok(())
371 }
372
373 fn merge_same_kind(
374 &mut self,
375 kind: Self,
376 column_idx: usize,
377 property_name: &str,
378 ) -> MltResult<()> {
379 match self {
380 Self::None => *self = kind,
381 Self::Bool if matches!(kind, Self::Bool) => {}
382 Self::F32 if matches!(kind, Self::F32) => {}
383 Self::F64 if matches!(kind, Self::F64) => {}
384 Self::Bool
385 | Self::Signed { .. }
386 | Self::Unsigned { .. }
387 | Self::F32
388 | Self::F64
389 | Self::String { .. } => return mixed_prop_err(column_idx, property_name),
390 }
391 Ok(())
392 }
393}
394
395#[cfg(any(test, feature = "__private"))]
396impl TileLayer {
397 pub(crate) fn analyze(&self, allow_shared_dict: bool) -> MltResult<LayerStats> {
399 analyze(self, allow_shared_dict)
400 }
401}
402
403#[hotpath::measure]
405pub(crate) fn analyze(source: &impl LayerSource, allow_shared_dict: bool) -> MltResult<LayerStats> {
406 let mut property_bits = Vec::with_capacity(source.property_count());
407 let mut properties = analyze_properties(source, &mut property_bits)?;
408 let id = analyze_ids(source, &property_bits);
409 if allow_shared_dict {
410 group_string_properties(source, &mut properties);
411 }
412 Ok(LayerStats { id, properties })
413}
414
415fn analyze_ids(
416 source: &impl LayerSource,
417 property_bits: &[(BitVec<u8>, usize)],
418) -> Option<PropertyStats> {
419 let mut min = u64::MAX;
420 let mut max = 0u64;
421 let mut bits = BitVec::<u8>::with_capacity(source.feature_count());
422 for feature in 0..source.feature_count() {
423 if let Some(id) = source.id(feature) {
424 min = min.min(id);
425 max = max.max(id);
426 bits.push(true);
427 } else {
428 bits.push(false);
429 }
430 }
431 let presence = Presence::from_bits(&bits, property_bits);
432 if presence == Presence::AllNull {
433 None
434 } else {
435 Some(PropertyStats {
436 presence,
437 stats: PropertyTypedStats::Unsigned { min, max },
438 })
439 }
440}
441
442fn analyze_properties(
443 source: &impl LayerSource,
444 property_bits: &mut Vec<(BitVec<u8>, usize)>,
445) -> MltResult<Vec<PropertyStats>> {
446 let kinds = source.property_kinds();
447 (0..source.property_count())
448 .map(|col_idx| -> MltResult<PropertyStats> {
449 let mut stats = PropertyTypedStats::default();
450 let mut bits = BitVec::<u8>::with_capacity(source.feature_count());
451 for feature in 0..source.feature_count() {
452 if let Some(prop) = source.property(feature, col_idx) {
453 if Some(&prop.kind()) != kinds.get(col_idx) {
454 return mixed_prop_err(col_idx, source.property_name(col_idx));
455 }
456 stats.push(prop, col_idx, source.property_name(col_idx))?;
457 bits.push(true);
458 } else {
459 bits.push(false);
460 }
461 }
462
463 let presence = Presence::from_bits(&bits, property_bits);
464 if presence == Presence::Mixed {
465 property_bits.push((bits, col_idx));
466 }
467 Ok(PropertyStats { presence, stats })
468 })
469 .collect()
470}
471
472#[inline]
473fn mixed_prop_err<T>(column_idx: usize, property_name: &str) -> MltResult<T> {
474 Err(MltError::MixedPropertyTypes(
475 column_idx,
476 property_name.to_owned(),
477 ))
478}