use std::any::TypeId;
use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use crate::engine::channel_allocator::ChannelAllocator;
use crate::engine::entity::Entity;
use crate::engine::types::ChannelID;
#[cfg(feature = "messaging_gpu")]
use crate::engine::types::GPUResourceID;
use super::error::MessagingError;
use super::message::{
BruteForceMessage, BucketMessage, Message, MessageHandle, MessageTypeID, SpatialMessage,
TargetedMessage,
};
#[derive(Clone, Copy, Debug)]
pub struct SpatialConfig {
pub width: f32,
pub height: f32,
pub cell_size: f32,
}
impl SpatialConfig {
#[inline]
pub fn cols(&self) -> u32 {
(self.width / self.cell_size).ceil() as u32
}
#[inline]
pub fn rows(&self) -> u32 {
(self.height / self.cell_size).ceil() as u32
}
#[inline]
pub fn total_cells(&self) -> usize {
self.cols() as usize * self.rows() as usize
}
#[inline]
pub fn cell_id_of(&self, x: f32, y: f32) -> u32 {
let col = ((x / self.cell_size) as u32).min(self.cols() - 1);
let row = ((y / self.cell_size) as u32).min(self.rows() - 1);
row * self.cols() + col
}
#[inline]
pub fn cell_range_for_radius(&self, cx: f32, cy: f32, r: f32) -> (u32, u32, u32, u32) {
let cols = self.cols();
let rows = self.rows();
let intersects =
cx + r >= 0.0 && cy + r >= 0.0 && cx - r < self.width && cy - r < self.height;
if !intersects {
return (1, 0, 1, 0);
}
let col_lo = ((((cx - r) / self.cell_size).floor().max(0.0)) as u32).min(cols - 1);
let col_hi = ((cx + r) / self.cell_size).ceil().min((cols - 1) as f32) as u32;
let row_lo = ((((cy - r) / self.cell_size).floor().max(0.0)) as u32).min(rows - 1);
let row_hi = ((cy + r) / self.cell_size).ceil().min((rows - 1) as f32) as u32;
(col_lo, col_hi, row_lo, row_hi)
}
}
#[derive(Clone, Copy, Debug)]
pub struct Capacity {
pub initial: usize,
pub max: Option<usize>,
}
impl Capacity {
pub fn unbounded(initial: usize) -> Self {
Self { initial, max: None }
}
pub fn bounded(initial: usize, max: usize) -> Self {
Self {
initial,
max: Some(max),
}
}
}
#[derive(Clone, Copy, Debug)]
pub enum Specialisation {
BruteForce,
Bucket {
max_buckets: u32,
},
Spatial(SpatialConfig),
Targeted,
}
impl Specialisation {
pub fn name(&self) -> &'static str {
match self {
Specialisation::BruteForce => "BruteForce",
Specialisation::Bucket { .. } => "Bucket",
Specialisation::Spatial(_) => "Spatial",
Specialisation::Targeted => "Targeted",
}
}
}
type BucketKeyFn = unsafe fn(*const u8) -> u32;
type PositionFn = unsafe fn(*const u8) -> (f32, f32);
type RecipientFn = unsafe fn(*const u8) -> Entity;
pub(crate) struct ErasedFns {
pub bucket_key: Option<BucketKeyFn>,
pub position: Option<PositionFn>,
pub recipient: Option<RecipientFn>,
}
pub struct MessageDescriptor {
pub type_id: TypeId,
pub type_name: &'static str,
pub item_size: usize,
pub item_align: usize,
pub specialisation: Specialisation,
pub capacity: Capacity,
pub gpu_safe: bool,
#[cfg(feature = "messaging_gpu")]
pub gpu_resource_id: Option<GPUResourceID>,
pub channel_id: ChannelID,
pub(crate) message_type_id: MessageTypeID,
pub(crate) erased_fns: ErasedFns,
}
pub struct MessageRegistry {
registry_id: u64,
descriptors: Vec<MessageDescriptor>,
by_type: HashMap<TypeId, MessageTypeID>,
frozen: bool,
}
static NEXT_REGISTRY_ID: AtomicU64 = AtomicU64::new(1);
impl MessageRegistry {
pub fn new() -> Self {
MessageRegistry {
registry_id: NEXT_REGISTRY_ID.fetch_add(1, Ordering::Relaxed),
descriptors: Vec::new(),
by_type: HashMap::new(),
frozen: false,
}
}
fn check_not_registered<M: Message>(&self) -> Result<(), MessagingError> {
if self.by_type.contains_key(&TypeId::of::<M>()) {
return Err(MessagingError::AlreadyRegistered(std::any::type_name::<M>()));
}
if self.frozen {
return Err(MessagingError::RegistryFrozen);
}
Ok(())
}
fn push_descriptor<M: Message>(&mut self, desc: MessageDescriptor) -> MessageHandle<M> {
let mtid = desc.message_type_id;
let channel_id = desc.channel_id;
self.by_type.insert(desc.type_id, mtid);
self.descriptors.push(desc);
MessageHandle::new(mtid, self.registry_id, channel_id)
}
pub fn register_brute_force<M: BruteForceMessage>(
&mut self,
allocator: &mut ChannelAllocator,
capacity: Capacity,
) -> Result<MessageHandle<M>, MessagingError> {
self.check_not_registered::<M>()?;
let mtid = MessageTypeID(self.descriptors.len() as u32);
let channel_id = allocator
.alloc()
.map_err(|_| MessagingError::ChannelAllocationOverflow)?;
let desc = MessageDescriptor {
type_id: TypeId::of::<M>(),
type_name: std::any::type_name::<M>(),
item_size: std::mem::size_of::<M>(),
item_align: std::mem::align_of::<M>(),
specialisation: Specialisation::BruteForce,
capacity,
gpu_safe: M::GPU_SAFE,
#[cfg(feature = "messaging_gpu")]
gpu_resource_id: None,
channel_id,
message_type_id: mtid,
erased_fns: ErasedFns {
bucket_key: None,
position: None,
recipient: None,
},
};
Ok(self.push_descriptor::<M>(desc))
}
pub fn register_bucket<M: BucketMessage>(
&mut self,
allocator: &mut ChannelAllocator,
max_buckets: u32,
capacity: Capacity,
) -> Result<MessageHandle<M>, MessagingError> {
self.check_not_registered::<M>()?;
if max_buckets == 0 {
return Err(MessagingError::InvalidBucketConfig);
}
let mtid = MessageTypeID(self.descriptors.len() as u32);
let channel_id = allocator
.alloc()
.map_err(|_| MessagingError::ChannelAllocationOverflow)?;
let desc = MessageDescriptor {
type_id: TypeId::of::<M>(),
type_name: std::any::type_name::<M>(),
item_size: std::mem::size_of::<M>(),
item_align: std::mem::align_of::<M>(),
specialisation: Specialisation::Bucket { max_buckets },
capacity,
gpu_safe: M::GPU_SAFE,
#[cfg(feature = "messaging_gpu")]
gpu_resource_id: None,
channel_id,
message_type_id: mtid,
erased_fns: ErasedFns {
bucket_key: Some(|ptr| {
unsafe { (*(ptr as *const M)).bucket_key() }
}),
position: None,
recipient: None,
},
};
Ok(self.push_descriptor::<M>(desc))
}
pub fn register_spatial<M: SpatialMessage>(
&mut self,
allocator: &mut ChannelAllocator,
config: SpatialConfig,
capacity: Capacity,
) -> Result<MessageHandle<M>, MessagingError> {
self.check_not_registered::<M>()?;
if config.cell_size <= 0.0 || config.width <= 0.0 || config.height <= 0.0 {
return Err(MessagingError::InvalidSpatialConfig {
cell_size: config.cell_size,
width: config.width,
height: config.height,
});
}
let mtid = MessageTypeID(self.descriptors.len() as u32);
let channel_id = allocator
.alloc()
.map_err(|_| MessagingError::ChannelAllocationOverflow)?;
let desc = MessageDescriptor {
type_id: TypeId::of::<M>(),
type_name: std::any::type_name::<M>(),
item_size: std::mem::size_of::<M>(),
item_align: std::mem::align_of::<M>(),
specialisation: Specialisation::Spatial(config),
capacity,
gpu_safe: M::GPU_SAFE,
#[cfg(feature = "messaging_gpu")]
gpu_resource_id: None,
channel_id,
message_type_id: mtid,
erased_fns: ErasedFns {
bucket_key: None,
position: Some(|ptr| {
unsafe { (*(ptr as *const M)).position() }
}),
recipient: None,
},
};
Ok(self.push_descriptor::<M>(desc))
}
pub fn register_targeted<M: TargetedMessage>(
&mut self,
allocator: &mut ChannelAllocator,
capacity: Capacity,
) -> Result<MessageHandle<M>, MessagingError> {
self.check_not_registered::<M>()?;
let mtid = MessageTypeID(self.descriptors.len() as u32);
let channel_id = allocator
.alloc()
.map_err(|_| MessagingError::ChannelAllocationOverflow)?;
let desc = MessageDescriptor {
type_id: TypeId::of::<M>(),
type_name: std::any::type_name::<M>(),
item_size: std::mem::size_of::<M>(),
item_align: std::mem::align_of::<M>(),
specialisation: Specialisation::Targeted,
capacity,
gpu_safe: M::GPU_SAFE,
#[cfg(feature = "messaging_gpu")]
gpu_resource_id: None,
channel_id,
message_type_id: mtid,
erased_fns: ErasedFns {
bucket_key: None,
position: None,
recipient: Some(|ptr| {
unsafe { (*(ptr as *const M)).recipient() }
}),
},
};
Ok(self.push_descriptor::<M>(desc))
}
pub fn freeze(&mut self) {
self.frozen = true;
}
#[cfg(feature = "messaging_gpu")]
pub(crate) fn attach_gpu_resource<M: Message>(
&mut self,
handle: MessageHandle<M>,
resource_id: GPUResourceID,
) {
let desc = &mut self.descriptors[handle.message_type_id.index()];
desc.gpu_safe = true;
desc.gpu_resource_id = Some(resource_id);
}
#[inline]
pub fn is_frozen(&self) -> bool {
self.frozen
}
#[inline]
pub fn descriptors(&self) -> &[MessageDescriptor] {
&self.descriptors
}
#[inline]
pub fn descriptor_of<M: Message>(&self) -> Option<&MessageDescriptor> {
let mtid = self.by_type.get(&TypeId::of::<M>())?;
self.descriptors.get(mtid.index())
}
#[inline]
pub fn handle_of<M: Message>(&self) -> Option<MessageHandle<M>> {
let mtid = self.by_type.get(&TypeId::of::<M>()).copied()?;
let desc = self.descriptors.get(mtid.index())?;
Some(MessageHandle::new(mtid, self.registry_id, desc.channel_id))
}
#[inline]
pub(crate) fn registry_id(&self) -> u64 {
self.registry_id
}
#[inline]
pub(crate) fn descriptor(&self, mtid: MessageTypeID) -> &MessageDescriptor {
&self.descriptors[mtid.index()]
}
}
impl Default for MessageRegistry {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Clone, Copy, Debug)]
struct TestBucket(u32);
impl Message for TestBucket {}
impl BucketMessage for TestBucket {
fn bucket_key(&self) -> u32 {
self.0
}
}
#[test]
fn bucket_zero_config_is_rejected() {
let mut alloc = ChannelAllocator::new();
let mut registry = MessageRegistry::new();
let err = registry
.register_bucket::<TestBucket>(&mut alloc, 0, Capacity::unbounded(1))
.unwrap_err();
assert!(matches!(err, MessagingError::InvalidBucketConfig));
}
#[test]
fn typed_handle_has_channel_id() {
let mut alloc = ChannelAllocator::new();
let mut registry = MessageRegistry::new();
let handle = registry
.register_bucket::<TestBucket>(&mut alloc, 4, Capacity::unbounded(1))
.unwrap();
assert_eq!(handle.channel_id(), 0);
assert!(registry.handle_of::<TestBucket>().is_some());
}
}