pub const POOL_CAPACITY: usize = 64000;
pub const ROLLBACK_FRAMES: usize = 120;
#[repr(C, packed)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct WavDiff {
pub channel: i32, pub frame_offset: i32, pub param_id: i32, pub index: u16, pub old_val: u16, pub new_val: u16, }
const _: () = assert!(core::mem::size_of::<WavDiff>() == 18);
impl WavDiff {
pub fn coords(&self) -> (i32, i32, i32) {
(self.channel, self.frame_offset, self.param_id)
}
}
impl Default for WavDiff {
fn default() -> Self {
Self {
channel: 0,
frame_offset: 0,
param_id: 0,
index: 0,
old_val: 0,
new_val: 0,
}
}
}
pub struct DiffPool {
buffer: Box<[WavDiff; POOL_CAPACITY]>, head: usize,
count: usize,
}
impl DiffPool {
pub fn new() -> Self {
let buffer = vec![WavDiff::default(); POOL_CAPACITY]
.into_boxed_slice()
.try_into()
.unwrap_or_else(|_| unreachable!());
Self {
buffer,
head: 0,
count: 0,
}
}
pub fn push(&mut self, diff: WavDiff) -> u32 {
let idx = self.head;
self.buffer[idx] = diff;
self.head = (self.head + 1) % POOL_CAPACITY;
if self.count < POOL_CAPACITY {
self.count += 1;
}
idx as u32
}
pub fn get(&self, index: u32) -> Option<&WavDiff> {
let idx = index as usize;
if idx < POOL_CAPACITY {
Some(&self.buffer[idx])
} else {
None
}
}
pub fn head(&self) -> usize {
self.head
}
pub fn count(&self) -> usize {
self.count
}
pub fn frame_diffs(&self, start: u32, count: u16) -> FrameDiffIter<'_> {
FrameDiffIter {
pool: self,
current: start as usize,
remaining: count as usize,
}
}
pub fn frame_diffs_reverse(&self, start: u32, count: u16) -> FrameDiffReverseIter<'_> {
let c = count as usize;
let last = if c == 0 {
start as usize
} else {
(start as usize + c - 1) % POOL_CAPACITY
};
FrameDiffReverseIter {
pool: self,
current: last,
remaining: c,
}
}
}
pub struct FrameDiffIter<'a> {
pool: &'a DiffPool,
current: usize,
remaining: usize,
}
impl<'a> Iterator for FrameDiffIter<'a> {
type Item = &'a WavDiff;
fn next(&mut self) -> Option<Self::Item> {
if self.remaining == 0 {
return None;
}
let diff = &self.pool.buffer[self.current];
self.current = (self.current + 1) % POOL_CAPACITY;
self.remaining -= 1;
Some(diff)
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.remaining, Some(self.remaining))
}
}
pub struct FrameDiffReverseIter<'a> {
pool: &'a DiffPool,
current: usize,
remaining: usize,
}
impl<'a> Iterator for FrameDiffReverseIter<'a> {
type Item = &'a WavDiff;
fn next(&mut self) -> Option<Self::Item> {
if self.remaining == 0 {
return None;
}
let diff = &self.pool.buffer[self.current];
if self.current == 0 {
self.current = POOL_CAPACITY - 1;
} else {
self.current -= 1;
}
self.remaining -= 1;
Some(diff)
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.remaining, Some(self.remaining))
}
}
#[derive(Clone, Copy, Debug)]
pub struct FrameSnapshot {
pub tick: u64,
pub inputs: u16,
pub diff_start: u32,
pub diff_count: u16,
pub checksum: [u8; 32],
}
impl Default for FrameSnapshot {
fn default() -> Self {
Self {
tick: 0,
inputs: 0,
diff_start: 0,
diff_count: 0,
checksum: [0u8; 32],
}
}
}
pub struct RollbackBuffer {
frames: [FrameSnapshot; ROLLBACK_FRAMES],
head: usize,
count: usize,
}
impl RollbackBuffer {
pub fn new() -> Self {
Self {
frames: [FrameSnapshot::default(); ROLLBACK_FRAMES],
head: 0,
count: 0,
}
}
pub fn push(&mut self, snapshot: FrameSnapshot) {
self.frames[self.head] = snapshot;
self.head = (self.head + 1) % ROLLBACK_FRAMES;
if self.count < ROLLBACK_FRAMES {
self.count += 1;
}
}
pub fn find_by_tick(&self, tick: u64) -> Option<&FrameSnapshot> {
for i in 0..self.count {
let idx = if self.head == 0 {
ROLLBACK_FRAMES - 1 - i
} else {
(self.head + ROLLBACK_FRAMES - 1 - i) % ROLLBACK_FRAMES
};
if self.frames[idx].tick == tick {
return Some(&self.frames[idx]);
}
}
None
}
pub fn latest(&self) -> Option<&FrameSnapshot> {
if self.count == 0 {
return None;
}
let idx = if self.head == 0 {
ROLLBACK_FRAMES - 1
} else {
self.head - 1
};
Some(&self.frames[idx])
}
pub fn count(&self) -> usize {
self.count
}
pub fn frames_forward(&self, start_tick: u64, end_tick: u64) -> FrameRangeIter<'_> {
let oldest_idx = if self.count < ROLLBACK_FRAMES {
0
} else {
self.head
};
FrameRangeIter {
buffer: self,
pos: 0,
oldest_idx,
start_tick,
end_tick,
}
}
pub fn frames_reverse(&self, start_tick: u64, end_tick: u64) -> FrameRangeReverseIter<'_> {
FrameRangeReverseIter {
buffer: self,
pos: 0,
start_tick,
end_tick,
}
}
}
pub struct FrameRangeIter<'a> {
buffer: &'a RollbackBuffer,
pos: usize,
oldest_idx: usize,
start_tick: u64,
end_tick: u64,
}
impl<'a> Iterator for FrameRangeIter<'a> {
type Item = &'a FrameSnapshot;
fn next(&mut self) -> Option<Self::Item> {
while self.pos < self.buffer.count {
let idx = (self.oldest_idx + self.pos) % ROLLBACK_FRAMES;
self.pos += 1;
let frame = &self.buffer.frames[idx];
if frame.tick >= self.start_tick && frame.tick <= self.end_tick {
return Some(frame);
}
}
None
}
}
pub struct FrameRangeReverseIter<'a> {
buffer: &'a RollbackBuffer,
pos: usize,
start_tick: u64,
end_tick: u64,
}
impl<'a> Iterator for FrameRangeReverseIter<'a> {
type Item = &'a FrameSnapshot;
fn next(&mut self) -> Option<Self::Item> {
while self.pos < self.buffer.count {
let idx = if self.buffer.head == 0 {
ROLLBACK_FRAMES - 1 - self.pos
} else {
(self.buffer.head + ROLLBACK_FRAMES - 1 - self.pos) % ROLLBACK_FRAMES
};
self.pos += 1;
let frame = &self.buffer.frames[idx];
if frame.tick >= self.start_tick && frame.tick <= self.end_tick {
return Some(frame);
}
}
None
}
}
pub fn rewind(
pool: &DiffPool,
rollback: &RollbackBuffer,
start_tick: u64,
end_tick: u64,
mut apply_fn: impl FnMut((i32, i32, i32), u16, u16),
) {
for frame in rollback.frames_reverse(start_tick, end_tick) {
for diff in pool.frame_diffs_reverse(frame.diff_start, frame.diff_count) {
apply_fn(diff.coords(), diff.index, diff.old_val);
}
}
}
pub fn replay(
pool: &DiffPool,
rollback: &RollbackBuffer,
start_tick: u64,
end_tick: u64,
mut apply_fn: impl FnMut((i32, i32, i32), u16, u16),
) {
for frame in rollback.frames_forward(start_tick, end_tick) {
for diff in pool.frame_diffs(frame.diff_start, frame.diff_count) {
apply_fn(diff.coords(), diff.index, diff.new_val);
}
}
}
pub fn compute_tick_checksum(pool: &DiffPool, diff_start: u32, diff_count: u16) -> [u8; 32] {
let mut hasher = blake3::Hasher::new();
for diff in pool.frame_diffs(diff_start, diff_count) {
let bytes: &[u8] = unsafe {
core::slice::from_raw_parts(
diff as *const WavDiff as *const u8,
core::mem::size_of::<WavDiff>(),
)
};
hasher.update(bytes);
}
*hasher.finalize().as_bytes()
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
use std::collections::HashMap;
fn arb_wav_diff() -> impl Strategy<Value = WavDiff> {
(
-8i32..=8i32,
-8i32..=8i32,
-8i32..=8i32,
0u16..=255u16,
0u16..=31u16,
0u16..=31u16,
)
.prop_map(|(cx, cy, cz, idx, old, new)| WavDiff {
channel: cx,
frame_offset: cy,
param_id: cz,
index: idx,
old_val: old,
new_val: new,
})
}
fn arb_diff_sequence() -> impl Strategy<Value = Vec<Vec<WavDiff>>> {
proptest::collection::vec(
proptest::collection::vec(arb_wav_diff(), 1..=10),
1..=20,
)
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(64))]
#[test]
fn prop_cp6_rollback_equivalence(frames_diffs in arb_diff_sequence()) {
let mut pool = DiffPool::new();
let mut rollback = RollbackBuffer::new();
let mut grid: HashMap<((i32, i32, i32), u16), u16> = HashMap::new();
let start_tick = 1u64;
for (frame_idx, frame_diffs) in frames_diffs.iter().enumerate() {
let tick = start_tick + frame_idx as u64;
let diff_start = pool.head() as u32;
let mut diff_count = 0u16;
for diff in frame_diffs {
let key = (diff.coords(), diff.index);
let current = grid.get(&key).copied().unwrap_or(0);
let actual_diff = WavDiff {
channel: diff.channel,
frame_offset: diff.frame_offset,
param_id: diff.param_id,
index: diff.index,
old_val: current,
new_val: diff.new_val,
};
pool.push(actual_diff);
grid.insert(key, diff.new_val);
diff_count += 1;
}
rollback.push(FrameSnapshot {
tick,
inputs: 0,
diff_start,
diff_count,
checksum: [0u8; 32],
});
}
let final_state = grid.clone();
let end_tick = start_tick + frames_diffs.len() as u64 - 1;
rewind(&pool, &rollback, start_tick, end_tick, |coords, index, val| {
grid.insert((coords, index), val);
});
let rewound_state = grid.clone();
for key in final_state.keys() {
let val = rewound_state.get(key).copied().unwrap_or(0);
prop_assert_eq!(val, 0,
"After rewind, key {:?} should be 0 (initial), got {}", key, val);
}
replay(&pool, &rollback, start_tick, end_tick, |coords, index, val| {
grid.insert((coords, index), val);
});
for (key, expected_mat) in &final_state {
let actual = grid.get(key).copied().unwrap_or(0);
prop_assert_eq!(actual, *expected_mat,
"After replay, key {:?} should be {}, got {}", key, expected_mat, actual);
}
}
}
#[test]
fn test_wav_diff_is_18_bytes() {
assert_eq!(core::mem::size_of::<WavDiff>(), 18);
}
#[test]
fn test_diffpool_wraparound() {
let mut pool = DiffPool::new();
for i in 0..POOL_CAPACITY {
pool.push(WavDiff {
channel: 0,
frame_offset: 0,
param_id: 0,
index: (i % 65536) as u16,
old_val: 0,
new_val: 1,
});
}
assert_eq!(pool.count(), POOL_CAPACITY);
assert_eq!(pool.head(), 0);
pool.push(WavDiff {
channel: 99,
frame_offset: 0,
param_id: 0,
index: 42,
old_val: 5,
new_val: 10,
});
assert_eq!(pool.count(), POOL_CAPACITY); let d = pool.get(0).unwrap();
let cx = { d.channel };
let idx = { d.index };
assert_eq!(cx, 99);
assert_eq!(idx, 42);
}
#[test]
fn test_rollback_buffer_cap() {
let mut rb = RollbackBuffer::new();
for i in 0..150u64 {
rb.push(FrameSnapshot {
tick: i,
inputs: 0,
diff_start: 0,
diff_count: 0,
checksum: [0u8; 32],
});
}
assert_eq!(rb.count(), ROLLBACK_FRAMES);
assert!(rb.find_by_tick(29).is_none());
assert!(rb.find_by_tick(30).is_some());
assert!(rb.find_by_tick(149).is_some());
}
}