use super::EntryQuery;
use crate::event::EventKind;
use crate::store::index::{IndexEntry, QueryHit};
use std::collections::HashSet;
use std::sync::Arc;
#[repr(C, align(64))]
pub(crate) struct Tile<const N: usize> {
pub kinds: Vec<EventKind>,
pub entries: Vec<Arc<IndexEntry>>,
pub len: usize,
}
impl<const N: usize> Tile<N> {
pub(crate) fn new() -> Self {
Self {
kinds: Vec::with_capacity(N),
entries: Vec::with_capacity(N),
len: 0,
}
}
#[inline]
pub(crate) fn is_full(&self) -> bool {
self.len >= N
}
pub(crate) fn push(&mut self, kind: EventKind, entry: Arc<IndexEntry>) {
debug_assert!(!self.is_full(), "Tile<{N}>::push called on a full tile");
self.kinds.push(kind);
self.entries.push(entry);
self.len += 1;
}
}
pub(super) struct AoSoAInner<const N: usize> {
pub(super) tiles: Vec<Tile<N>>,
open_tiles: std::collections::HashMap<EventKind, usize>,
scope_entities: std::collections::HashMap<Arc<str>, HashSet<Arc<str>>>,
}
impl<const N: usize> AoSoAInner<N> {
pub(super) fn new() -> Self {
Self {
tiles: Vec::new(),
open_tiles: std::collections::HashMap::new(),
scope_entities: std::collections::HashMap::new(),
}
}
pub(super) fn from_entries(entries: &[Arc<IndexEntry>]) -> Self {
let mut built = Self::new();
for entry in entries {
built.push(entry);
}
built
}
pub(super) fn push(&mut self, entry: &Arc<IndexEntry>) {
let scope: Arc<str> = entry.coord.scope_arc();
let entity: Arc<str> = entry.coord.entity_arc();
let kind = entry.kind;
debug_assert_eq!(
scope.as_ref(),
entry.coord.scope(),
"scope_entities bucket must match entry.coord.scope()"
);
match self.open_tiles.get(&kind).copied() {
Some(idx) => {
self.tiles[idx].push(kind, Arc::clone(entry));
if self.tiles[idx].is_full() {
self.open_tiles.remove(&kind);
}
}
None => {
let new_idx = self.tiles.len();
let mut tile = Tile::new();
tile.push(kind, Arc::clone(entry));
let is_full = tile.is_full();
self.tiles.push(tile);
if !is_full {
self.open_tiles.insert(kind, new_idx);
}
}
}
self.scope_entities.entry(scope).or_default().insert(entity);
}
fn query_hits_entries(&self, mut matches: impl FnMut(EventKind) -> bool) -> Vec<QueryHit> {
let mut out = Vec::new();
for tile in &self.tiles {
if tile.len == 0 {
continue;
}
if !matches(tile.kinds[0]) {
continue;
}
for entry in tile.entries.iter().take(tile.len) {
out.push(QueryHit::from_entry(entry));
}
}
out
}
pub(super) fn query_hits_by_kind(&self, target: EventKind) -> Vec<QueryHit> {
self.query_hits_entries(|kind| kind == target)
}
pub(super) fn query_hits_by_category(&self, category: u8) -> Vec<QueryHit> {
self.query_hits_entries(|kind| kind.category() == category)
}
pub(super) fn query_hits_by_scope(&self, scope: &str) -> Vec<QueryHit> {
let Some(entities) = self.scope_entities.get(scope) else {
return Vec::new();
};
let mut out = Vec::new();
for tile in &self.tiles {
for entry in tile.entries.iter().take(tile.len) {
if entities.contains(entry.coord.entity_arc().as_ref()) {
out.push(QueryHit::from_entry(entry));
}
}
}
out
}
pub(super) fn hits_candidates(&self, spec: &EntryQuery<'_>) -> Vec<QueryHit> {
match spec {
EntryQuery::Kind(kind) => self.query_hits_by_kind(*kind),
EntryQuery::Category(category) => self.query_hits_by_category(*category),
EntryQuery::Scope(scope) => self.query_hits_by_scope(scope),
}
}
#[cfg(test)]
pub(crate) fn with_tile<R>(&self, idx: usize, f: impl FnOnce(&Tile<N>) -> R) -> Option<R> {
self.tiles.get(idx).map(f)
}
pub(super) fn clear(&mut self) {
self.tiles.clear();
self.open_tiles.clear();
self.scope_entities.clear();
}
}