use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, RwLock};
use crate::engine::types::{ArchetypeID, ComponentID, COMPONENT_CAP};
#[derive(Debug)]
enum Words {
Inline(AtomicU64),
Heap(Vec<AtomicU64>),
}
impl Words {
fn new(chunk_count: usize) -> Self {
if chunk_count <= 64 {
Words::Inline(AtomicU64::new(0))
} else {
let word_count = chunk_count.div_ceil(64);
let mut words = Vec::with_capacity(word_count);
for _ in 0..word_count {
words.push(AtomicU64::new(0));
}
Words::Heap(words)
}
}
#[inline]
fn get(&self, index: usize) -> Option<&AtomicU64> {
match self {
Words::Inline(w) => {
if index == 0 {
Some(w)
} else {
None
}
}
Words::Heap(v) => v.get(index),
}
}
fn iter(&self) -> WordsIter<'_> {
WordsIter {
words: self,
index: 0,
}
}
}
struct WordsIter<'a> {
words: &'a Words,
index: usize,
}
impl<'a> Iterator for WordsIter<'a> {
type Item = &'a AtomicU64;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let w = self.words.get(self.index)?;
self.index += 1;
Some(w)
}
}
#[derive(Debug)]
pub struct Entry {
chunk_count: usize,
words: Words,
}
impl Entry {
fn new(chunk_count: usize) -> Self {
Self {
chunk_count,
words: Words::new(chunk_count),
}
}
#[inline]
pub(crate) fn mark_dirty(&self, chunk: usize) {
let word_idx = chunk / 64;
let bit_offset = chunk % 64;
if let Some(word) = self.words.get(word_idx) {
word.fetch_or(1u64 << bit_offset, Ordering::Relaxed);
}
}
#[inline]
fn mark_all_dirty(&self) {
for word in self.words.iter() {
word.store(u64::MAX, Ordering::Relaxed);
}
}
fn take_dirty_chunks_and_clear(&self) -> Vec<usize> {
let mut out = Vec::new();
for (word_index, word) in self.words.iter().enumerate() {
let bits = word.swap(0, Ordering::AcqRel);
if bits == 0 {
continue;
}
let base = word_index * 64;
let mut remaining = bits;
while remaining != 0 {
let bit = remaining.trailing_zeros() as usize;
let index = base + bit;
if index < self.chunk_count {
out.push(index);
}
remaining &= remaining - 1;
}
}
out
}
}
#[derive(Debug)]
pub struct DirtyChunks {
entries: RwLock<Vec<Option<Arc<Entry>>>>,
}
impl Default for DirtyChunks {
fn default() -> Self {
Self::new()
}
}
impl DirtyChunks {
pub fn new() -> Self {
Self {
entries: RwLock::new(Vec::new()),
}
}
#[inline]
fn flat_index(archetype: ArchetypeID, component: ComponentID) -> usize {
(archetype as usize) * COMPONENT_CAP + (component as usize)
}
fn ensure_vec_capacity(vec: &mut Vec<Option<Arc<Entry>>>, index: usize) {
if index >= vec.len() {
vec.resize_with(index + 1, || None);
}
}
pub fn notify_archetype_changed(&self, archetype_id: ArchetypeID) {
if let Ok(entries) = self.entries.read() {
let base = (archetype_id as usize) * COMPONENT_CAP;
if base >= entries.len() {
return; }
let end = (base + COMPONENT_CAP).min(entries.len());
for entry in entries[base..end].iter().flatten() {
entry.mark_all_dirty();
}
}
}
fn ensure_entry(
&self,
archetype: ArchetypeID,
component: ComponentID,
chunk_count: usize,
) -> Arc<Entry> {
let index = Self::flat_index(archetype, component);
if let Ok(entries) = self.entries.read() {
if let Some(Some(entry)) = entries.get(index) {
if entry.chunk_count == chunk_count {
return entry.clone();
}
}
}
let mut entries = self.entries.write().expect("DirtyChunks lock poisoned");
Self::ensure_vec_capacity(&mut entries, index);
if let Some(entry) = &entries[index] {
if entry.chunk_count == chunk_count {
return entry.clone();
}
}
let entry = Arc::new(Entry::new(chunk_count));
entry.mark_all_dirty();
entries[index] = Some(entry.clone());
entry
}
#[inline]
pub fn resolve_entry(
&self,
archetype: ArchetypeID,
component: ComponentID,
chunk_count: usize,
) -> Arc<Entry> {
self.ensure_entry(archetype, component, chunk_count)
}
#[inline]
pub fn mark_chunk_dirty(
&self,
archetype: ArchetypeID,
component: ComponentID,
chunk: usize,
chunk_count: usize,
) {
let entry = self.ensure_entry(archetype, component, chunk_count);
entry.mark_dirty(chunk);
}
pub fn mark_all_dirty(
&self,
archetype: ArchetypeID,
component: ComponentID,
chunk_count: usize,
) {
let entry = self.ensure_entry(archetype, component, chunk_count);
entry.mark_all_dirty();
}
pub fn take_dirty_chunks(
&self,
archetype: ArchetypeID,
component: ComponentID,
chunk_count: usize,
) -> Vec<usize> {
let entry = self.ensure_entry(archetype, component, chunk_count);
entry.take_dirty_chunks_and_clear()
}
}