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