use super::FinalizeOptions;
use super::finalize::{Analysis, finalize_into};
use super::samples::SampleSet;
use crate::encoding::{CompressionLevel, EncoderDictionary, FrameCompressor};
use core::ops::Range;
use std::{io, vec::Vec};
const SHRINK_START: usize = 256;
pub(super) struct Scored {
pub(super) dict: Vec<u8>,
pub(super) total: usize,
}
pub(super) struct Evaluator<'s> {
samples: &'s SampleSet<'s>,
finalize_samples: usize,
scoring: Range<usize>,
capacity: usize,
finalize: FinalizeOptions,
compressor: Option<FrameCompressor>,
frame: Vec<u8>,
dict: Vec<u8>,
shrunk: Vec<u8>,
analysis: Analysis<'s>,
}
impl<'s> Evaluator<'s> {
pub(super) fn new(
samples: &'s SampleSet<'s>,
finalize_samples: usize,
scoring: Range<usize>,
capacity: usize,
finalize: FinalizeOptions,
) -> Self {
Self {
samples,
finalize_samples,
scoring,
capacity,
finalize,
compressor: None,
frame: Vec::new(),
dict: Vec::new(),
shrunk: Vec::new(),
analysis: Analysis::default(),
}
}
pub(super) fn finalize(&mut self, content: &[u8]) -> io::Result<Vec<u8>> {
let mut out = Vec::new();
self.finalize_into(content, &mut out)?;
Ok(out)
}
fn finalize_into(&mut self, content: &[u8], out: &mut Vec<u8>) -> io::Result<()> {
finalize_into(
out,
&mut self.analysis,
content,
self.samples,
self.finalize_samples,
self.capacity,
self.finalize,
)
}
fn finalize_and_price(&mut self, content: &[u8], buffer: &mut Vec<u8>) -> io::Result<usize> {
self.finalize_into(content, buffer)?;
self.price(buffer)
}
fn price(&mut self, dict: &[u8]) -> io::Result<usize> {
let prepared = EncoderDictionary::from_bytes(dict)
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
let level = self.finalize.level;
let compressor = self
.compressor
.get_or_insert_with(|| FrameCompressor::new(CompressionLevel::from_level(level)));
compressor
.set_encoder_dictionary(prepared)
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
let mut total = dict.len();
for index in self.scoring.clone() {
compressor.compress_independent_frame_into(self.samples.sample(index), &mut self.frame);
total += self.frame.len();
}
Ok(total)
}
pub(super) fn score(&mut self, content: &[u8]) -> io::Result<Priced<'_>> {
let mut dict = core::mem::take(&mut self.dict);
let priced = self.finalize_and_price(content, &mut dict);
self.dict = dict;
Ok(Priced {
total: priced?,
dict: &self.dict,
})
}
pub(super) fn shrink(
&mut self,
content: &[u8],
full: Scored,
regression: u32,
) -> io::Result<Scored> {
let mut size = SHRINK_START;
while size < content.len() {
let mut shrunk = core::mem::take(&mut self.shrunk);
let priced = self.finalize_and_price(&content[content.len() - size..], &mut shrunk);
let total = priced?;
if within_regression(total, full.total, regression) {
return Ok(Scored {
dict: shrunk,
total,
});
}
self.shrunk = shrunk;
size *= 2;
}
Ok(full)
}
}
pub(super) fn within_regression(total: usize, full: usize, regression: u32) -> bool {
total as u128 * 100 <= full as u128 * (u128::from(regression) + 100)
}
pub(super) struct Priced<'e> {
pub(super) dict: &'e [u8],
pub(super) total: usize,
}
#[derive(Clone, Copy)]
pub(super) enum Keep {
Dictionary { shrink: Option<u32> },
Content,
}
impl Keep {
fn content(self) -> bool {
!matches!(self, Self::Dictionary { shrink: None })
}
fn dictionary(self) -> bool {
matches!(self, Self::Dictionary { .. })
}
}
pub(super) struct Best<P> {
found: Option<(Scored, Vec<u8>, P)>,
last_error: Option<io::Error>,
keep: Keep,
}
impl<P> Best<P> {
pub(super) fn new(keep: Keep) -> Self {
Self {
found: None,
last_error: None,
keep,
}
}
pub(super) fn offer(&mut self, candidate: io::Result<Priced<'_>>, content: &[u8], params: P) {
let content = if self.keep.content() { content } else { &[] };
let keep_dictionary = self.keep.dictionary();
match candidate {
Ok(priced) => {
let dict = if keep_dictionary { priced.dict } else { &[] };
match &mut self.found {
Some((best, _, _)) if priced.total >= best.total => {}
Some((best, kept, kept_params)) => {
best.total = priced.total;
best.dict.clear();
best.dict.extend_from_slice(dict);
kept.clear();
kept.extend_from_slice(content);
*kept_params = params;
}
None => {
let best = Scored {
dict: dict.to_vec(),
total: priced.total,
};
self.found = Some((best, content.to_vec(), params));
}
}
}
Err(err) => self.last_error = Some(err),
}
}
pub(super) fn finish(self, evaluator: &mut Evaluator<'_>) -> io::Result<(Vec<u8>, P)> {
let keep = self.keep;
let (full, content, params) = match (self.found, self.last_error) {
(Some(found), _) => found,
(None, Some(err)) => return Err(err),
(None, None) => {
return Err(super::samples::refuse(
super::TrainingError::Parameter,
"no parameter combination is valid for this dictionary size",
));
}
};
match keep {
Keep::Content => Ok((content, params)),
Keep::Dictionary { shrink: None } => Ok((full.dict, params)),
Keep::Dictionary {
shrink: Some(regression),
} => Ok((evaluator.shrink(&content, full, regression)?.dict, params)),
}
}
}