1use std::fmt::{Display, Formatter, Result as FmtResult};
2
3use derive_debug::Dbg;
4use num_enum::TryFromPrimitive;
5
6use crate::errors::AsMltError as _;
7#[cfg(feature = "unstable-v2")]
8use crate::tile::ZStep;
9use crate::utils::formatter::{bytes_dbg, compact_dbg};
10use crate::{MltError, MltResult};
11
12#[derive(Debug, Clone, Copy, PartialEq, TryFromPrimitive)]
18#[repr(u8)]
19pub enum LogicalTechnique {
20 None = 0b0000_0000,
21 Delta = 0b0010_0000,
22 ComponentwiseDelta = 0b0100_0000,
23 Rle = 0b0110_0000,
24 Morton = 0b1000_0000,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, TryFromPrimitive)]
33#[repr(u8)]
34pub enum LogicalCombination {
35 None = 0b0000_0000,
36 Delta = 0b0010_0000,
37 DeltaRle = 0b0010_1100,
38 ComponentwiseDelta = 0b0100_0000,
39 Rle = 0b0110_0000,
40 MortonDelta = 0b1000_0100,
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum RleLayout {
50 Split,
52 #[cfg(feature = "unstable-v2")]
54 Interleaved,
55}
56
57#[derive(Debug, Clone, Copy, PartialEq)]
59pub enum RleMeta {
60 Split { runs: u32, num_rle_values: u32 },
63 #[cfg(feature = "unstable-v2")]
68 Interleaved { num_rle_values: u32 },
69}
70
71impl RleMeta {
72 #[cfg(feature = "unstable-v2")]
74 #[must_use]
75 pub(crate) fn num_rle_values(self) -> u32 {
76 match self {
77 Self::Split { num_rle_values, .. } => num_rle_values,
78 #[cfg(feature = "unstable-v2")]
79 Self::Interleaved { num_rle_values } => num_rle_values,
80 }
81 }
82}
83
84#[derive(Debug, Clone, Copy, PartialEq)]
86pub struct Morton {
87 pub(crate) bits: u32,
89 pub(crate) shift: u32,
91}
92
93impl Morton {
94 pub fn new(bits: u32, shift: u32) -> MltResult<Self> {
95 if bits <= 16 {
96 Ok(Self { bits, shift })
97 } else {
98 Err(MltError::InvalidMortonBits(bits))
99 }
100 }
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
106pub struct AlpScale {
107 pub(crate) e: u8,
109 pub(crate) f: u8,
111}
112
113#[derive(Clone, Copy, PartialEq, Eq)]
116pub struct Alp {
117 pub(crate) scale: AlpScale,
118 pub(crate) base: i64,
120}
121
122#[cfg(feature = "unstable-v2")]
123impl AlpScale {
124 pub(crate) const MAX_EXPONENT: u8 = 18;
127
128 #[cfg(test)]
132 pub(crate) fn net(self) -> u8 {
133 self.e - self.f
134 }
135
136 pub(crate) fn to_byte(self) -> u8 {
138 Self::row_start(self.e) + self.f
139 }
140
141 pub(crate) fn from_byte(byte: u8) -> MltResult<Self> {
143 (0..=Self::MAX_EXPONENT)
144 .find_map(|e| {
145 let f = byte.checked_sub(Self::row_start(e))?;
146 (f <= e).then_some(Self { e, f })
147 })
148 .ok_or(MltError::InvalidAlpScale(byte))
149 }
150
151 fn row_start(e: u8) -> u8 {
153 (1..=e).sum()
154 }
155}
156
157impl std::fmt::Debug for Alp {
159 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
160 write!(
161 f,
162 "Alp {{ e: {}, f: {}, base: {} }}",
163 self.scale.e, self.scale.f, self.base
164 )
165 }
166}
167
168#[cfg(feature = "unstable-v2")]
169impl Alp {
170 #[cfg(test)]
171 pub(crate) fn new(e: u8, f: u8, base: i64) -> MltResult<Self> {
172 if e <= AlpScale::MAX_EXPONENT && f <= e {
173 Ok(Self {
174 scale: AlpScale { e, f },
175 base,
176 })
177 } else {
178 Err(MltError::InvalidAlpParams(e, f))
179 }
180 }
181
182 #[expect(
186 clippy::cast_sign_loss,
187 reason = "the bit pattern is the point; `code_at` casts it back"
188 )]
189 pub(crate) fn offset_of(self, code: i64) -> u64 {
190 (code as u64).wrapping_sub(self.base as u64)
191 }
192
193 #[expect(
195 clippy::cast_possible_wrap,
196 clippy::cast_sign_loss,
197 reason = "the bit pattern is the point; inverts `offset_of` exactly"
198 )]
199 pub(crate) fn code_at(self, offset: u64) -> i64 {
200 (self.base as u64).wrapping_add(offset) as i64
201 }
202}
203
204#[derive(Debug, Clone, Copy, PartialEq, Eq)]
207pub enum ValueKind {
208 Int,
209 Bool,
210 Float,
211 Vertex,
212}
213
214#[derive(Debug, Clone, Copy, PartialEq)]
217pub enum IntLogical {
218 None,
219 Delta,
220 Rle(RleMeta),
221 DeltaRle(RleMeta),
222 Delta2,
224}
225
226#[derive(Debug, Clone, Copy, PartialEq)]
228pub enum BoolLogical {
229 None,
231 Runs,
233 Sparse,
235 ByteRle(RleMeta),
238}
239
240#[derive(Debug, Clone, Copy, PartialEq)]
242pub enum FloatLogical {
243 None,
245 Dict,
249 Alp(Alp),
252}
253
254#[derive(Debug, Clone, Copy, PartialEq)]
256pub enum VertexLogical {
257 None,
258 Delta,
259 ComponentwiseDelta,
260 ComponentwiseDelta2,
262 MortonDelta(Morton),
263 Rans,
265 #[cfg(feature = "unstable-v2")]
267 Xyz(ZStep, XyzLogical),
268}
269
270impl VertexLogical {
271 #[must_use]
273 pub fn words_per_vertex(self) -> u32 {
274 match self {
275 Self::None
276 | Self::Delta
277 | Self::ComponentwiseDelta
278 | Self::ComponentwiseDelta2
279 | Self::Rans => 2,
280 Self::MortonDelta(_) => 1,
281 #[cfg(feature = "unstable-v2")]
282 Self::Xyz(..) => 3,
283 }
284 }
285}
286
287#[cfg(feature = "unstable-v2")]
291#[derive(Debug, Clone, Copy, PartialEq, Eq)]
292pub enum XyzLogical {
293 None,
294 Delta,
295 ComponentwiseDelta,
296}
297
298#[derive(Debug, Clone, Copy, PartialEq)]
302pub enum LogicalEncoding {
303 Int(IntLogical),
304 Bool(BoolLogical),
305 Float(FloatLogical),
306 Vertex(VertexLogical),
307}
308
309impl LogicalEncoding {
310 #[must_use]
312 pub(crate) fn words_per_value(self) -> u32 {
313 match self {
314 Self::Vertex(logical) => logical.words_per_vertex(),
315 Self::Int(_) | Self::Bool(_) | Self::Float(_) => 1,
316 }
317 }
318
319 #[must_use]
321 pub fn kind(self) -> ValueKind {
322 match self {
323 Self::Int(_) => ValueKind::Int,
324 Self::Bool(_) => ValueKind::Bool,
325 Self::Float(_) => ValueKind::Float,
326 Self::Vertex(_) => ValueKind::Vertex,
327 }
328 }
329
330 #[must_use]
332 pub(crate) fn none(kind: ValueKind) -> Self {
333 match kind {
334 ValueKind::Int => Self::Int(IntLogical::None),
335 ValueKind::Bool => Self::Bool(BoolLogical::None),
336 ValueKind::Float => Self::Float(FloatLogical::None),
337 ValueKind::Vertex => Self::Vertex(VertexLogical::None),
338 }
339 }
340
341 #[must_use]
345 pub(crate) fn is_identity(self) -> bool {
346 matches!(
347 self,
348 Self::Int(IntLogical::None)
349 | Self::Bool(BoolLogical::None)
350 | Self::Float(FloatLogical::None | FloatLogical::Dict)
351 | Self::Vertex(VertexLogical::None)
352 )
353 }
354}
355
356#[derive(Debug, PartialEq)]
362pub struct LogicalValue {
363 pub(crate) meta: StreamMeta,
364}
365
366#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, TryFromPrimitive)]
370#[repr(u8)]
371pub enum DictionaryType {
372 None = 0b0000_0000,
373 Single = 0b0000_0001,
374 Shared = 0b0000_0010,
375 Vertex = 0b0000_0011,
376 Morton = 0b0000_0100,
377 Fsst = 0b0000_0101,
378}
379
380#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, TryFromPrimitive)]
382#[repr(u8)]
383pub enum OffsetType {
384 Vertex = 0b0000_0000,
385 Index = 0b0000_0001,
386 String = 0b0000_0010,
387 Key = 0b0000_0011,
388}
389
390#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, TryFromPrimitive)]
392#[repr(u8)]
393pub enum LengthType {
394 VarBinary = 0b0000_0000,
395 Geometries = 0b0000_0001,
396 Parts = 0b0000_0010,
397 Rings = 0b0000_0011,
398 Triangles = 0b0000_0100,
399 Symbol = 0b0000_0101,
400 Dictionary = 0b0000_0110,
401 #[cfg(feature = "unstable-v2")]
403 Nested = 0b0000_0111,
404}
405
406#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
408pub enum StreamType {
409 Present,
410 Data(DictionaryType),
411 Offset(OffsetType),
412 Length(LengthType),
413}
414
415#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
417pub enum PhysicalEncoding {
418 None,
419 FastPFor(FastPForKind),
422 VarInt,
425 #[cfg(feature = "unstable-v2")]
429 BitPacked,
430}
431
432#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
434pub enum FastPForKind {
435 Block256Be,
437 #[cfg(feature = "unstable-v2")]
439 Block128Le,
440}
441
442#[derive(Debug, Clone, Copy, PartialEq)]
445pub struct IntEncoding {
446 pub logical: LogicalEncoding,
447 pub physical: PhysicalEncoding,
448}
449
450impl IntEncoding {
451 #[must_use]
452 pub(crate) const fn new(logical: LogicalEncoding, physical: PhysicalEncoding) -> Self {
453 Self { logical, physical }
454 }
455
456 #[must_use]
457 pub(crate) fn none(kind: ValueKind) -> Self {
458 Self::new(LogicalEncoding::none(kind), PhysicalEncoding::None)
459 }
460}
461
462#[derive(Clone, Copy, Dbg, PartialEq)]
464pub struct StreamMeta {
465 #[dbg(formatter = "compact_dbg")]
466 pub stream_type: StreamType,
467 #[dbg(formatter = "compact_dbg")]
468 pub encoding: IntEncoding,
469 pub(crate) num_values: u32,
470}
471
472impl StreamMeta {
473 #[inline]
474 pub(crate) fn new(stream_type: StreamType, encoding: IntEncoding, num_values: u32) -> Self {
475 Self {
476 stream_type,
477 encoding,
478 num_values,
479 }
480 }
481
482 #[inline]
484 pub(crate) fn new2(
485 stream_type: StreamType,
486 logical: LogicalEncoding,
487 physical: PhysicalEncoding,
488 num_words: usize,
489 ) -> MltResult<Self> {
490 let enc = IntEncoding::new(logical, physical);
491 let num_words = u32::try_from(num_words)?;
492 let per_value = logical.words_per_value();
493 if !num_words.is_multiple_of(per_value) {
494 return Err(MltError::PartialVertex(num_words, per_value));
495 }
496 Ok(Self::new(stream_type, enc, num_words / per_value))
497 }
498
499 #[inline]
501 pub(crate) fn num_words(&self) -> MltResult<u32> {
502 self.num_values
503 .checked_mul(self.encoding.logical.words_per_value())
504 .or_overflow()
505 }
506
507 #[inline]
508 pub(crate) fn new_none(
509 stream_type: StreamType,
510 kind: ValueKind,
511 num_values: usize,
512 ) -> MltResult<Self> {
513 let enc = IntEncoding::none(kind);
514 Ok(Self::new(stream_type, enc, u32::try_from(num_values)?))
515 }
516}
517
518#[derive(Clone, Dbg, PartialEq)]
520pub struct RawStream<'a> {
521 pub meta: StreamMeta,
522 #[dbg(formatter = "bytes_dbg")]
523 pub(crate) data: &'a [u8],
524}
525
526impl<'a> RawStream<'a> {
527 #[must_use]
528 pub(crate) fn new(meta: StreamMeta, data: &'a [u8]) -> Self {
529 Self { meta, data }
530 }
531}
532
533impl Display for StreamType {
537 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
539 match self {
540 Self::Present => f.write_str("present"),
541 Self::Data(dict) => {
542 let name = match dict {
543 DictionaryType::None => return f.write_str("data"),
544 DictionaryType::Single => "single",
545 DictionaryType::Shared => "shared",
546 DictionaryType::Vertex => "vertex",
547 DictionaryType::Morton => "morton",
548 DictionaryType::Fsst => "fsst",
549 };
550 write!(f, "data[{name}]")
551 }
552 Self::Offset(offset) => {
553 let name = match offset {
554 OffsetType::Vertex => "vertex",
555 OffsetType::Index => "index",
556 OffsetType::String => "string",
557 OffsetType::Key => "key",
558 };
559 write!(f, "offset[{name}]")
560 }
561 Self::Length(length) => {
562 let name = match length {
563 LengthType::VarBinary => "var-binary",
564 LengthType::Geometries => "geometries",
565 LengthType::Parts => "parts",
566 LengthType::Rings => "rings",
567 LengthType::Triangles => "triangles",
568 LengthType::Symbol => "symbol",
569 LengthType::Dictionary => "dictionary",
570 #[cfg(feature = "unstable-v2")]
571 LengthType::Nested => "nested",
572 };
573 write!(f, "length[{name}]")
574 }
575 }
576 }
577}
578
579impl Display for LogicalEncoding {
580 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
584 let (kind, enc) = match self {
585 Self::Int(int) => (
586 "int",
587 match int {
588 IntLogical::None => "none",
589 IntLogical::Delta => "delta",
590 IntLogical::Rle(_) => "rle",
591 IntLogical::DeltaRle(_) => "delta-rle",
592 IntLogical::Delta2 => "delta2",
593 },
594 ),
595 Self::Bool(b) => (
596 "bool",
597 match b {
598 BoolLogical::None => "none",
599 BoolLogical::Runs => "runs",
600 BoolLogical::Sparse => "sparse",
601 BoolLogical::ByteRle(_) => "byte-rle",
602 },
603 ),
604 Self::Float(float) => (
605 "float",
606 match float {
607 FloatLogical::None => "none",
608 FloatLogical::Dict => "dict",
609 FloatLogical::Alp(_) => "alp",
610 },
611 ),
612 Self::Vertex(vertex) => (
613 "vertex",
614 match vertex {
615 VertexLogical::None => "none",
616 VertexLogical::Delta => "delta",
617 VertexLogical::ComponentwiseDelta => "componentwise-delta",
618 VertexLogical::ComponentwiseDelta2 => "componentwise-delta2",
619 VertexLogical::MortonDelta(_) => "morton-delta",
620 VertexLogical::Rans => "rans",
621 #[cfg(feature = "unstable-v2")]
622 VertexLogical::Xyz(_, XyzLogical::None) => "xyz-none",
623 #[cfg(feature = "unstable-v2")]
624 VertexLogical::Xyz(_, XyzLogical::Delta) => "xyz-delta",
625 #[cfg(feature = "unstable-v2")]
626 VertexLogical::Xyz(_, XyzLogical::ComponentwiseDelta) => {
627 "xyz-componentwise-delta"
628 }
629 },
630 ),
631 };
632 write!(f, "{kind}/{enc}")
633 }
634}
635
636impl Display for PhysicalEncoding {
637 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
639 match self {
640 Self::None => f.write_str("none"),
641 Self::VarInt => f.write_str("varint"),
642 Self::FastPFor(kind) => {
643 let name = match kind {
644 FastPForKind::Block256Be => "256be",
645 #[cfg(feature = "unstable-v2")]
646 FastPForKind::Block128Le => "128le",
647 };
648 write!(f, "fastpfor[{name}]")
649 }
650 #[cfg(feature = "unstable-v2")]
651 Self::BitPacked => f.write_str("bit-packed"),
652 }
653 }
654}
655
656#[cfg(test)]
657mod tests {
658 use rstest::rstest;
659 use usize_cast::IntoUsize as _;
660
661 use super::*;
662
663 fn morton() -> Morton {
664 Morton { bits: 4, shift: 0 }
665 }
666
667 fn alp() -> Alp {
668 Alp {
669 scale: AlpScale { e: 3, f: 1 },
670 base: -5,
671 }
672 }
673
674 fn rle() -> RleMeta {
675 RleMeta::Split {
676 runs: 2,
677 num_rle_values: 5,
678 }
679 }
680
681 #[rstest]
682 #[case::present(StreamType::Present, "present")]
683 #[case::data_none(StreamType::Data(DictionaryType::None), "data")]
684 #[case::data_single(StreamType::Data(DictionaryType::Single), "data[single]")]
685 #[case::data_shared(StreamType::Data(DictionaryType::Shared), "data[shared]")]
686 #[case::data_vertex(StreamType::Data(DictionaryType::Vertex), "data[vertex]")]
687 #[case::data_morton(StreamType::Data(DictionaryType::Morton), "data[morton]")]
688 #[case::data_fsst(StreamType::Data(DictionaryType::Fsst), "data[fsst]")]
689 #[case::offset_vertex(StreamType::Offset(OffsetType::Vertex), "offset[vertex]")]
690 #[case::offset_index(StreamType::Offset(OffsetType::Index), "offset[index]")]
691 #[case::offset_string(StreamType::Offset(OffsetType::String), "offset[string]")]
692 #[case::offset_key(StreamType::Offset(OffsetType::Key), "offset[key]")]
693 #[case::length_var_binary(StreamType::Length(LengthType::VarBinary), "length[var-binary]")]
694 #[case::length_geometries(StreamType::Length(LengthType::Geometries), "length[geometries]")]
695 #[case::length_parts(StreamType::Length(LengthType::Parts), "length[parts]")]
696 #[case::length_rings(StreamType::Length(LengthType::Rings), "length[rings]")]
697 #[case::length_triangles(StreamType::Length(LengthType::Triangles), "length[triangles]")]
698 #[case::length_symbol(StreamType::Length(LengthType::Symbol), "length[symbol]")]
699 #[case::length_dictionary(StreamType::Length(LengthType::Dictionary), "length[dictionary]")]
700 fn every_stream_type_renders_its_wire_label(
701 #[case] stream_type: StreamType,
702 #[case] expected: &str,
703 ) {
704 assert_eq!(stream_type.to_string(), expected);
705 }
706
707 #[cfg(feature = "unstable-v2")]
708 #[test]
709 fn a_nested_length_stream_renders_its_wire_label() {
710 assert_eq!(
711 StreamType::Length(LengthType::Nested).to_string(),
712 "length[nested]"
713 );
714 }
715
716 #[rstest]
717 #[case::int_none(LogicalEncoding::Int(IntLogical::None), "int/none")]
718 #[case::int_delta(LogicalEncoding::Int(IntLogical::Delta), "int/delta")]
719 #[case::int_rle(LogicalEncoding::Int(IntLogical::Rle(rle())), "int/rle")]
720 #[case::int_delta_rle(LogicalEncoding::Int(IntLogical::DeltaRle(rle())), "int/delta-rle")]
721 #[case::bool_none(LogicalEncoding::Bool(BoolLogical::None), "bool/none")]
722 #[case::bool_byte_rle(LogicalEncoding::Bool(BoolLogical::ByteRle(rle())), "bool/byte-rle")]
723 #[case::float_none(LogicalEncoding::Float(FloatLogical::None), "float/none")]
724 #[case::float_dict(LogicalEncoding::Float(FloatLogical::Dict), "float/dict")]
725 #[case::float_alp(LogicalEncoding::Float(FloatLogical::Alp(alp())), "float/alp")]
726 #[case::vertex_none(LogicalEncoding::Vertex(VertexLogical::None), "vertex/none")]
727 #[case::vertex_delta(LogicalEncoding::Vertex(VertexLogical::Delta), "vertex/delta")]
728 #[case::vertex_componentwise_delta(
729 LogicalEncoding::Vertex(VertexLogical::ComponentwiseDelta),
730 "vertex/componentwise-delta"
731 )]
732 #[case::vertex_morton_delta(
733 LogicalEncoding::Vertex(VertexLogical::MortonDelta(morton())),
734 "vertex/morton-delta"
735 )]
736 fn every_logical_encoding_renders_kind_then_encoding(
737 #[case] encoding: LogicalEncoding,
738 #[case] expected: &str,
739 ) {
740 assert_eq!(encoding.to_string(), expected);
741 }
742
743 #[cfg(feature = "unstable-v2")]
744 #[rstest]
745 #[case::none(XyzLogical::None, "vertex/xyz-none")]
746 #[case::delta(XyzLogical::Delta, "vertex/xyz-delta")]
747 #[case::componentwise_delta(XyzLogical::ComponentwiseDelta, "vertex/xyz-componentwise-delta")]
748 fn every_xyz_encoding_renders_kind_then_encoding(
749 #[case] xyz: XyzLogical,
750 #[case] expected: &str,
751 ) {
752 let step = ZStep::new(0).unwrap();
753 assert_eq!(
754 LogicalEncoding::Vertex(VertexLogical::Xyz(step, xyz)).to_string(),
755 expected
756 );
757 }
758
759 #[rstest]
760 #[case::none(PhysicalEncoding::None, "none")]
761 #[case::varint(PhysicalEncoding::VarInt, "varint")]
762 #[case::fastpfor_256be(
763 PhysicalEncoding::FastPFor(FastPForKind::Block256Be),
764 "fastpfor[256be]"
765 )]
766 fn every_physical_encoding_renders_its_wire_label(
767 #[case] encoding: PhysicalEncoding,
768 #[case] expected: &str,
769 ) {
770 assert_eq!(encoding.to_string(), expected);
771 }
772
773 #[cfg(feature = "unstable-v2")]
774 #[rstest]
775 #[case::fastpfor_128le(
776 PhysicalEncoding::FastPFor(FastPForKind::Block128Le),
777 "fastpfor[128le]"
778 )]
779 #[case::bit_packed(PhysicalEncoding::BitPacked, "bit-packed")]
780 fn every_v2_physical_encoding_renders_its_wire_label(
781 #[case] encoding: PhysicalEncoding,
782 #[case] expected: &str,
783 ) {
784 assert_eq!(encoding.to_string(), expected);
785 }
786
787 #[rstest]
788 #[case::ints(LogicalEncoding::Int(IntLogical::Delta), 6, 6)]
789 #[case::pairs(LogicalEncoding::Vertex(VertexLogical::ComponentwiseDelta), 6, 3)]
790 #[case::morton_codes(LogicalEncoding::Vertex(VertexLogical::MortonDelta(morton())), 6, 6)]
791 #[cfg_attr(feature = "unstable-v2", case::triples(
792 LogicalEncoding::Vertex(VertexLogical::Xyz(ZStep::new(0).unwrap(), XyzLogical::Delta)),
793 6,
794 2
795 ))]
796 fn a_stream_counts_its_words_as_values(
797 #[case] logical: LogicalEncoding,
798 #[case] words: usize,
799 #[case] values: u32,
800 ) {
801 let meta = StreamMeta::new2(
802 StreamType::Present,
803 logical,
804 PhysicalEncoding::VarInt,
805 words,
806 )
807 .unwrap();
808 assert_eq!(meta.num_values, values);
809 assert_eq!(meta.num_words().unwrap().into_usize(), words);
810 }
811
812 #[rstest]
813 #[case::pairs(LogicalEncoding::Vertex(VertexLogical::Delta), 5, 2)]
814 #[cfg_attr(feature = "unstable-v2", case::triples(
815 LogicalEncoding::Vertex(VertexLogical::Xyz(ZStep::new(0).unwrap(), XyzLogical::Delta)),
816 4,
817 3
818 ))]
819 fn a_stream_of_partial_vertices_is_rejected(
820 #[case] logical: LogicalEncoding,
821 #[case] words: u32,
822 #[case] per_vertex: u32,
823 ) {
824 let err = StreamMeta::new2(
825 StreamType::Present,
826 logical,
827 PhysicalEncoding::VarInt,
828 words.into_usize(),
829 )
830 .unwrap_err();
831 assert!(
832 matches!(err, MltError::PartialVertex(w, p) if w == words && p == per_vertex),
833 "{err:?}"
834 );
835 }
836
837 #[rstest]
838 #[case::int(LogicalEncoding::Int(IntLogical::None), ValueKind::Int)]
839 #[case::bool(LogicalEncoding::Bool(BoolLogical::None), ValueKind::Bool)]
840 #[case::float(LogicalEncoding::Float(FloatLogical::None), ValueKind::Float)]
841 #[case::vertex(LogicalEncoding::Vertex(VertexLogical::None), ValueKind::Vertex)]
842 fn the_identity_encoding_of_a_kind_reports_that_kind_back(
843 #[case] expected: LogicalEncoding,
844 #[case] kind: ValueKind,
845 ) {
846 let none = LogicalEncoding::none(kind);
847 assert_eq!(none, expected);
848 assert_eq!(none.kind(), kind);
849 assert!(none.is_identity());
850 }
851}