cloudini 0.3.1

The cloudini point cloud compression library for Rust.
Documentation
//! C++ decoder bridge (feature `use_cpp`).
//!
//! [`CppPointcloudDecoder`] wraps the cloudini C++ implementation via FFI and
//! exposes the same interface as the native Rust [`crate::PointcloudDecoder`].

use crate::ffi;
use crate::header::{decode_header, encode_header};
use crate::{EncodingInfo, Error, Result};

/// Decodes a Cloudini buffer using the C++ implementation.
///
/// The API is intentionally identical to [`crate::PointcloudDecoder`] so the
/// two types can be used interchangeably.
///
/// The decoder is stateless; you can reuse one instance across multiple buffers.
pub struct CppPointcloudDecoder;

impl CppPointcloudDecoder {
    /// Create a new (stateless) decoder.
    pub fn new() -> Self {
        Self
    }

    /// Decode a complete Cloudini buffer (embedded header + compressed chunks).
    ///
    /// Returns `(EncodingInfo, point_bytes)`.  The `EncodingInfo` is parsed on
    /// the Rust side so both implementations return identical metadata types.
    ///
    /// # Errors
    ///
    /// Returns [`Error::Cpp`] if the C++ side returns 0 (any internal error).
    /// Header parsing errors are forwarded from the Rust header parser.
    pub fn decode(&self, data: &[u8]) -> Result<(EncodingInfo, Vec<u8>)> {
        // Ask the C++ side to parse the header and return the output byte count.
        let decompressed_size =
            unsafe { ffi::cloudini_c_decompressed_size(data.as_ptr(), data.len() as u32) };
        if decompressed_size == 0 {
            return Err(Error::Cpp(
                "cloudini_c_decompressed_size returned 0 — header may be corrupt".into(),
            ));
        }

        let mut output = vec![0u8; decompressed_size as usize];
        let written = unsafe {
            ffi::cloudini_c_decode(
                data.as_ptr(),
                data.len() as u32,
                output.as_mut_ptr(),
                decompressed_size,
            )
        };

        if written == 0 {
            return Err(Error::Cpp("cloudini_c_decode returned 0".into()));
        }

        // Parse EncodingInfo via the Rust header parser for API symmetry.
        let (info, _) = decode_header(data)?;
        Ok((info, output))
    }

    /// Decode compressed chunk data using a previously parsed [`EncodingInfo`].
    ///
    /// `compressed_data` must **not** include the header — pass the slice
    /// starting immediately after the null terminator that ends the YAML block.
    ///
    /// Mirrors [`crate::PointcloudDecoder::decode_with_info`].
    pub fn decode_with_info(&self, info: &EncodingInfo, compressed_data: &[u8]) -> Result<Vec<u8>> {
        // The C++ API requires the full buffer (header + chunks). Reconstruct it.
        let mut full = encode_header(info);
        full.extend_from_slice(compressed_data);
        let (_, point_data) = self.decode(&full)?;
        Ok(point_data)
    }
}

impl Default for CppPointcloudDecoder {
    fn default() -> Self {
        Self::new()
    }
}