use crate::alloc_prelude::*;
pub(crate) const NUM_SOLVER_COLORS: usize = 129;
const NUM_BUCKETS: usize = NUM_SOLVER_COLORS;
pub(crate) const GENERIC_BUCKET: u16 = NUM_BUCKETS as u16;
const _: () = assert!(NUM_BUCKETS < 1 << (32 - GraphPos::BUCKET_SHIFT));
#[cfg_attr(not(feature = "parallel"), allow(dead_code))] pub(crate) const NUM_BUCKETS_WITH_GENERIC: usize = NUM_BUCKETS + 1;
#[inline]
pub(crate) fn bucket_id(color: u8) -> u16 {
color as u16
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
pub(crate) struct ContactRef {
pub edge: u32,
pub manifold: u32,
}
impl ContactRef {
pub(crate) const PADDING: ContactRef = ContactRef {
edge: u32::MAX,
manifold: u32::MAX,
};
#[inline]
pub(crate) fn is_padding(self) -> bool {
self.edge == u32::MAX
}
}
impl Default for ContactRef {
fn default() -> Self {
Self::PADDING
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
pub(crate) struct GraphPos(u32);
impl GraphPos {
pub(crate) const NONE: GraphPos = GraphPos(u32::MAX);
const BUCKET_SHIFT: u32 = 22;
const LOCAL_MASK: u32 = (1 << Self::BUCKET_SHIFT) - 1;
#[inline]
pub(crate) fn new(bucket: u16, local: u32) -> Self {
debug_assert!(local < (1 << Self::BUCKET_SHIFT));
debug_assert!((bucket as u32) < (1 << (32 - Self::BUCKET_SHIFT)));
let pos = GraphPos(((bucket as u32) << Self::BUCKET_SHIFT) | local);
debug_assert!(pos.is_some());
pos
}
#[inline]
pub(crate) fn is_some(self) -> bool {
self.0 != u32::MAX
}
#[inline]
pub(crate) fn bucket(self) -> u16 {
(self.0 >> Self::BUCKET_SHIFT) as u16
}
#[inline]
pub(crate) fn local(self) -> u32 {
self.0 & Self::LOCAL_MASK
}
}
impl Default for GraphPos {
fn default() -> Self {
GraphPos::NONE
}
}
#[derive(Clone, Default)]
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
pub(crate) struct SolverContactGraph {
buckets: Vec<Vec<ContactRef>>,
}
impl SolverContactGraph {
pub(crate) fn new() -> Self {
Self {
buckets: alloc::vec![Vec::new(); NUM_BUCKETS + 1],
}
}
#[cfg(not(feature = "parallel"))]
pub(crate) fn clear(&mut self) {
if self.buckets.is_empty() {
self.buckets.resize_with(NUM_BUCKETS + 1, Vec::new);
}
for b in &mut self.buckets {
b.clear();
}
}
#[inline]
pub(crate) fn insert(&mut self, color: u8, contact: ContactRef) -> GraphPos {
let bucket = bucket_id(color);
let arr = &mut self.buckets[bucket as usize];
let local = arr.len() as u32;
arr.push(contact);
GraphPos::new(bucket, local)
}
#[inline]
pub(crate) fn insert_generic(&mut self, contact: ContactRef) -> GraphPos {
let arr = &mut self.buckets[GENERIC_BUCKET as usize];
let local = arr.len() as u32;
arr.push(contact);
GraphPos::new(GENERIC_BUCKET, local)
}
#[cfg(feature = "parallel")]
pub(crate) fn resize_for_bulk_rebuild(&mut self, lens: &[u32]) -> Vec<*mut ContactRef> {
if self.buckets.is_empty() {
self.buckets.resize_with(NUM_BUCKETS_WITH_GENERIC, Vec::new);
}
debug_assert_eq!(lens.len(), self.buckets.len());
self.buckets
.iter_mut()
.zip(lens.iter())
.map(|(b, len)| {
b.clear();
b.resize(*len as usize, ContactRef::PADDING);
b.as_mut_ptr()
})
.collect()
}
#[inline]
pub(crate) fn rewrite_edge(&mut self, pos: GraphPos, new_edge: u32) {
debug_assert!(pos.is_some());
self.buckets[pos.bucket() as usize][pos.local() as usize].edge = new_edge;
}
#[inline]
pub(crate) fn remove(&mut self, pos: GraphPos) -> Option<ContactRef> {
debug_assert!(pos.is_some());
let arr = &mut self.buckets[pos.bucket() as usize];
let local = pos.local() as usize;
let last = arr.len() - 1;
arr.swap_remove(local);
if local != last {
Some(arr[local])
} else {
None
}
}
#[cfg_attr(not(feature = "parallel"), allow(dead_code))]
pub(crate) fn len(&self) -> usize {
self.buckets.iter().map(|b| b.len()).sum()
}
pub(crate) fn buckets(&self) -> impl Iterator<Item = (u8, &[ContactRef])> {
self.buckets[..NUM_BUCKETS]
.iter()
.enumerate()
.filter_map(|(id, arr)| {
if arr.is_empty() {
None
} else {
Some((id as u8, arr.as_slice()))
}
})
}
pub(crate) fn generic(&self) -> &[ContactRef] {
&self.buckets[GENERIC_BUCKET as usize]
}
}
#[cfg(test)]
mod test {
use super::*;
fn cref(edge: u32) -> ContactRef {
ContactRef { edge, manifold: 0 }
}
#[test]
fn insert_remove_backref_consistency() {
let mut g = SolverContactGraph::new();
let mut pos: alloc::collections::BTreeMap<u32, GraphPos> = Default::default();
for e in 0..200u32 {
let color = (e % 5) as u8;
let p = g.insert(color, cref(e));
pos.insert(e, p);
}
assert_eq!(g.len(), 200);
let to_remove: alloc::vec::Vec<u32> = (0..200u32).step_by(3).collect();
for &e in &to_remove {
let p = pos.remove(&e).unwrap();
if let Some(moved) = g.remove(p) {
*pos.get_mut(&moved.edge).unwrap() = p;
}
}
assert_eq!(g.len(), 200 - to_remove.len());
for (color, arr) in g.buckets() {
for (local, c) in arr.iter().enumerate() {
assert_eq!((c.edge % 5) as u8, color);
let tracked = pos[&c.edge];
assert_eq!(tracked.bucket(), bucket_id(color));
assert_eq!(tracked.local() as usize, local);
assert!(!to_remove.contains(&c.edge));
}
}
}
#[test]
fn high_bucket_ids_roundtrip() {
let mut g = SolverContactGraph::new();
let overflow = (NUM_SOLVER_COLORS - 1) as u8; let p = g.insert(overflow, cref(7));
assert_eq!(p.bucket(), bucket_id(overflow));
let _ = g.insert(0, cref(1));
assert!(g.remove(p).is_none());
let remaining: alloc::vec::Vec<_> = g.buckets().map(|(c, arr)| (c, arr.len())).collect();
assert_eq!(remaining, alloc::vec![(0, 1)]);
let pg = g.insert_generic(cref(9));
assert_eq!(pg.bucket(), GENERIC_BUCKET);
assert_eq!(g.generic().len(), 1);
assert!(g.remove(pg).is_none());
assert!(g.generic().is_empty());
}
#[test]
fn move_between_color_buckets() {
let mut g = SolverContactGraph::new();
let mut pos: alloc::collections::BTreeMap<u32, GraphPos> = Default::default();
for e in 0..10u32 {
pos.insert(e, g.insert(3, cref(e)));
}
let p = pos[&4];
if let Some(moved) = g.remove(p) {
*pos.get_mut(&moved.edge).unwrap() = p;
}
pos.insert(4, g.insert(7, cref(4)));
assert_eq!(g.len(), 10);
for (color, arr) in g.buckets() {
for (local, c) in arr.iter().enumerate() {
assert_eq!(pos[&c.edge].local() as usize, local);
if c.edge == 4 {
assert_eq!(color, 7);
} else {
assert_eq!(color, 3);
}
}
}
}
}