use bitvec::prelude::*;
pub struct PieceMap {
piece_size: u64,
total_size: u64,
piece_count: usize,
completed: BitVec<u8, Lsb0>,
completed_pieces: usize,
completed_bytes: u64,
next_candidate: usize,
highest_completed_piece: Option<usize>,
}
impl PieceMap {
pub fn new(total_size: u64, piece_size: u64) -> Self {
let piece_count = total_size.div_ceil(piece_size) as usize;
Self {
piece_size,
total_size,
piece_count,
completed: bitvec![u8, Lsb0; 0; piece_count],
completed_pieces: 0,
completed_bytes: 0,
next_candidate: 0,
highest_completed_piece: None,
}
}
pub fn from_bitset(
total_size: u64,
piece_size: u64,
bitset: &[u8],
piece_count: usize,
) -> Self {
let mut completed = BitVec::<u8, Lsb0>::from_slice(bitset);
completed.resize(piece_count, false);
let mut completed_pieces = 0usize;
let mut completed_bytes = 0u64;
let mut next_candidate = piece_count;
let mut highest_completed_piece = None;
for piece_id in 0..piece_count {
if completed[piece_id] {
completed_pieces += 1;
completed_bytes += piece_len(total_size, piece_size, piece_id);
highest_completed_piece = Some(piece_id);
} else if next_candidate == piece_count {
next_candidate = piece_id;
}
}
Self {
piece_size,
total_size,
piece_count,
completed,
completed_pieces,
completed_bytes,
next_candidate,
highest_completed_piece,
}
}
pub fn piece_size(&self) -> u64 {
self.piece_size
}
#[allow(dead_code)]
pub fn total_size(&self) -> u64 {
self.total_size
}
pub fn piece_count(&self) -> usize {
self.piece_count
}
pub fn piece_range(&self, piece_id: usize) -> (u64, u64) {
let start = piece_id as u64 * self.piece_size;
let end = start + piece_len(self.total_size, self.piece_size, piece_id);
(start, end)
}
#[allow(dead_code)]
pub fn is_complete(&self, piece_id: usize) -> bool {
piece_id < self.piece_count && self.completed[piece_id]
}
pub fn mark_complete(&mut self, piece_id: usize) {
if piece_id < self.piece_count && !self.completed[piece_id] {
self.completed.set(piece_id, true);
self.completed_pieces += 1;
self.completed_bytes += piece_len(self.total_size, self.piece_size, piece_id);
self.highest_completed_piece = Some(
self.highest_completed_piece
.map_or(piece_id, |current| current.max(piece_id)),
);
if piece_id == self.next_candidate {
self.advance_next_candidate();
}
}
}
pub fn first_missing(&self) -> Option<usize> {
(self.next_candidate < self.piece_count).then_some(self.next_candidate)
}
pub fn highest_completed_piece(&self) -> Option<usize> {
self.highest_completed_piece
}
pub fn all_done(&self) -> bool {
self.completed.all()
}
pub fn completed_count(&self) -> usize {
self.completed_pieces
}
pub fn completed_bytes(&self) -> u64 {
self.completed_bytes
}
pub fn remaining_count(&self) -> usize {
self.piece_count - self.completed_pieces
}
pub fn to_bitset_bytes(&self) -> Vec<u8> {
self.completed.as_raw_slice().to_vec()
}
fn advance_next_candidate(&mut self) {
while self.next_candidate < self.piece_count && self.completed[self.next_candidate] {
self.next_candidate += 1;
}
}
}
fn piece_len(total_size: u64, piece_size: u64, piece_id: usize) -> u64 {
let start = piece_id as u64 * piece_size;
((piece_id as u64 + 1) * piece_size).min(total_size) - start
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_piece_map_basic() {
let mut pm = PieceMap::new(10_000_000, 1_000_000); assert_eq!(pm.piece_count(), 10);
assert!(!pm.all_done());
assert_eq!(pm.completed_bytes(), 0);
pm.mark_complete(0);
pm.mark_complete(5);
assert!(pm.is_complete(0));
assert!(!pm.is_complete(1));
assert!(pm.is_complete(5));
assert_eq!(pm.completed_count(), 2);
assert_eq!(pm.completed_bytes(), 2_000_000);
}
#[test]
fn test_piece_range_last_piece() {
let pm = PieceMap::new(2_500_000, 1_000_000); assert_eq!(pm.piece_count(), 3);
assert_eq!(pm.piece_range(0), (0, 1_000_000));
assert_eq!(pm.piece_range(1), (1_000_000, 2_000_000));
assert_eq!(pm.piece_range(2), (2_000_000, 2_500_000)); }
#[test]
fn test_first_missing_tracks_next_gap() {
let mut pm = PieceMap::new(5_000_000, 1_000_000);
pm.mark_complete(0);
pm.mark_complete(2);
assert_eq!(pm.first_missing(), Some(1));
pm.mark_complete(1);
assert_eq!(pm.first_missing(), Some(3));
}
#[test]
fn test_bitset_roundtrip() {
let mut pm = PieceMap::new(5_000_000, 1_000_000);
pm.mark_complete(0);
pm.mark_complete(3);
let bytes = pm.to_bitset_bytes();
let pm2 = PieceMap::from_bitset(5_000_000, 1_000_000, &bytes, 5);
assert!(pm2.is_complete(0));
assert!(!pm2.is_complete(1));
assert!(!pm2.is_complete(2));
assert!(pm2.is_complete(3));
assert!(!pm2.is_complete(4));
}
#[test]
fn test_all_done() {
let mut pm = PieceMap::new(2_000_000, 1_000_000);
assert!(!pm.all_done());
pm.mark_complete(0);
assert!(!pm.all_done());
pm.mark_complete(1);
assert!(pm.all_done());
}
#[test]
fn test_total_size_and_accessors() {
let pm = PieceMap::new(2_500_000, 1_000_000);
assert_eq!(pm.total_size(), 2_500_000);
assert_eq!(pm.piece_size(), 1_000_000);
assert_eq!(pm.piece_count(), 3);
assert_eq!(pm.remaining_count(), 3);
assert_eq!(pm.completed_count(), 0);
}
#[test]
fn test_mark_complete_out_of_bounds() {
let mut pm = PieceMap::new(1_000_000, 1_000_000);
pm.mark_complete(999);
assert!(!pm.is_complete(999));
}
#[test]
fn test_is_complete_out_of_bounds() {
let pm = PieceMap::new(1_000_000, 1_000_000);
assert!(!pm.is_complete(999));
}
#[test]
fn test_completed_bytes_partial() {
let mut pm = PieceMap::new(2_500_000, 1_000_000);
pm.mark_complete(2); assert_eq!(pm.completed_bytes(), 500_000);
}
#[test]
fn test_highest_completed_piece_updates() {
let mut pm = PieceMap::new(4_500_000, 1_000_000);
assert_eq!(pm.highest_completed_piece(), None);
pm.mark_complete(1);
assert_eq!(pm.highest_completed_piece(), Some(1));
pm.mark_complete(3);
assert_eq!(pm.highest_completed_piece(), Some(3));
}
#[test]
fn test_from_bitset_restores_counters() {
let bytes = vec![0b0000_0101];
let pm = PieceMap::from_bitset(3_500_000, 1_000_000, &bytes, 4);
assert_eq!(pm.completed_count(), 2);
assert_eq!(pm.completed_bytes(), 2_000_000);
assert_eq!(pm.first_missing(), Some(1));
assert_eq!(pm.highest_completed_piece(), Some(2));
}
}