use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Debug)]
pub struct DirtyMask(AtomicU64);
impl DirtyMask {
pub fn new() -> Self {
Self(AtomicU64::new(0))
}
pub fn all_dirty() -> Self {
Self(AtomicU64::new(u64::MAX))
}
pub fn mark_dirty(&self, bit: u8) {
if bit < 64 {
self.0.fetch_or(1 << bit, Ordering::SeqCst);
}
}
pub fn clear(&self, bit: u8) {
if bit < 64 {
self.0.fetch_and(!(1 << bit), Ordering::SeqCst);
}
}
pub fn clear_all(&self) {
self.0.store(0, Ordering::SeqCst);
}
pub fn is_dirty(&self, bit: u8) -> bool {
if bit >= 64 {
return false;
}
(self.0.load(Ordering::SeqCst) & (1 << bit)) != 0
}
pub fn any_dirty(&self) -> bool {
self.0.load(Ordering::SeqCst) != 0
}
pub fn raw(&self) -> u64 {
self.0.load(Ordering::SeqCst)
}
pub fn count(&self) -> u32 {
self.0.load(Ordering::SeqCst).count_ones()
}
pub fn dirty_indices(&self) -> Vec<u8> {
let raw = self.raw();
(0..64).filter(|&i| (raw & (1 << i)) != 0).collect()
}
pub fn swap(&self, new_value: u64) -> u64 {
self.0.swap(new_value, Ordering::SeqCst)
}
}
impl Default for DirtyMask {
fn default() -> Self {
Self::new()
}
}
impl Clone for DirtyMask {
fn clone(&self) -> Self {
Self(AtomicU64::new(self.0.load(Ordering::SeqCst)))
}
}
#[derive(Debug)]
pub struct DirtyBits {
pub persona: DirtyMask,
pub standards: DirtyMask,
pub workflow: DirtyMask,
pub context: DirtyMask,
change_counter: AtomicU64,
last_sync: AtomicU64,
}
impl DirtyBits {
pub fn new() -> Self {
Self {
persona: DirtyMask::new(),
standards: DirtyMask::new(),
workflow: DirtyMask::new(),
context: DirtyMask::new(),
change_counter: AtomicU64::new(0),
last_sync: AtomicU64::new(0),
}
}
pub fn dirty_persona(&self, index: u8) {
self.persona.mark_dirty(index);
self.change_counter.fetch_add(1, Ordering::SeqCst);
}
pub fn dirty_standard(&self, index: u8) {
self.standards.mark_dirty(index);
self.change_counter.fetch_add(1, Ordering::SeqCst);
}
pub fn dirty_workflow(&self, index: u8) {
self.workflow.mark_dirty(index);
self.change_counter.fetch_add(1, Ordering::SeqCst);
}
pub fn dirty_context(&self, index: u8) {
self.context.mark_dirty(index);
self.change_counter.fetch_add(1, Ordering::SeqCst);
}
pub fn has_changes(&self) -> bool {
self.change_counter.load(Ordering::SeqCst) > self.last_sync.load(Ordering::SeqCst)
}
pub fn mark_synced(&self) {
self.persona.clear_all();
self.standards.clear_all();
self.workflow.clear_all();
self.context.clear_all();
self.last_sync
.store(self.change_counter.load(Ordering::SeqCst), Ordering::SeqCst);
}
pub fn change_count(&self) -> u64 {
self.change_counter.load(Ordering::SeqCst)
}
pub fn pending_changes(&self) -> u64 {
self.change_counter.load(Ordering::SeqCst) - self.last_sync.load(Ordering::SeqCst)
}
pub fn summary(&self) -> DirtySummary {
DirtySummary {
persona_dirty: self.persona.any_dirty(),
standards_dirty: self.standards.any_dirty(),
workflow_dirty: self.workflow.any_dirty(),
context_dirty: self.context.any_dirty(),
total_dirty: self.persona.count()
+ self.standards.count()
+ self.workflow.count()
+ self.context.count(),
}
}
}
impl Default for DirtyBits {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct DirtySummary {
pub persona_dirty: bool,
pub standards_dirty: bool,
pub workflow_dirty: bool,
pub context_dirty: bool,
pub total_dirty: u32,
}
impl DirtySummary {
pub fn any_dirty(&self) -> bool {
self.persona_dirty || self.standards_dirty || self.workflow_dirty || self.context_dirty
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dirty_mask() {
let mask = DirtyMask::new();
assert!(!mask.any_dirty());
mask.mark_dirty(0);
assert!(mask.is_dirty(0));
assert!(mask.any_dirty());
mask.mark_dirty(5);
assert!(mask.is_dirty(5));
assert_eq!(mask.count(), 2);
mask.clear(0);
assert!(!mask.is_dirty(0));
assert!(mask.is_dirty(5));
}
#[test]
fn test_dirty_bits() {
let bits = DirtyBits::new();
assert!(!bits.has_changes());
bits.dirty_standard(0);
assert!(bits.has_changes());
assert_eq!(bits.pending_changes(), 1);
bits.dirty_standard(1);
assert_eq!(bits.pending_changes(), 2);
bits.mark_synced();
assert!(!bits.has_changes());
assert_eq!(bits.pending_changes(), 0);
}
#[test]
fn test_dirty_summary() {
let bits = DirtyBits::new();
bits.dirty_persona(0);
bits.dirty_workflow(5);
let summary = bits.summary();
assert!(summary.persona_dirty);
assert!(!summary.standards_dirty);
assert!(summary.workflow_dirty);
assert!(summary.any_dirty());
}
}