use std::collections::HashMap;
use std::ops::Range;
use crate::engine::entity::Entity;
use crate::messaging::aligned_buffer::AlignedBuffer;
use crate::messaging::error::MessagingError;
use crate::messaging::message::Message;
use crate::messaging::registry::ErasedFns;
use crate::ECSResult;
pub(crate) struct TargetedBuffer {
pub(crate) data: AlignedBuffer,
pub(crate) inbox_index: HashMap<Entity, Range<u32>>,
item_size: usize,
}
impl TargetedBuffer {
pub(crate) fn new(item_size: usize, item_align: usize, capacity: usize) -> Self {
TargetedBuffer {
data: AlignedBuffer::with_capacity(item_size, item_align, capacity),
inbox_index: HashMap::new(),
item_size,
}
}
pub(crate) fn begin_tick(&mut self) {
self.data.clear();
self.inbox_index.clear();
}
pub(crate) unsafe fn finalise(
&mut self,
raw: &AlignedBuffer,
fns: &ErasedFns,
) -> ECSResult<()> {
let n = raw.len();
if n == 0 {
return Ok(());
}
let recipient_fn = fns.recipient.ok_or(MessagingError::MissingErasedFunction {
specialisation: "Targeted",
function: "recipient",
})?;
let mut entity_pairs: Vec<Entity> = Vec::with_capacity(n);
let mut counts: HashMap<Entity, u32> = HashMap::with_capacity(n / 4 + 1);
for i in 0..n {
let ptr = unsafe { raw.as_ptr_at(i) };
let entity = unsafe { recipient_fn(ptr) };
entity_pairs.push(entity);
*counts.entry(entity).or_insert(0) += 1;
}
let mut entities: Vec<Entity> = counts.keys().copied().collect();
entities.sort_unstable_by_key(|e| e.to_raw());
self.inbox_index.clear();
let mut cursor: u32 = 0;
for &entity in &entities {
let cnt = counts[&entity];
let start = cursor;
cursor += cnt;
self.inbox_index.insert(entity, start..cursor);
}
self.data.reserve(n);
unsafe { self.data.set_len(n) };
let mut scatter_cursor: HashMap<Entity, u32> = self
.inbox_index
.iter()
.map(|(&e, r)| (e, r.start))
.collect();
for (i, &entity) in entity_pairs.iter().enumerate() {
let src = unsafe { raw.as_ptr_at(i) };
let dst_idx = *scatter_cursor
.get(&entity)
.ok_or(MessagingError::FinaliseInvariant {
specialisation: "Targeted",
reason: "entity missing from scatter cursor",
})? as usize;
let dst = unsafe { self.data.as_mut_ptr_at(dst_idx) };
unsafe { std::ptr::copy_nonoverlapping(src, dst, self.item_size) };
let cursor =
scatter_cursor
.get_mut(&entity)
.ok_or(MessagingError::FinaliseInvariant {
specialisation: "Targeted",
reason: "entity missing from scatter cursor",
})?;
*cursor += 1;
}
Ok(())
}
}
pub struct InboxIter<'a, M> {
slice: &'a [M],
index: usize,
}
impl<'a, M: Message> InboxIter<'a, M> {
pub(crate) fn new(buf: &'a TargetedBuffer, recipient: Entity) -> Self {
let range = match buf.inbox_index.get(&recipient) {
Some(r) => r.clone(),
None => {
return InboxIter {
slice: &[],
index: 0,
}
}
};
if range.is_empty() || buf.data.is_empty() {
return InboxIter {
slice: &[],
index: 0,
};
}
let full: &'a [M] = unsafe { buf.data.as_slice() };
let start = range.start as usize;
let end = range.end as usize;
InboxIter {
slice: &full[start..end],
index: 0,
}
}
pub(crate) fn empty() -> Self {
InboxIter {
slice: &[],
index: 0,
}
}
}
impl<'a, M: Copy> Iterator for InboxIter<'a, M> {
type Item = M;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.index < self.slice.len() {
let item = self.slice[self.index];
self.index += 1;
Some(item)
} else {
None
}
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let r = self.slice.len() - self.index;
(r, Some(r))
}
}
impl<'a, M: Copy> ExactSizeIterator for InboxIter<'a, M> {}
#[cfg(test)]
mod tests {
use super::*;
use crate::engine::error::ECSError;
#[derive(Clone, Copy)]
struct TestMsg {
_value: u32,
}
#[test]
fn missing_recipient_accessor_returns_error() {
let mut raw = AlignedBuffer::with_capacity(
std::mem::size_of::<TestMsg>(),
std::mem::align_of::<TestMsg>(),
1,
);
unsafe { raw.push(TestMsg { _value: 1 }) };
let mut buf = TargetedBuffer::new(
std::mem::size_of::<TestMsg>(),
std::mem::align_of::<TestMsg>(),
1,
);
let fns = ErasedFns {
bucket_key: None,
position: None,
recipient: None,
};
let err = unsafe { buf.finalise(&raw, &fns) }.unwrap_err();
assert!(matches!(
err,
ECSError::Messaging(MessagingError::MissingErasedFunction {
specialisation: "Targeted",
function: "recipient"
})
));
}
}