use std::fmt::{Display, Formatter, Result as FmtResult};
use derive_debug::Dbg;
use num_enum::TryFromPrimitive;
use crate::errors::AsMltError as _;
#[cfg(feature = "unstable-v2")]
use crate::tile::ZStep;
use crate::utils::formatter::{bytes_dbg, compact_dbg};
use crate::{MltError, MltResult};
#[derive(Debug, Clone, Copy, PartialEq, TryFromPrimitive)]
#[repr(u8)]
pub enum LogicalTechnique {
None = 0b0000_0000,
Delta = 0b0010_0000,
ComponentwiseDelta = 0b0100_0000,
Rle = 0b0110_0000,
Morton = 0b1000_0000,
}
#[derive(Debug, Clone, Copy, PartialEq, TryFromPrimitive)]
#[repr(u8)]
pub enum LogicalCombination {
None = 0b0000_0000,
Delta = 0b0010_0000,
DeltaRle = 0b0010_1100,
ComponentwiseDelta = 0b0100_0000,
Rle = 0b0110_0000,
MortonDelta = 0b1000_0100,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RleLayout {
Split,
#[cfg(feature = "unstable-v2")]
Interleaved,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum RleMeta {
Split { runs: u32, num_rle_values: u32 },
#[cfg(feature = "unstable-v2")]
Interleaved { num_rle_values: u32 },
}
impl RleMeta {
#[cfg(feature = "unstable-v2")]
#[must_use]
pub(crate) fn num_rle_values(self) -> u32 {
match self {
Self::Split { num_rle_values, .. } => num_rle_values,
#[cfg(feature = "unstable-v2")]
Self::Interleaved { num_rle_values } => num_rle_values,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Morton {
pub(crate) bits: u32,
pub(crate) shift: u32,
}
impl Morton {
pub fn new(bits: u32, shift: u32) -> MltResult<Self> {
if bits <= 16 {
Ok(Self { bits, shift })
} else {
Err(MltError::InvalidMortonBits(bits))
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AlpScale {
pub(crate) e: u8,
pub(crate) f: u8,
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Alp {
pub(crate) scale: AlpScale,
pub(crate) base: i64,
}
#[cfg(feature = "unstable-v2")]
impl AlpScale {
pub(crate) const MAX_EXPONENT: u8 = 18;
#[cfg(test)]
pub(crate) fn net(self) -> u8 {
self.e - self.f
}
pub(crate) fn to_byte(self) -> u8 {
Self::row_start(self.e) + self.f
}
pub(crate) fn from_byte(byte: u8) -> MltResult<Self> {
(0..=Self::MAX_EXPONENT)
.find_map(|e| {
let f = byte.checked_sub(Self::row_start(e))?;
(f <= e).then_some(Self { e, f })
})
.ok_or(MltError::InvalidAlpScale(byte))
}
fn row_start(e: u8) -> u8 {
(1..=e).sum()
}
}
impl std::fmt::Debug for Alp {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
write!(
f,
"Alp {{ e: {}, f: {}, base: {} }}",
self.scale.e, self.scale.f, self.base
)
}
}
#[cfg(feature = "unstable-v2")]
impl Alp {
#[cfg(test)]
pub(crate) fn new(e: u8, f: u8, base: i64) -> MltResult<Self> {
if e <= AlpScale::MAX_EXPONENT && f <= e {
Ok(Self {
scale: AlpScale { e, f },
base,
})
} else {
Err(MltError::InvalidAlpParams(e, f))
}
}
#[expect(
clippy::cast_sign_loss,
reason = "the bit pattern is the point; `code_at` casts it back"
)]
pub(crate) fn offset_of(self, code: i64) -> u64 {
(code as u64).wrapping_sub(self.base as u64)
}
#[expect(
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
reason = "the bit pattern is the point; inverts `offset_of` exactly"
)]
pub(crate) fn code_at(self, offset: u64) -> i64 {
(self.base as u64).wrapping_add(offset) as i64
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ValueKind {
Int,
Bool,
Float,
Vertex,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum IntLogical {
None,
Delta,
Rle(RleMeta),
DeltaRle(RleMeta),
Delta2,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BoolLogical {
None,
ByteRle(RleMeta),
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum FloatLogical {
None,
Dict,
Alp(Alp),
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum VertexLogical {
None,
Delta,
ComponentwiseDelta,
ComponentwiseDelta2,
MortonDelta(Morton),
Rans,
#[cfg(feature = "unstable-v2")]
Xyz(ZStep, XyzLogical),
}
impl VertexLogical {
#[must_use]
pub fn words_per_vertex(self) -> u32 {
match self {
Self::None
| Self::Delta
| Self::ComponentwiseDelta
| Self::ComponentwiseDelta2
| Self::Rans => 2,
Self::MortonDelta(_) => 1,
#[cfg(feature = "unstable-v2")]
Self::Xyz(..) => 3,
}
}
}
#[cfg(feature = "unstable-v2")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum XyzLogical {
None,
Delta,
ComponentwiseDelta,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LogicalEncoding {
Int(IntLogical),
Bool(BoolLogical),
Float(FloatLogical),
Vertex(VertexLogical),
}
impl LogicalEncoding {
#[must_use]
pub(crate) fn words_per_value(self) -> u32 {
match self {
Self::Vertex(logical) => logical.words_per_vertex(),
Self::Int(_) | Self::Bool(_) | Self::Float(_) => 1,
}
}
#[must_use]
pub fn kind(self) -> ValueKind {
match self {
Self::Int(_) => ValueKind::Int,
Self::Bool(_) => ValueKind::Bool,
Self::Float(_) => ValueKind::Float,
Self::Vertex(_) => ValueKind::Vertex,
}
}
#[must_use]
pub(crate) fn none(kind: ValueKind) -> Self {
match kind {
ValueKind::Int => Self::Int(IntLogical::None),
ValueKind::Bool => Self::Bool(BoolLogical::None),
ValueKind::Float => Self::Float(FloatLogical::None),
ValueKind::Vertex => Self::Vertex(VertexLogical::None),
}
}
#[must_use]
pub(crate) fn is_identity(self) -> bool {
matches!(
self,
Self::Int(IntLogical::None)
| Self::Bool(BoolLogical::None)
| Self::Float(FloatLogical::None | FloatLogical::Dict)
| Self::Vertex(VertexLogical::None)
)
}
}
#[derive(Debug, PartialEq)]
pub struct LogicalValue {
pub(crate) meta: StreamMeta,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, TryFromPrimitive)]
#[repr(u8)]
pub enum DictionaryType {
None = 0b0000_0000,
Single = 0b0000_0001,
Shared = 0b0000_0010,
Vertex = 0b0000_0011,
Morton = 0b0000_0100,
Fsst = 0b0000_0101,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, TryFromPrimitive)]
#[repr(u8)]
pub enum OffsetType {
Vertex = 0b0000_0000,
Index = 0b0000_0001,
String = 0b0000_0010,
Key = 0b0000_0011,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, TryFromPrimitive)]
#[repr(u8)]
pub enum LengthType {
VarBinary = 0b0000_0000,
Geometries = 0b0000_0001,
Parts = 0b0000_0010,
Rings = 0b0000_0011,
Triangles = 0b0000_0100,
Symbol = 0b0000_0101,
Dictionary = 0b0000_0110,
#[cfg(feature = "unstable-v2")]
Nested = 0b0000_0111,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum StreamType {
Present,
Data(DictionaryType),
Offset(OffsetType),
Length(LengthType),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum PhysicalEncoding {
None,
FastPFor(FastPForKind),
VarInt,
#[cfg(feature = "unstable-v2")]
BitPacked,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum FastPForKind {
Block256Be,
#[cfg(feature = "unstable-v2")]
Block128Le,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct IntEncoding {
pub logical: LogicalEncoding,
pub physical: PhysicalEncoding,
}
impl IntEncoding {
#[must_use]
pub(crate) const fn new(logical: LogicalEncoding, physical: PhysicalEncoding) -> Self {
Self { logical, physical }
}
#[must_use]
pub(crate) fn none(kind: ValueKind) -> Self {
Self::new(LogicalEncoding::none(kind), PhysicalEncoding::None)
}
}
#[derive(Clone, Copy, Dbg, PartialEq)]
pub struct StreamMeta {
#[dbg(formatter = "compact_dbg")]
pub stream_type: StreamType,
#[dbg(formatter = "compact_dbg")]
pub encoding: IntEncoding,
pub(crate) num_values: u32,
}
impl StreamMeta {
#[inline]
pub(crate) fn new(stream_type: StreamType, encoding: IntEncoding, num_values: u32) -> Self {
Self {
stream_type,
encoding,
num_values,
}
}
#[inline]
pub(crate) fn new2(
stream_type: StreamType,
logical: LogicalEncoding,
physical: PhysicalEncoding,
num_words: usize,
) -> MltResult<Self> {
let enc = IntEncoding::new(logical, physical);
let num_words = u32::try_from(num_words)?;
let per_value = logical.words_per_value();
if !num_words.is_multiple_of(per_value) {
return Err(MltError::PartialVertex(num_words, per_value));
}
Ok(Self::new(stream_type, enc, num_words / per_value))
}
#[inline]
pub(crate) fn num_words(&self) -> MltResult<u32> {
self.num_values
.checked_mul(self.encoding.logical.words_per_value())
.or_overflow()
}
#[inline]
pub(crate) fn new_none(
stream_type: StreamType,
kind: ValueKind,
num_values: usize,
) -> MltResult<Self> {
let enc = IntEncoding::none(kind);
Ok(Self::new(stream_type, enc, u32::try_from(num_values)?))
}
}
#[derive(Clone, Dbg, PartialEq)]
pub struct RawStream<'a> {
pub meta: StreamMeta,
#[dbg(formatter = "bytes_dbg")]
pub(crate) data: &'a [u8],
}
impl<'a> RawStream<'a> {
#[must_use]
pub(crate) fn new(meta: StreamMeta, data: &'a [u8]) -> Self {
Self { meta, data }
}
}
impl Display for StreamType {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match self {
Self::Present => f.write_str("present"),
Self::Data(dict) => {
let name = match dict {
DictionaryType::None => return f.write_str("data"),
DictionaryType::Single => "single",
DictionaryType::Shared => "shared",
DictionaryType::Vertex => "vertex",
DictionaryType::Morton => "morton",
DictionaryType::Fsst => "fsst",
};
write!(f, "data[{name}]")
}
Self::Offset(offset) => {
let name = match offset {
OffsetType::Vertex => "vertex",
OffsetType::Index => "index",
OffsetType::String => "string",
OffsetType::Key => "key",
};
write!(f, "offset[{name}]")
}
Self::Length(length) => {
let name = match length {
LengthType::VarBinary => "var-binary",
LengthType::Geometries => "geometries",
LengthType::Parts => "parts",
LengthType::Rings => "rings",
LengthType::Triangles => "triangles",
LengthType::Symbol => "symbol",
LengthType::Dictionary => "dictionary",
#[cfg(feature = "unstable-v2")]
LengthType::Nested => "nested",
};
write!(f, "length[{name}]")
}
}
}
}
impl Display for LogicalEncoding {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
let (kind, enc) = match self {
Self::Int(int) => (
"int",
match int {
IntLogical::None => "none",
IntLogical::Delta => "delta",
IntLogical::Rle(_) => "rle",
IntLogical::DeltaRle(_) => "delta-rle",
IntLogical::Delta2 => "delta2",
},
),
Self::Bool(b) => (
"bool",
match b {
BoolLogical::None => "none",
BoolLogical::ByteRle(_) => "byte-rle",
},
),
Self::Float(float) => (
"float",
match float {
FloatLogical::None => "none",
FloatLogical::Dict => "dict",
FloatLogical::Alp(_) => "alp",
},
),
Self::Vertex(vertex) => (
"vertex",
match vertex {
VertexLogical::None => "none",
VertexLogical::Delta => "delta",
VertexLogical::ComponentwiseDelta => "componentwise-delta",
VertexLogical::ComponentwiseDelta2 => "componentwise-delta2",
VertexLogical::MortonDelta(_) => "morton-delta",
VertexLogical::Rans => "rans",
#[cfg(feature = "unstable-v2")]
VertexLogical::Xyz(_, XyzLogical::None) => "xyz-none",
#[cfg(feature = "unstable-v2")]
VertexLogical::Xyz(_, XyzLogical::Delta) => "xyz-delta",
#[cfg(feature = "unstable-v2")]
VertexLogical::Xyz(_, XyzLogical::ComponentwiseDelta) => {
"xyz-componentwise-delta"
}
},
),
};
write!(f, "{kind}/{enc}")
}
}
impl Display for PhysicalEncoding {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match self {
Self::None => f.write_str("none"),
Self::VarInt => f.write_str("varint"),
Self::FastPFor(kind) => {
let name = match kind {
FastPForKind::Block256Be => "256be",
#[cfg(feature = "unstable-v2")]
FastPForKind::Block128Le => "128le",
};
write!(f, "fastpfor[{name}]")
}
#[cfg(feature = "unstable-v2")]
Self::BitPacked => f.write_str("bit-packed"),
}
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use usize_cast::IntoUsize as _;
use super::*;
fn morton() -> Morton {
Morton { bits: 4, shift: 0 }
}
fn alp() -> Alp {
Alp {
scale: AlpScale { e: 3, f: 1 },
base: -5,
}
}
fn rle() -> RleMeta {
RleMeta::Split {
runs: 2,
num_rle_values: 5,
}
}
#[rstest]
#[case::present(StreamType::Present, "present")]
#[case::data_none(StreamType::Data(DictionaryType::None), "data")]
#[case::data_single(StreamType::Data(DictionaryType::Single), "data[single]")]
#[case::data_shared(StreamType::Data(DictionaryType::Shared), "data[shared]")]
#[case::data_vertex(StreamType::Data(DictionaryType::Vertex), "data[vertex]")]
#[case::data_morton(StreamType::Data(DictionaryType::Morton), "data[morton]")]
#[case::data_fsst(StreamType::Data(DictionaryType::Fsst), "data[fsst]")]
#[case::offset_vertex(StreamType::Offset(OffsetType::Vertex), "offset[vertex]")]
#[case::offset_index(StreamType::Offset(OffsetType::Index), "offset[index]")]
#[case::offset_string(StreamType::Offset(OffsetType::String), "offset[string]")]
#[case::offset_key(StreamType::Offset(OffsetType::Key), "offset[key]")]
#[case::length_var_binary(StreamType::Length(LengthType::VarBinary), "length[var-binary]")]
#[case::length_geometries(StreamType::Length(LengthType::Geometries), "length[geometries]")]
#[case::length_parts(StreamType::Length(LengthType::Parts), "length[parts]")]
#[case::length_rings(StreamType::Length(LengthType::Rings), "length[rings]")]
#[case::length_triangles(StreamType::Length(LengthType::Triangles), "length[triangles]")]
#[case::length_symbol(StreamType::Length(LengthType::Symbol), "length[symbol]")]
#[case::length_dictionary(StreamType::Length(LengthType::Dictionary), "length[dictionary]")]
fn every_stream_type_renders_its_wire_label(
#[case] stream_type: StreamType,
#[case] expected: &str,
) {
assert_eq!(stream_type.to_string(), expected);
}
#[cfg(feature = "unstable-v2")]
#[test]
fn a_nested_length_stream_renders_its_wire_label() {
assert_eq!(
StreamType::Length(LengthType::Nested).to_string(),
"length[nested]"
);
}
#[rstest]
#[case::int_none(LogicalEncoding::Int(IntLogical::None), "int/none")]
#[case::int_delta(LogicalEncoding::Int(IntLogical::Delta), "int/delta")]
#[case::int_rle(LogicalEncoding::Int(IntLogical::Rle(rle())), "int/rle")]
#[case::int_delta_rle(LogicalEncoding::Int(IntLogical::DeltaRle(rle())), "int/delta-rle")]
#[case::bool_none(LogicalEncoding::Bool(BoolLogical::None), "bool/none")]
#[case::bool_byte_rle(LogicalEncoding::Bool(BoolLogical::ByteRle(rle())), "bool/byte-rle")]
#[case::float_none(LogicalEncoding::Float(FloatLogical::None), "float/none")]
#[case::float_dict(LogicalEncoding::Float(FloatLogical::Dict), "float/dict")]
#[case::float_alp(LogicalEncoding::Float(FloatLogical::Alp(alp())), "float/alp")]
#[case::vertex_none(LogicalEncoding::Vertex(VertexLogical::None), "vertex/none")]
#[case::vertex_delta(LogicalEncoding::Vertex(VertexLogical::Delta), "vertex/delta")]
#[case::vertex_componentwise_delta(
LogicalEncoding::Vertex(VertexLogical::ComponentwiseDelta),
"vertex/componentwise-delta"
)]
#[case::vertex_morton_delta(
LogicalEncoding::Vertex(VertexLogical::MortonDelta(morton())),
"vertex/morton-delta"
)]
fn every_logical_encoding_renders_kind_then_encoding(
#[case] encoding: LogicalEncoding,
#[case] expected: &str,
) {
assert_eq!(encoding.to_string(), expected);
}
#[cfg(feature = "unstable-v2")]
#[rstest]
#[case::none(XyzLogical::None, "vertex/xyz-none")]
#[case::delta(XyzLogical::Delta, "vertex/xyz-delta")]
#[case::componentwise_delta(XyzLogical::ComponentwiseDelta, "vertex/xyz-componentwise-delta")]
fn every_xyz_encoding_renders_kind_then_encoding(
#[case] xyz: XyzLogical,
#[case] expected: &str,
) {
let step = ZStep::new(0).unwrap();
assert_eq!(
LogicalEncoding::Vertex(VertexLogical::Xyz(step, xyz)).to_string(),
expected
);
}
#[rstest]
#[case::none(PhysicalEncoding::None, "none")]
#[case::varint(PhysicalEncoding::VarInt, "varint")]
#[case::fastpfor_256be(
PhysicalEncoding::FastPFor(FastPForKind::Block256Be),
"fastpfor[256be]"
)]
fn every_physical_encoding_renders_its_wire_label(
#[case] encoding: PhysicalEncoding,
#[case] expected: &str,
) {
assert_eq!(encoding.to_string(), expected);
}
#[cfg(feature = "unstable-v2")]
#[rstest]
#[case::fastpfor_128le(
PhysicalEncoding::FastPFor(FastPForKind::Block128Le),
"fastpfor[128le]"
)]
#[case::bit_packed(PhysicalEncoding::BitPacked, "bit-packed")]
fn every_v2_physical_encoding_renders_its_wire_label(
#[case] encoding: PhysicalEncoding,
#[case] expected: &str,
) {
assert_eq!(encoding.to_string(), expected);
}
#[rstest]
#[case::ints(LogicalEncoding::Int(IntLogical::Delta), 6, 6)]
#[case::pairs(LogicalEncoding::Vertex(VertexLogical::ComponentwiseDelta), 6, 3)]
#[case::morton_codes(LogicalEncoding::Vertex(VertexLogical::MortonDelta(morton())), 6, 6)]
#[cfg_attr(feature = "unstable-v2", case::triples(
LogicalEncoding::Vertex(VertexLogical::Xyz(ZStep::new(0).unwrap(), XyzLogical::Delta)),
6,
2
))]
fn a_stream_counts_its_words_as_values(
#[case] logical: LogicalEncoding,
#[case] words: usize,
#[case] values: u32,
) {
let meta = StreamMeta::new2(
StreamType::Present,
logical,
PhysicalEncoding::VarInt,
words,
)
.unwrap();
assert_eq!(meta.num_values, values);
assert_eq!(meta.num_words().unwrap().into_usize(), words);
}
#[rstest]
#[case::pairs(LogicalEncoding::Vertex(VertexLogical::Delta), 5, 2)]
#[cfg_attr(feature = "unstable-v2", case::triples(
LogicalEncoding::Vertex(VertexLogical::Xyz(ZStep::new(0).unwrap(), XyzLogical::Delta)),
4,
3
))]
fn a_stream_of_partial_vertices_is_rejected(
#[case] logical: LogicalEncoding,
#[case] words: u32,
#[case] per_vertex: u32,
) {
let err = StreamMeta::new2(
StreamType::Present,
logical,
PhysicalEncoding::VarInt,
words.into_usize(),
)
.unwrap_err();
assert!(
matches!(err, MltError::PartialVertex(w, p) if w == words && p == per_vertex),
"{err:?}"
);
}
#[rstest]
#[case::int(LogicalEncoding::Int(IntLogical::None), ValueKind::Int)]
#[case::bool(LogicalEncoding::Bool(BoolLogical::None), ValueKind::Bool)]
#[case::float(LogicalEncoding::Float(FloatLogical::None), ValueKind::Float)]
#[case::vertex(LogicalEncoding::Vertex(VertexLogical::None), ValueKind::Vertex)]
fn the_identity_encoding_of_a_kind_reports_that_kind_back(
#[case] expected: LogicalEncoding,
#[case] kind: ValueKind,
) {
let none = LogicalEncoding::none(kind);
assert_eq!(none, expected);
assert_eq!(none.kind(), kind);
assert!(none.is_identity());
}
}