Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions src/cpp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
pub use cxx::Exception;
use cxx::UniquePtr;

use crate::CodecToSlice;

/// FFI bridge to the C++ FastPFOR library.
///
/// This module contains the raw FFI declarations for interfacing with the C++ code.
Expand Down Expand Up @@ -142,6 +144,51 @@ pub trait Codec32: CodecWrapper {
}
}

impl<C: Codec32> CodecToSlice<u32> for C {
type Error = Exception;

fn compress_to_slice<'out>(
&mut self,
input: &[u32],
output: &'out mut [u32],
) -> Result<&'out [u32], Self::Error> {
let result = self.encode32(input, output)?;
Ok(&*result)
}

fn decompress_to_slice<'out>(
&mut self,
input: &[u32],
output: &'out mut [u32],
) -> Result<&'out [u32], Self::Error> {
let result = self.decode32(input, output)?;
Ok(&*result)
}
}

// Note: 64-bit integers are compressed into 32-bit word arrays.
impl<C: Codec64> CodecToSlice<u64, u32> for C {
type Error = Exception;

fn compress_to_slice<'out>(
&mut self,
input: &[u64],
output: &'out mut [u32],
) -> Result<&'out [u32], Self::Error> {
let result = self.encode64(input, output)?;
Ok(&*result)
}

fn decompress_to_slice<'out>(
&mut self,
input: &[u32],
output: &'out mut [u64],
) -> Result<&'out [u64], Self::Error> {
let result = self.decode64(input, output)?;
Ok(&*result)
}
}

/// Trait for codecs that support 64-bit integer compression.
///
/// Only certain codecs support 64-bit integers. These are marked with the `@ 64`
Expand Down Expand Up @@ -420,4 +467,22 @@ mod tests {

assert_eq!(decoded, input);
}

#[test]
fn supports_compress_to_slice() {
let mut cpp_codec = FastPFor128Codec::new();
let data = vec![1, 2, 3, 4, 5];
let mut compressed = vec![0u32; data.len() * 4];

let compressed_len = {
let result = cpp_codec.compress_to_slice(&data, &mut compressed).unwrap();
result.len()
};

let mut decompressed = vec![0u32; data.len()];
let result = cpp_codec
.decompress_to_slice(&compressed[..compressed_len], &mut decompressed)
.unwrap();
assert_eq!(result, &data[..]);
}
}
36 changes: 36 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,39 @@ pub mod cpp;
#[cfg(feature = "rust")]
/// Rust re-implementation of `FastPFor` (work in progress)
pub mod rust;

/// Low-level compression interface using caller-provided buffers.
///
/// Codecs write into pre-allocated slices and return a sub-slice showing exactly
/// what was written. Works across FFI boundaries and allows buffer reuse.
///
/// # Type Parameters
///
/// - `In`: Input data type (e.g., `u32` or `u64` for integer codecs)
/// - `Out`: Compressed output type (defaults to `In`, but may differ - e.g.,
/// 64-bit integers compress to 32-bit words: `CodecToSlice<u64, u32>`)
///
/// # Buffer Sizing
///
/// Caller must ensure output buffers are large enough. For compression, estimate
/// `input.len() * 2 + 1024`. For decompression, size depends on the codec.
pub trait CodecToSlice<In, Out = In> {
/// Error type returned by compression/decompression operations.
type Error;

/// Compresses input into output buffer, returning slice of data written.
fn compress_to_slice<'out>(
&mut self,
input: &[In],
output: &'out mut [Out],
) -> Result<&'out [Out], Self::Error>;

/// Decompresses input into output buffer, returning slice of data written.
///
/// Output size cannot be known in advance for some codecs (e.g., RLE).
fn decompress_to_slice<'out>(
&mut self,
input: &[Out],
output: &'out mut [In],
) -> Result<&'out [In], Self::Error>;
}
118 changes: 117 additions & 1 deletion src/rust/integer_compression/codec.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use crate::rust::{FastPFOR, JustCopy, VariableByte};
use std::io::Cursor;

use crate::rust::{FastPFOR, FastPForResult, Integer, JustCopy, VariableByte};
use crate::CodecToSlice;

/// Type-erased wrapper for compression codecs.
///
Expand All @@ -12,6 +15,94 @@ pub enum Codec {
JustCopy(JustCopy),
}

