use kevy_bytes::SmallBytes;
#[derive(Clone)]
pub struct SmallSetData {
count: u8,
used: u8,
buf: [u8; SMALL_SET_BUF_CAP],
}
pub(crate) const SMALL_SET_BUF_CAP: usize = 22;
pub(crate) const SMALL_SET_MEMBER_MAX: usize = SMALL_SET_BUF_CAP - 1;
pub(crate) const SMALL_SET_COUNT_MAX: usize = 8;
pub(crate) enum AddResult {
Added,
AlreadyPresent,
NoRoom,
}
impl SmallSetData {
pub(crate) fn new() -> Self {
Self {
count: 0,
used: 0,
buf: [0; SMALL_SET_BUF_CAP],
}
}
pub(crate) fn with_one(member: &[u8]) -> Option<Self> {
if member.len() > SMALL_SET_MEMBER_MAX {
return None;
}
let mut s = Self::new();
s.buf[0] = member.len() as u8;
s.buf[1..1 + member.len()].copy_from_slice(member);
s.count = 1;
s.used = 1 + member.len() as u8;
Some(s)
}
pub fn len(&self) -> usize {
self.count as usize
}
pub fn is_empty(&self) -> bool {
self.count == 0
}
pub fn contains(&self, member: &[u8]) -> bool {
self.iter_slices().any(|m| m == member)
}
pub fn iter_slices(&self) -> SmallSetIter<'_> {
SmallSetIter { buf: &self.buf[..self.used as usize], cursor: 0 }
}
pub fn iter(&self) -> SmallSetIter<'_> {
self.iter_slices()
}
pub(crate) fn try_add(&mut self, member: &[u8]) -> AddResult {
if self.contains(member) {
return AddResult::AlreadyPresent;
}
if member.len() > SMALL_SET_MEMBER_MAX {
return AddResult::NoRoom;
}
if self.count as usize >= SMALL_SET_COUNT_MAX {
return AddResult::NoRoom;
}
let need = 1 + member.len();
let new_used = self.used as usize + need;
if new_used > SMALL_SET_BUF_CAP {
return AddResult::NoRoom;
}
let off = self.used as usize;
self.buf[off] = member.len() as u8;
self.buf[off + 1..off + need].copy_from_slice(member);
self.used = new_used as u8;
self.count += 1;
AddResult::Added
}
pub(crate) fn try_remove(&mut self, member: &[u8]) -> bool {
let mut cursor = 0usize;
let used = self.used as usize;
while cursor < used {
let len = self.buf[cursor] as usize;
let start = cursor + 1;
let end = start + len;
if &self.buf[start..end] == member {
self.buf.copy_within(end..used, cursor);
let shifted = used - end;
let new_used = cursor + shifted;
self.buf[new_used..used].fill(0);
self.used = new_used as u8;
self.count -= 1;
return true;
}
cursor = end;
}
false
}
}
pub struct SmallSetIter<'a> {
buf: &'a [u8],
cursor: usize,
}
impl<'a> Iterator for SmallSetIter<'a> {
type Item = &'a [u8];
fn next(&mut self) -> Option<&'a [u8]> {
if self.cursor >= self.buf.len() {
return None;
}
let len = self.buf[self.cursor] as usize;
let start = self.cursor + 1;
let end = start + len;
self.cursor = end;
Some(&self.buf[start..end])
}
}
pub(crate) fn promote(inline: &SmallSetData) -> crate::value::SetData {
let mut s = crate::value::SetData::with_capacity(inline.len().max(1));
for m in inline.iter_slices() {
s.insert(SmallBytes::from_slice(m));
}
s
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn size_is_24_bytes() {
assert_eq!(std::mem::size_of::<SmallSetData>(), 24);
}
#[test]
fn empty_and_with_one() {
let s = SmallSetData::new();
assert_eq!(s.len(), 0);
assert!(!s.contains(b"foo"));
let s = SmallSetData::with_one(b"hi").unwrap();
assert_eq!(s.len(), 1);
assert!(s.contains(b"hi"));
assert!(!s.contains(b"hj"));
}
#[test]
fn member_too_long_for_with_one() {
let big = vec![b'x'; SMALL_SET_MEMBER_MAX + 1];
assert!(SmallSetData::with_one(&big).is_none());
}
#[test]
fn add_dedup_and_iter() {
let mut s = SmallSetData::new();
assert!(matches!(s.try_add(b"a"), AddResult::Added));
assert!(matches!(s.try_add(b"b"), AddResult::Added));
assert!(matches!(s.try_add(b"a"), AddResult::AlreadyPresent));
assert_eq!(s.len(), 2);
let v: Vec<&[u8]> = s.iter_slices().collect();
assert_eq!(v, vec![b"a".as_slice(), b"b".as_slice()]);
}
#[test]
fn full_buffer_returns_no_room() {
let mut s = SmallSetData::new();
let m1 = b"element:__rand_int__";
assert_eq!(m1.len(), 20);
assert!(matches!(s.try_add(m1), AddResult::Added));
assert!(matches!(s.try_add(b"x"), AddResult::NoRoom));
}
#[test]
fn member_too_long_returns_no_room() {
let mut s = SmallSetData::new();
let big = vec![b'x'; SMALL_SET_MEMBER_MAX + 1];
assert!(matches!(s.try_add(&big), AddResult::NoRoom));
assert_eq!(s.len(), 0);
}
#[test]
fn remove_middle_shifts_tail() {
let mut s = SmallSetData::new();
assert!(matches!(s.try_add(b"aa"), AddResult::Added));
assert!(matches!(s.try_add(b"bbb"), AddResult::Added));
assert!(matches!(s.try_add(b"cc"), AddResult::Added));
assert!(s.try_remove(b"bbb"));
assert_eq!(s.len(), 2);
assert!(s.contains(b"aa"));
assert!(!s.contains(b"bbb"));
assert!(s.contains(b"cc"));
let v: Vec<&[u8]> = s.iter_slices().collect();
assert_eq!(v, vec![b"aa".as_slice(), b"cc".as_slice()]);
}
#[test]
fn remove_absent_returns_false() {
let mut s = SmallSetData::new();
s.try_add(b"a");
assert!(!s.try_remove(b"zz"));
assert_eq!(s.len(), 1);
}
#[test]
fn count_cap_returns_no_room() {
let mut s = SmallSetData::new();
for c in b"abcdefgh" {
assert!(matches!(s.try_add(&[*c]), AddResult::Added));
}
assert_eq!(s.len(), SMALL_SET_COUNT_MAX);
assert!(matches!(s.try_add(b"i"), AddResult::NoRoom));
}
#[test]
fn promote_preserves_members() {
let mut s = SmallSetData::new();
s.try_add(b"a");
s.try_add(b"bb");
s.try_add(b"ccc");
let promoted = promote(&s);
assert_eq!(promoted.len(), 3);
assert!(promoted.contains(b"a".as_slice()));
assert!(promoted.contains(b"bb".as_slice()));
assert!(promoted.contains(b"ccc".as_slice()));
}
}