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 ByteRle(RleMeta),
234}
235
236#[derive(Debug, Clone, Copy, PartialEq)]
238pub enum FloatLogical {
239 None,
241 Dict,
245 Alp(Alp),
248}
249
250#[derive(Debug, Clone, Copy, PartialEq)]
252pub enum VertexLogical {
253 None,
254 Delta,
255 ComponentwiseDelta,
256 ComponentwiseDelta2,
258 MortonDelta(Morton),
259 Rans,
261 #[cfg(feature = "unstable-v2")]
263 Xyz(ZStep, XyzLogical),
264}
265
266impl VertexLogical {
267 #[must_use]
269 pub fn words_per_vertex(self) -> u32 {
270 match self {
271 Self::None
272 | Self::Delta
273 | Self::ComponentwiseDelta
274 | Self::ComponentwiseDelta2
275 | Self::Rans => 2,
276 Self::MortonDelta(_) => 1,
277 #[cfg(feature = "unstable-v2")]
278 Self::Xyz(..) => 3,
279 }
280 }
281}
282
283#[cfg(feature = "unstable-v2")]
287#[derive(Debug, Clone, Copy, PartialEq, Eq)]
288pub enum XyzLogical {
289 None,
290 Delta,
291 ComponentwiseDelta,
292}
293
294#[derive(Debug, Clone, Copy, PartialEq)]
298pub enum LogicalEncoding {
299 Int(IntLogical),
300 Bool(BoolLogical),
301 Float(FloatLogical),
302 Vertex(VertexLogical),
303}
304
305impl LogicalEncoding {
306 #[must_use]
308 pub(crate) fn words_per_value(self) -> u32 {
309 match self {
310 Self::Vertex(logical) => logical.words_per_vertex(),
311 Self::Int(_) | Self::Bool(_) | Self::Float(_) => 1,
312 }
313 }
314
315 #[must_use]
317 pub fn kind(self) -> ValueKind {
318 match self {
319 Self::Int(_) => ValueKind::Int,
320 Self::Bool(_) => ValueKind::Bool,
321 Self::Float(_) => ValueKind::Float,
322 Self::Vertex(_) => ValueKind::Vertex,
323 }
324 }
325
326 #[must_use]
328 pub(crate) fn none(kind: ValueKind) -> Self {
329 match kind {
330 ValueKind::Int => Self::Int(IntLogical::None),
331 ValueKind::Bool => Self::Bool(BoolLogical::None),
332 ValueKind::Float => Self::Float(FloatLogical::None),
333 ValueKind::Vertex => Self::Vertex(VertexLogical::None),
334 }
335 }
336
337 #[must_use]
341 pub(crate) fn is_identity(self) -> bool {
342 matches!(
343 self,
344 Self::Int(IntLogical::None)
345 | Self::Bool(BoolLogical::None)
346 | Self::Float(FloatLogical::None | FloatLogical::Dict)
347 | Self::Vertex(VertexLogical::None)
348 )
349 }
350}
351
352#[derive(Debug, PartialEq)]
358pub struct LogicalValue {
359 pub(crate) meta: StreamMeta,
360}
361
362#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, TryFromPrimitive)]
366#[repr(u8)]
367pub enum DictionaryType {
368 None = 0b0000_0000,
369 Single = 0b0000_0001,
370 Shared = 0b0000_0010,
371 Vertex = 0b0000_0011,
372 Morton = 0b0000_0100,
373 Fsst = 0b0000_0101,
374}
375
376#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, TryFromPrimitive)]
378#[repr(u8)]
379pub enum OffsetType {
380 Vertex = 0b0000_0000,
381 Index = 0b0000_0001,
382 String = 0b0000_0010,
383 Key = 0b0000_0011,
384}
385
386#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, TryFromPrimitive)]
388#[repr(u8)]
389pub enum LengthType {
390 VarBinary = 0b0000_0000,
391 Geometries = 0b0000_0001,
392 Parts = 0b0000_0010,
393 Rings = 0b0000_0011,
394 Triangles = 0b0000_0100,
395 Symbol = 0b0000_0101,
396 Dictionary = 0b0000_0110,
397 #[cfg(feature = "unstable-v2")]
399 Nested = 0b0000_0111,
400}
401
402#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
404pub enum StreamType {
405 Present,
406 Data(DictionaryType),
407 Offset(OffsetType),
408 Length(LengthType),
409}
410
411#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
413pub enum PhysicalEncoding {
414 None,
415 FastPFor(FastPForKind),
418 VarInt,
421 #[cfg(feature = "unstable-v2")]
425 BitPacked,
426}
427
428#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
430pub enum FastPForKind {
431 Block256Be,
433 #[cfg(feature = "unstable-v2")]
435 Block128Le,
436}
437
438#[derive(Debug, Clone, Copy, PartialEq)]
441pub struct IntEncoding {
442 pub logical: LogicalEncoding,
443 pub physical: PhysicalEncoding,
444}
445
446impl IntEncoding {
447 #[must_use]
448 pub(crate) const fn new(logical: LogicalEncoding, physical: PhysicalEncoding) -> Self {
449 Self { logical, physical }
450 }
451
452 #[must_use]
453 pub(crate) fn none(kind: ValueKind) -> Self {
454 Self::new(LogicalEncoding::none(kind), PhysicalEncoding::None)
455 }
456}
457
458#[derive(Clone, Copy, Dbg, PartialEq)]
460pub struct StreamMeta {
461 #[dbg(formatter = "compact_dbg")]
462 pub stream_type: StreamType,
463 #[dbg(formatter = "compact_dbg")]
464 pub encoding: IntEncoding,
465 pub(crate) num_values: u32,
466}
467
468impl StreamMeta {
469 #[inline]
470 pub(crate) fn new(stream_type: StreamType, encoding: IntEncoding, num_values: u32) -> Self {
471 Self {
472 stream_type,
473 encoding,
474 num_values,
475 }
476 }
477
478 #[inline]
480 pub(crate) fn new2(
481 stream_type: StreamType,
482 logical: LogicalEncoding,
483 physical: PhysicalEncoding,
484 num_words: usize,
485 ) -> MltResult<Self> {
486 let enc = IntEncoding::new(logical, physical);
487 let num_words = u32::try_from(num_words)?;
488 let per_value = logical.words_per_value();
489 if !num_words.is_multiple_of(per_value) {
490 return Err(MltError::PartialVertex(num_words, per_value));
491 }
492 Ok(Self::new(stream_type, enc, num_words / per_value))
493 }
494
495 #[inline]
497 pub(crate) fn num_words(&self) -> MltResult<u32> {
498 self.num_values
499 .checked_mul(self.encoding.logical.words_per_value())
500 .or_overflow()
501 }
502
503 #[inline]
504 pub(crate) fn new_none(
505 stream_type: StreamType,
506 kind: ValueKind,
507 num_values: usize,
508 ) -> MltResult<Self> {
509 let enc = IntEncoding::none(kind);
510 Ok(Self::new(stream_type, enc, u32::try_from(num_values)?))
511 }
512}
513
514#[derive(Clone, Dbg, PartialEq)]
516pub struct RawStream<'a> {
517 pub meta: StreamMeta,
518 #[dbg(formatter = "bytes_dbg")]
519 pub(crate) data: &'a [u8],
520}
521
522impl<'a> RawStream<'a> {
523 #[must_use]
524 pub(crate) fn new(meta: StreamMeta, data: &'a [u8]) -> Self {
525 Self { meta, data }
526 }
527}
528
529impl Display for StreamType {
533 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
535 match self {
536 Self::Present => f.write_str("present"),
537 Self::Data(dict) => {
538 let name = match dict {
539 DictionaryType::None => return f.write_str("data"),
540 DictionaryType::Single => "single",
541 DictionaryType::Shared => "shared",
542 DictionaryType::Vertex => "vertex",
543 DictionaryType::Morton => "morton",
544 DictionaryType::Fsst => "fsst",
545 };
546 write!(f, "data[{name}]")
547 }
548 Self::Offset(offset) => {
549 let name = match offset {
550 OffsetType::Vertex => "vertex",
551 OffsetType::Index => "index",
552 OffsetType::String => "string",
553 OffsetType::Key => "key",
554 };
555 write!(f, "offset[{name}]")
556 }
557 Self::Length(length) => {
558 let name = match length {
559 LengthType::VarBinary => "var-binary",
560 LengthType::Geometries => "geometries",
561 LengthType::Parts => "parts",
562 LengthType::Rings => "rings",
563 LengthType::Triangles => "triangles",
564 LengthType::Symbol => "symbol",
565 LengthType::Dictionary => "dictionary",
566 #[cfg(feature = "unstable-v2")]
567 LengthType::Nested => "nested",
568 };
569 write!(f, "length[{name}]")
570 }
571 }
572 }
573}
574
575impl Display for LogicalEncoding {
576 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
580 let (kind, enc) = match self {
581 Self::Int(int) => (
582 "int",
583 match int {
584 IntLogical::None => "none",
585 IntLogical::Delta => "delta",
586 IntLogical::Rle(_) => "rle",
587 IntLogical::DeltaRle(_) => "delta-rle",
588 IntLogical::Delta2 => "delta2",
589 },
590 ),
591 Self::Bool(b) => (
592 "bool",
593 match b {
594 BoolLogical::None => "none",
595 BoolLogical::ByteRle(_) => "byte-rle",
596 },
597 ),
598 Self::Float(float) => (
599 "float",
600 match float {
601 FloatLogical::None => "none",
602 FloatLogical::Dict => "dict",
603 FloatLogical::Alp(_) => "alp",
604 },
605 ),
606 Self::Vertex(vertex) => (
607 "vertex",
608 match vertex {
609 VertexLogical::None => "none",
610 VertexLogical::Delta => "delta",
611 VertexLogical::ComponentwiseDelta => "componentwise-delta",
612 VertexLogical::ComponentwiseDelta2 => "componentwise-delta2",
613 VertexLogical::MortonDelta(_) => "morton-delta",
614 VertexLogical::Rans => "rans",
615 #[cfg(feature = "unstable-v2")]
616 VertexLogical::Xyz(_, XyzLogical::None) => "xyz-none",
617 #[cfg(feature = "unstable-v2")]
618 VertexLogical::Xyz(_, XyzLogical::Delta) => "xyz-delta",
619 #[cfg(feature = "unstable-v2")]
620 VertexLogical::Xyz(_, XyzLogical::ComponentwiseDelta) => {
621 "xyz-componentwise-delta"
622 }
623 },
624 ),
625 };
626 write!(f, "{kind}/{enc}")
627 }
628}
629
630impl Display for PhysicalEncoding {
631 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
633 match self {
634 Self::None => f.write_str("none"),
635 Self::VarInt => f.write_str("varint"),
636 Self::FastPFor(kind) => {
637 let name = match kind {
638 FastPForKind::Block256Be => "256be",
639 #[cfg(feature = "unstable-v2")]
640 FastPForKind::Block128Le => "128le",
641 };
642 write!(f, "fastpfor[{name}]")
643 }
644 #[cfg(feature = "unstable-v2")]
645 Self::BitPacked => f.write_str("bit-packed"),
646 }
647 }
648}
649
650#[cfg(test)]
651mod tests {
652 use rstest::rstest;
653 use usize_cast::IntoUsize as _;
654
655 use super::*;
656
657 fn morton() -> Morton {
658 Morton { bits: 4, shift: 0 }
659 }
660
661 fn alp() -> Alp {
662 Alp {
663 scale: AlpScale { e: 3, f: 1 },
664 base: -5,
665 }
666 }
667
668 fn rle() -> RleMeta {
669 RleMeta::Split {
670 runs: 2,
671 num_rle_values: 5,
672 }
673 }
674
675 #[rstest]
676 #[case::present(StreamType::Present, "present")]
677 #[case::data_none(StreamType::Data(DictionaryType::None), "data")]
678 #[case::data_single(StreamType::Data(DictionaryType::Single), "data[single]")]
679 #[case::data_shared(StreamType::Data(DictionaryType::Shared), "data[shared]")]
680 #[case::data_vertex(StreamType::Data(DictionaryType::Vertex), "data[vertex]")]
681 #[case::data_morton(StreamType::Data(DictionaryType::Morton), "data[morton]")]
682 #[case::data_fsst(StreamType::Data(DictionaryType::Fsst), "data[fsst]")]
683 #[case::offset_vertex(StreamType::Offset(OffsetType::Vertex), "offset[vertex]")]
684 #[case::offset_index(StreamType::Offset(OffsetType::Index), "offset[index]")]
685 #[case::offset_string(StreamType::Offset(OffsetType::String), "offset[string]")]
686 #[case::offset_key(StreamType::Offset(OffsetType::Key), "offset[key]")]
687 #[case::length_var_binary(StreamType::Length(LengthType::VarBinary), "length[var-binary]")]
688 #[case::length_geometries(StreamType::Length(LengthType::Geometries), "length[geometries]")]
689 #[case::length_parts(StreamType::Length(LengthType::Parts), "length[parts]")]
690 #[case::length_rings(StreamType::Length(LengthType::Rings), "length[rings]")]
691 #[case::length_triangles(StreamType::Length(LengthType::Triangles), "length[triangles]")]
692 #[case::length_symbol(StreamType::Length(LengthType::Symbol), "length[symbol]")]
693 #[case::length_dictionary(StreamType::Length(LengthType::Dictionary), "length[dictionary]")]
694 fn every_stream_type_renders_its_wire_label(
695 #[case] stream_type: StreamType,
696 #[case] expected: &str,
697 ) {
698 assert_eq!(stream_type.to_string(), expected);
699 }
700
701 #[cfg(feature = "unstable-v2")]
702 #[test]
703 fn a_nested_length_stream_renders_its_wire_label() {
704 assert_eq!(
705 StreamType::Length(LengthType::Nested).to_string(),
706 "length[nested]"
707 );
708 }
709
710 #[rstest]
711 #[case::int_none(LogicalEncoding::Int(IntLogical::None), "int/none")]
712 #[case::int_delta(LogicalEncoding::Int(IntLogical::Delta), "int/delta")]
713 #[case::int_rle(LogicalEncoding::Int(IntLogical::Rle(rle())), "int/rle")]
714 #[case::int_delta_rle(LogicalEncoding::Int(IntLogical::DeltaRle(rle())), "int/delta-rle")]
715 #[case::bool_none(LogicalEncoding::Bool(BoolLogical::None), "bool/none")]
716 #[case::bool_byte_rle(LogicalEncoding::Bool(BoolLogical::ByteRle(rle())), "bool/byte-rle")]
717 #[case::float_none(LogicalEncoding::Float(FloatLogical::None), "float/none")]
718 #[case::float_dict(LogicalEncoding::Float(FloatLogical::Dict), "float/dict")]
719 #[case::float_alp(LogicalEncoding::Float(FloatLogical::Alp(alp())), "float/alp")]
720 #[case::vertex_none(LogicalEncoding::Vertex(VertexLogical::None), "vertex/none")]
721 #[case::vertex_delta(LogicalEncoding::Vertex(VertexLogical::Delta), "vertex/delta")]
722 #[case::vertex_componentwise_delta(
723 LogicalEncoding::Vertex(VertexLogical::ComponentwiseDelta),
724 "vertex/componentwise-delta"
725 )]
726 #[case::vertex_morton_delta(
727 LogicalEncoding::Vertex(VertexLogical::MortonDelta(morton())),
728 "vertex/morton-delta"
729 )]
730 fn every_logical_encoding_renders_kind_then_encoding(
731 #[case] encoding: LogicalEncoding,
732 #[case] expected: &str,
733 ) {
734 assert_eq!(encoding.to_string(), expected);
735 }
736
737 #[cfg(feature = "unstable-v2")]
738 #[rstest]
739 #[case::none(XyzLogical::None, "vertex/xyz-none")]
740 #[case::delta(XyzLogical::Delta, "vertex/xyz-delta")]
741 #[case::componentwise_delta(XyzLogical::ComponentwiseDelta, "vertex/xyz-componentwise-delta")]
742 fn every_xyz_encoding_renders_kind_then_encoding(
743 #[case] xyz: XyzLogical,
744 #[case] expected: &str,
745 ) {
746 let step = ZStep::new(0).unwrap();
747 assert_eq!(
748 LogicalEncoding::Vertex(VertexLogical::Xyz(step, xyz)).to_string(),
749 expected
750 );
751 }
752
753 #[rstest]
754 #[case::none(PhysicalEncoding::None, "none")]
755 #[case::varint(PhysicalEncoding::VarInt, "varint")]
756 #[case::fastpfor_256be(
757 PhysicalEncoding::FastPFor(FastPForKind::Block256Be),
758 "fastpfor[256be]"
759 )]
760 fn every_physical_encoding_renders_its_wire_label(
761 #[case] encoding: PhysicalEncoding,
762 #[case] expected: &str,
763 ) {
764 assert_eq!(encoding.to_string(), expected);
765 }
766
767 #[cfg(feature = "unstable-v2")]
768 #[rstest]
769 #[case::fastpfor_128le(
770 PhysicalEncoding::FastPFor(FastPForKind::Block128Le),
771 "fastpfor[128le]"
772 )]
773 #[case::bit_packed(PhysicalEncoding::BitPacked, "bit-packed")]
774 fn every_v2_physical_encoding_renders_its_wire_label(
775 #[case] encoding: PhysicalEncoding,
776 #[case] expected: &str,
777 ) {
778 assert_eq!(encoding.to_string(), expected);
779 }
780
781 #[rstest]
782 #[case::ints(LogicalEncoding::Int(IntLogical::Delta), 6, 6)]
783 #[case::pairs(LogicalEncoding::Vertex(VertexLogical::ComponentwiseDelta), 6, 3)]
784 #[case::morton_codes(LogicalEncoding::Vertex(VertexLogical::MortonDelta(morton())), 6, 6)]
785 #[cfg_attr(feature = "unstable-v2", case::triples(
786 LogicalEncoding::Vertex(VertexLogical::Xyz(ZStep::new(0).unwrap(), XyzLogical::Delta)),
787 6,
788 2
789 ))]
790 fn a_stream_counts_its_words_as_values(
791 #[case] logical: LogicalEncoding,
792 #[case] words: usize,
793 #[case] values: u32,
794 ) {
795 let meta = StreamMeta::new2(
796 StreamType::Present,
797 logical,
798 PhysicalEncoding::VarInt,
799 words,
800 )
801 .unwrap();
802 assert_eq!(meta.num_values, values);
803 assert_eq!(meta.num_words().unwrap().into_usize(), words);
804 }
805
806 #[rstest]
807 #[case::pairs(LogicalEncoding::Vertex(VertexLogical::Delta), 5, 2)]
808 #[cfg_attr(feature = "unstable-v2", case::triples(
809 LogicalEncoding::Vertex(VertexLogical::Xyz(ZStep::new(0).unwrap(), XyzLogical::Delta)),
810 4,
811 3
812 ))]
813 fn a_stream_of_partial_vertices_is_rejected(
814 #[case] logical: LogicalEncoding,
815 #[case] words: u32,
816 #[case] per_vertex: u32,
817 ) {
818 let err = StreamMeta::new2(
819 StreamType::Present,
820 logical,
821 PhysicalEncoding::VarInt,
822 words.into_usize(),
823 )
824 .unwrap_err();
825 assert!(
826 matches!(err, MltError::PartialVertex(w, p) if w == words && p == per_vertex),
827 "{err:?}"
828 );
829 }
830
831 #[rstest]
832 #[case::int(LogicalEncoding::Int(IntLogical::None), ValueKind::Int)]
833 #[case::bool(LogicalEncoding::Bool(BoolLogical::None), ValueKind::Bool)]
834 #[case::float(LogicalEncoding::Float(FloatLogical::None), ValueKind::Float)]
835 #[case::vertex(LogicalEncoding::Vertex(VertexLogical::None), ValueKind::Vertex)]
836 fn the_identity_encoding_of_a_kind_reports_that_kind_back(
837 #[case] expected: LogicalEncoding,
838 #[case] kind: ValueKind,
839 ) {
840 let none = LogicalEncoding::none(kind);
841 assert_eq!(none, expected);
842 assert_eq!(none.kind(), kind);
843 assert!(none.is_identity());
844 }
845}