pub type ShapeFingerprint = [u32; 8];
#[derive(Debug, Clone)]
pub struct ShapeHistory {
entries: [ShapeFingerprint; MAX_HISTORY],
start: usize,
len: usize,
}
pub const MAX_HISTORY: usize = 16;
impl Default for ShapeHistory {
fn default() -> Self {
Self::new()
}
}
impl ShapeHistory {
#[must_use]
pub fn new() -> Self {
Self {
entries: [[0u32; 8]; MAX_HISTORY],
start: 0,
len: 0,
}
}
pub fn record(&mut self, fingerprint: ShapeFingerprint) {
if self.len < MAX_HISTORY {
let idx = (self.start + self.len) % MAX_HISTORY;
self.entries[idx] = fingerprint;
self.len += 1;
} else {
self.entries[self.start] = fingerprint;
self.start = (self.start + 1) % MAX_HISTORY;
}
}
#[must_use]
pub fn latest(&self) -> Option<&ShapeFingerprint> {
if self.len == 0 {
return None;
}
Some(&self.entries[(self.start + self.len - 1) % MAX_HISTORY])
}
#[must_use]
pub fn len(&self) -> usize {
self.len
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len == 0
}
fn get(&self, logical_idx: usize) -> ShapeFingerprint {
debug_assert!(logical_idx < self.len);
self.entries[(self.start + logical_idx) % MAX_HISTORY]
}
#[must_use]
pub fn predict_next(&self) -> Option<ShapeFingerprint> {
let n = self.len;
if n == 0 {
return None;
}
if n >= 2 && self.get(n - 1) == self.get(n - 2) {
return Some(self.get(n - 1));
}
for cycle in 2..=(n / 2) {
let suffix_start = n - cycle;
let prior_start = suffix_start.checked_sub(cycle)?;
let mut matches = true;
for i in 0..cycle {
if self.get(suffix_start + i) != self.get(prior_start + i) {
matches = false;
break;
}
}
if matches {
return Some(self.get(prior_start));
}
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
fn fp(seed: u32) -> ShapeFingerprint {
let mut a = [0u32; 8];
for (i, slot) in a.iter_mut().enumerate() {
*slot = seed.wrapping_mul(31).wrapping_add(i as u32);
}
a
}
#[test]
fn empty_history_predicts_nothing() {
let h = ShapeHistory::new();
assert!(h.predict_next().is_none());
}
#[test]
fn single_entry_history_cannot_predict() {
let mut h = ShapeHistory::new();
h.record(fp(1));
assert!(h.predict_next().is_none());
}
#[test]
fn repeated_fingerprint_predicts_repeat() {
let mut h = ShapeHistory::new();
h.record(fp(1));
h.record(fp(1));
assert_eq!(h.predict_next(), Some(fp(1)));
}
#[test]
fn two_step_cycle_is_predicted() {
let mut h = ShapeHistory::new();
h.record(fp(1));
h.record(fp(2));
h.record(fp(1));
h.record(fp(2));
assert_eq!(h.predict_next(), Some(fp(1)));
}
#[test]
fn three_step_cycle_is_predicted() {
let mut h = ShapeHistory::new();
h.record(fp(1));
h.record(fp(2));
h.record(fp(3));
h.record(fp(1));
h.record(fp(2));
h.record(fp(3));
assert_eq!(h.predict_next(), Some(fp(1)));
}
#[test]
fn no_pattern_means_no_prediction() {
let mut h = ShapeHistory::new();
h.record(fp(1));
h.record(fp(2));
h.record(fp(3));
h.record(fp(4));
assert!(h.predict_next().is_none());
}
#[test]
fn history_caps_at_max_entries() {
let mut h = ShapeHistory::new();
for i in 0..(MAX_HISTORY + 5) {
h.record(fp(i as u32));
}
assert_eq!(h.len(), MAX_HISTORY);
assert_eq!(h.latest(), Some(&fp((MAX_HISTORY + 4) as u32)));
}
}