impl Integer<u32> for Codec {
fn compress(
&mut self,
input: &[u32],
input_length: u32,
input_offset: &mut Cursor<u32>,
output: &mut [u32],
output_offset: &mut Cursor<u32>,
) -> FastPForResult<()> {
match self {
Codec::FastPFor(fastpfor) => {
fastpfor.compress(input, input_length, input_offset, output, output_offset)
}
Codec::VariableByte(vb) => {
vb.compress(input, input_length, input_offset, output, output_offset)
}
Codec::JustCopy(jc) => {
jc.compress(input, input_length, input_offset, output, output_offset)
}
}
}

fn uncompress(
&mut self,
input: &[u32],
input_length: u32,
input_offset: &mut Cursor<u32>,
output: &mut [u32],
output_offset: &mut Cursor<u32>,
) -> FastPForResult<()> {
match self {
Codec::FastPFor(fastpfor) => {
fastpfor.uncompress(input, input_length, input_offset, output, output_offset)
}
Codec::VariableByte(vb) => {
vb.uncompress(input, input_length, input_offset, output, output_offset)
}
Codec::JustCopy(jc) => {
jc.uncompress(input, input_length, input_offset, output, output_offset)
}
}
}
}

impl CodecToSlice<u32> for Codec {
type Error = crate::rust::FastPForError;

fn compress_to_slice<'out>(
&mut self,
input: &[u32],
output: &'out mut [u32],
) -> Result<&'out [u32], Self::Error> {
let mut input_offset = Cursor::new(0);
let mut output_offset = Cursor::new(0);

self.compress(
input,
input.len() as u32,
Comment thread
CommanderStorm marked this conversation as resolved.
Outdated
&mut input_offset,
output,
&mut output_offset,
)?;

let written = output_offset.position() as usize;
Ok(&output[..written])
}

fn decompress_to_slice<'out>(
&mut self,
input: &[u32],
output: &'out mut [u32],
) -> Result<&'out [u32], Self::Error> {
let mut input_offset = Cursor::new(0);
let mut output_offset = Cursor::new(0);

self.uncompress(
input,
input.len() as u32,
Comment thread
CommanderStorm marked this conversation as resolved.
Outdated
&mut input_offset,
output,
&mut output_offset,
)?;

let written = output_offset.position() as usize;
Ok(&output[..written])
}
}

impl From<FastPFOR> for Codec {
fn from(fastpfor: FastPFOR) -> Self {
Codec::FastPFor(Box::new(fastpfor))
Expand All @@ -29,3 +120,28 @@ impl From<JustCopy> for Codec {
Codec::JustCopy(jc)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn supports_compress_to_slice() {
let data = vec![1, 2, 3, 4, 5];
let mut rust_codec = Codec::from(VariableByte::new());
let mut compressed = vec![0u32; data.len() * 4];

let compressed_len = {
let result = rust_codec
.compress_to_slice(&data, &mut compressed)
.unwrap();
result.len()
};

let mut decompressed = vec![0u32; data.len()];
let result = rust_codec
.decompress_to_slice(&compressed[..compressed_len], &mut decompressed)
.unwrap();
assert_eq!(result, &data[..]);
}
}
46 changes: 1 addition & 45 deletions src/rust/integer_compression/integer_codec.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::io::Cursor;

use crate::rust::{Codec, FastPForResult};
use crate::rust::FastPForResult;

/// Integer compression/decompression interface with length headers.
///
Expand Down Expand Up @@ -37,47 +37,3 @@ pub trait Integer<T> {
output_offset: &mut Cursor<u32>,
) -> FastPForResult<()>;
}

impl Integer<u32> for Codec {
fn compress(
&mut self,
input: &[u32],
input_length: u32,
input_offset: &mut Cursor<u32>,
output: &mut [u32],
output_offset: &mut Cursor<u32>,
) -> FastPForResult<()> {
match self {
Codec::FastPFor(fastpfor) => {
fastpfor.compress(input, input_length, input_offset, output, output_offset)
}
Codec::VariableByte(vb) => {
vb.compress(input, input_length, input_offset, output, output_offset)
}
Codec::JustCopy(jc) => {
jc.compress(input, input_length, input_offset, output, output_offset)
}
}
}

fn uncompress(
&mut self,
input: &[u32],
input_length: u32,
input_offset: &mut Cursor<u32>,
output: &mut [u32],
output_offset: &mut Cursor<u32>,
) -> FastPForResult<()> {
match self {
Codec::FastPFor(fastpfor) => {
fastpfor.uncompress(input, input_length, input_offset, output, output_offset)
}
Codec::VariableByte(vb) => {
vb.uncompress(input, input_length, input_offset, output, output_offset)
}
Codec::JustCopy(jc) => {
jc.uncompress(input, input_length, input_offset, output, output_offset)
}
}
}
}