use crate::clock::CompactTimestamp;
use crate::error::{CRDTError, CRDTResult};
use crate::memory::{MemoryConfig, NodeId};
use crate::traits::{BoundedCRDT, CRDT, RealTimeCRDT};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u8)]
pub enum MapPointType {
Free = 0,
Obstacle = 1,
Unknown = 2,
Landmark = 3,
Goal = 4,
ChargingStation = 5,
}
impl MapPointType {
pub fn is_navigable(&self) -> bool {
matches!(
self,
MapPointType::Free | MapPointType::Goal | MapPointType::ChargingStation
)
}
pub fn is_poi(&self) -> bool {
matches!(
self,
MapPointType::Landmark | MapPointType::Goal | MapPointType::ChargingStation
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MapPoint {
pub x: i32,
pub y: i32,
pub point_type: MapPointType,
pub confidence: u8,
pub timestamp: CompactTimestamp,
pub observer_id: NodeId,
}
impl MapPoint {
pub fn new(
x: i32,
y: i32,
point_type: MapPointType,
confidence: u8,
timestamp: u64,
observer_id: NodeId,
) -> Self {
Self {
x,
y,
point_type,
confidence,
timestamp: CompactTimestamp::new(timestamp),
observer_id,
}
}
pub fn grid_key(&self, grid_size: i32) -> (i32, i32) {
(self.x / grid_size, self.y / grid_size)
}
pub fn distance_squared(&self, other: &MapPoint) -> u64 {
let dx = (self.x - other.x) as i64;
let dy = (self.y - other.y) as i64;
(dx * dx + dy * dy) as u64
}
pub fn should_override(&self, other: &MapPoint) -> bool {
if self.confidence > other.confidence {
return true;
}
if self.confidence == other.confidence {
return self.timestamp > other.timestamp;
}
false
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct MapData {
pub point: MapPoint,
pub hash: u32,
}
impl MapData {
pub fn new(point: MapPoint) -> Self {
let hash = Self::compute_hash(&point);
Self { point, hash }
}
fn compute_hash(point: &MapPoint) -> u32 {
let mut hash = 0u32;
hash ^= point.x as u32;
hash ^= (point.y as u32) << 16;
hash ^= (point.point_type as u32) << 8;
hash ^= point.confidence as u32;
hash
}
}
#[derive(Debug, Clone)]
pub struct SharedMap<C: MemoryConfig> {
points: [Option<MapData>; 64], point_count: usize,
local_robot_id: NodeId,
last_update: CompactTimestamp,
grid_size: i32,
_phantom: core::marker::PhantomData<C>,
}
impl<C: MemoryConfig> SharedMap<C> {
pub fn new(robot_id: NodeId) -> Self {
Self {
points: [const { None }; 64],
point_count: 0,
local_robot_id: robot_id,
last_update: CompactTimestamp::new(0),
grid_size: 100, _phantom: core::marker::PhantomData,
}
}
pub fn with_grid_size(robot_id: NodeId, grid_size: i32) -> Self {
Self {
points: [const { None }; 64],
point_count: 0,
local_robot_id: robot_id,
last_update: CompactTimestamp::new(0),
grid_size,
_phantom: core::marker::PhantomData,
}
}
pub fn add_observation(
&mut self,
x: i32,
y: i32,
point_type: MapPointType,
confidence: u8,
timestamp: u64,
) -> CRDTResult<()> {
let point = MapPoint::new(x, y, point_type, confidence, timestamp, self.local_robot_id);
let map_data = MapData::new(point);
self.add_map_data(map_data)?;
self.last_update = CompactTimestamp::new(timestamp);
Ok(())
}
pub fn all_points(&self) -> impl Iterator<Item = &MapPoint> {
self.points
.iter()
.filter_map(|d| d.as_ref())
.map(|d| &d.point)
}
pub fn points_by_type(&self, point_type: MapPointType) -> impl Iterator<Item = &MapPoint> {
self.all_points()
.filter(move |p| p.point_type == point_type)
}
pub fn navigable_points(&self) -> impl Iterator<Item = &MapPoint> {
self.all_points().filter(|p| p.point_type.is_navigable())
}
pub fn points_of_interest(&self) -> impl Iterator<Item = &MapPoint> {
self.all_points().filter(|p| p.point_type.is_poi())
}
pub fn points_near(
&self,
x: i32,
y: i32,
max_distance_squared: u64,
) -> impl Iterator<Item = &MapPoint> {
let target = MapPoint::new(x, y, MapPointType::Unknown, 0, 0, 0);
self.all_points()
.filter(move |p| p.distance_squared(&target) <= max_distance_squared)
}
pub fn obstacles_near(
&self,
x: i32,
y: i32,
max_distance_squared: u64,
) -> impl Iterator<Item = &MapPoint> {
self.points_near(x, y, max_distance_squared)
.filter(|p| p.point_type == MapPointType::Obstacle)
}
pub fn nearest_poi(&self, x: i32, y: i32) -> Option<&MapPoint> {
let target = MapPoint::new(x, y, MapPointType::Unknown, 0, 0, 0);
self.points_of_interest()
.min_by_key(|p| p.distance_squared(&target))
}
pub fn point_count(&self) -> usize {
self.point_count
}
pub fn grid_size(&self) -> i32 {
self.grid_size
}
pub fn is_navigable(&self, x: i32, y: i32, safety_radius: u64) -> bool {
let obstacle_count = self.obstacles_near(x, y, safety_radius).count();
obstacle_count == 0
}
fn add_map_data(&mut self, map_data: MapData) -> CRDTResult<()> {
for i in 0..64 {
if let Some(ref mut existing) = self.points[i] {
let same_location =
existing.point.x == map_data.point.x && existing.point.y == map_data.point.y;
if same_location {
if map_data.point.should_override(&existing.point) {
*existing = map_data;
}
return Ok(());
}
} else {
self.points[i] = Some(map_data);
self.point_count += 1;
return Ok(());
}
}
self.make_space_for_point(map_data)
}
fn make_space_for_point(&mut self, new_data: MapData) -> CRDTResult<()> {
let mut lowest_idx = None;
let mut lowest_confidence = u8::MAX;
for (i, data_opt) in self.points.iter().enumerate() {
if let Some(data) = data_opt {
if data.point.confidence < lowest_confidence {
lowest_confidence = data.point.confidence;
lowest_idx = Some(i);
}
}
}
if let Some(idx) = lowest_idx {
if new_data.point.confidence > lowest_confidence {
self.points[idx] = Some(new_data);
return Ok(());
}
}
Err(CRDTError::BufferOverflow)
}
pub fn validate_map(&self) -> CRDTResult<()> {
for point in self.all_points() {
if point.observer_id as usize >= C::MAX_NODES {
return Err(CRDTError::InvalidNodeId);
}
}
Ok(())
}
}
impl<C: MemoryConfig> CRDT<C> for SharedMap<C> {
type Error = CRDTError;
fn merge(&mut self, other: &Self) -> CRDTResult<()> {
for data in other.points.iter().filter_map(|d| d.as_ref()) {
self.add_map_data(*data)?;
}
if other.last_update > self.last_update {
self.last_update = other.last_update;
}
Ok(())
}
fn eq(&self, other: &Self) -> bool {
if self.point_count != other.point_count {
return false;
}
for data in self.points.iter().filter_map(|d| d.as_ref()) {
let mut found = false;
for other_data in other.points.iter().filter_map(|d| d.as_ref()) {
if data.hash == other_data.hash && data.point == other_data.point {
found = true;
break;
}
}
if !found {
return false;
}
}
true
}
fn size_bytes(&self) -> usize {
core::mem::size_of::<Self>()
}
fn validate(&self) -> CRDTResult<()> {
self.validate_map()
}
fn state_hash(&self) -> u32 {
let mut hash = self.local_robot_id as u32;
for data in self.points.iter().filter_map(|d| d.as_ref()) {
hash ^= data.hash;
}
hash ^= self.point_count as u32;
hash
}
fn can_merge(&self, _other: &Self) -> bool {
true
}
}
impl<C: MemoryConfig> BoundedCRDT<C> for SharedMap<C> {
const MAX_SIZE_BYTES: usize = core::mem::size_of::<Self>();
const MAX_ELEMENTS: usize = 64;
fn memory_usage(&self) -> usize {
core::mem::size_of::<Self>()
}
fn element_count(&self) -> usize {
self.point_count
}
fn compact(&mut self) -> CRDTResult<usize> {
Ok(0)
}
fn can_add_element(&self) -> bool {
self.point_count < Self::MAX_ELEMENTS
}
}
impl<C: MemoryConfig> RealTimeCRDT<C> for SharedMap<C> {
const MAX_MERGE_CYCLES: u32 = 200; const MAX_VALIDATE_CYCLES: u32 = 100;
const MAX_SERIALIZE_CYCLES: u32 = 150;
fn merge_bounded(&mut self, other: &Self) -> CRDTResult<()> {
self.merge(other)
}
fn validate_bounded(&self) -> CRDTResult<()> {
self.validate()
}
fn remaining_budget(&self) -> Option<u32> {
None
}
fn set_budget(&mut self, _cycles: u32) {
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::memory::DefaultConfig;
#[test]
fn test_map_point_type_properties() {
assert!(MapPointType::Free.is_navigable());
assert!(MapPointType::Goal.is_navigable());
assert!(!MapPointType::Obstacle.is_navigable());
assert!(MapPointType::Landmark.is_poi());
assert!(MapPointType::ChargingStation.is_poi());
assert!(!MapPointType::Free.is_poi());
}
#[test]
fn test_map_point_creation() {
let point = MapPoint::new(1000, 2000, MapPointType::Obstacle, 200, 1000, 1);
assert_eq!(point.x, 1000);
assert_eq!(point.y, 2000);
assert_eq!(point.point_type, MapPointType::Obstacle);
assert_eq!(point.confidence, 200);
assert_eq!(point.observer_id, 1);
let grid_key = point.grid_key(100);
assert_eq!(grid_key, (10, 20)); }
#[test]
fn test_map_point_distance() {
let point1 = MapPoint::new(0, 0, MapPointType::Free, 100, 1000, 1);
let point2 = MapPoint::new(3, 4, MapPointType::Free, 100, 1000, 1);
assert_eq!(point1.distance_squared(&point2), 25); }
#[test]
fn test_map_point_override() {
let point1 = MapPoint::new(0, 0, MapPointType::Free, 100, 1000, 1);
let point2 = MapPoint::new(0, 0, MapPointType::Obstacle, 150, 1001, 2); let point3 = MapPoint::new(0, 0, MapPointType::Unknown, 100, 1002, 3);
assert!(point2.should_override(&point1)); assert!(point3.should_override(&point1)); assert!(!point1.should_override(&point2)); }
#[test]
fn test_shared_map_creation() {
let map = SharedMap::<DefaultConfig>::new(1);
assert_eq!(map.point_count(), 0);
assert_eq!(map.grid_size(), 100);
let custom_map = SharedMap::<DefaultConfig>::with_grid_size(1, 50);
assert_eq!(custom_map.grid_size(), 50);
}
#[test]
fn test_map_observations() {
let mut map = SharedMap::<DefaultConfig>::new(1);
map.add_observation(1000, 2000, MapPointType::Obstacle, 200, 1000)
.unwrap();
map.add_observation(1500, 2500, MapPointType::Landmark, 180, 1001)
.unwrap();
map.add_observation(500, 1500, MapPointType::Free, 150, 1002)
.unwrap();
assert_eq!(map.point_count(), 3);
let obstacles_count = map.points_by_type(MapPointType::Obstacle).count();
assert_eq!(obstacles_count, 1);
let obstacle = map.points_by_type(MapPointType::Obstacle).next().unwrap();
assert_eq!(obstacle.x, 1000);
let pois_count = map.points_of_interest().count();
assert_eq!(pois_count, 1);
let poi = map.points_of_interest().next().unwrap();
assert_eq!(poi.point_type, MapPointType::Landmark);
let navigable_count = map.navigable_points().count();
assert_eq!(navigable_count, 1);
let navigable = map.navigable_points().next().unwrap();
assert_eq!(navigable.point_type, MapPointType::Free);
}
#[test]
fn test_spatial_queries() {
let mut map = SharedMap::<DefaultConfig>::new(1);
map.add_observation(0, 0, MapPointType::Free, 100, 1000)
.unwrap();
map.add_observation(100, 0, MapPointType::Obstacle, 150, 1001)
.unwrap();
map.add_observation(1000, 1000, MapPointType::Landmark, 200, 1002)
.unwrap();
let nearby_count = map.points_near(50, 0, 10000).count(); assert_eq!(nearby_count, 2);
let obstacles_count = map.obstacles_near(50, 0, 10000).count();
assert_eq!(obstacles_count, 1);
let obstacle = map.obstacles_near(50, 0, 10000).next().unwrap();
assert_eq!(obstacle.point_type, MapPointType::Obstacle);
let nearest_poi = map.nearest_poi(500, 500).unwrap();
assert_eq!(nearest_poi.point_type, MapPointType::Landmark);
assert!(map.is_navigable(0, 0, 1000)); assert!(!map.is_navigable(100, 0, 1000)); }
#[test]
fn test_map_point_override_behavior() {
let mut map = SharedMap::<DefaultConfig>::new(1);
map.add_observation(1000, 1000, MapPointType::Unknown, 100, 1000)
.unwrap();
assert_eq!(map.point_count(), 1);
map.add_observation(1000, 1000, MapPointType::Obstacle, 200, 1001)
.unwrap();
assert_eq!(map.point_count(), 1);
let point = map.all_points().next().unwrap();
assert_eq!(point.point_type, MapPointType::Obstacle);
assert_eq!(point.confidence, 200);
}
#[test]
fn test_shared_map_merge() {
let mut map1 = SharedMap::<DefaultConfig>::new(1);
let mut map2 = SharedMap::<DefaultConfig>::new(2);
map1.add_observation(0, 0, MapPointType::Free, 100, 1000)
.unwrap();
map2.add_observation(1000, 1000, MapPointType::Obstacle, 150, 1001)
.unwrap();
map1.merge(&map2).unwrap();
assert_eq!(map1.point_count(), 2);
assert!(map1.points_by_type(MapPointType::Free).next().is_some());
assert!(map1.points_by_type(MapPointType::Obstacle).next().is_some());
}
#[test]
fn test_bounded_crdt_implementation() {
let mut map = SharedMap::<DefaultConfig>::new(1);
assert_eq!(map.element_count(), 0);
assert!(map.can_add_element());
map.add_observation(0, 0, MapPointType::Free, 100, 1000)
.unwrap();
assert_eq!(map.element_count(), 1);
assert!(map.memory_usage() > 0);
}
#[test]
fn test_real_time_crdt_implementation() {
let mut map1 = SharedMap::<DefaultConfig>::new(1);
let map2 = SharedMap::<DefaultConfig>::new(2);
assert!(map1.merge_bounded(&map2).is_ok());
assert!(map1.validate_bounded().is_ok());
}
}