use super::errors::{TopologyError, WaymarkError};
use super::functions::FunctionRegistry;
use crate::physics::properties::PropertyValue;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[repr(u8)]
pub enum TopologyLayer {
Universe = 0,
Galaxy = 1,
Sector = 2,
World = 3,
Realm = 4,
Region = 5,
Area = 6,
Location = 7,
Room = 8,
Point = 9,
}
impl TopologyLayer {
pub const ALL: &[TopologyLayer] = &[
Self::Universe,
Self::Galaxy,
Self::Sector,
Self::World,
Self::Realm,
Self::Region,
Self::Area,
Self::Location,
Self::Room,
Self::Point,
];
pub fn from_u8(v: u8) -> Option<Self> {
match v {
0 => Some(Self::Universe),
1 => Some(Self::Galaxy),
2 => Some(Self::Sector),
3 => Some(Self::World),
4 => Some(Self::Realm),
5 => Some(Self::Region),
6 => Some(Self::Area),
7 => Some(Self::Location),
8 => Some(Self::Room),
9 => Some(Self::Point),
_ => None,
}
}
pub fn name(&self) -> &'static str {
match self {
Self::Universe => "Universe",
Self::Galaxy => "Galaxy",
Self::Sector => "Sector",
Self::World => "World",
Self::Realm => "Realm",
Self::Region => "Region",
Self::Area => "Area",
Self::Location => "Location",
Self::Room => "Room",
Self::Point => "Point",
}
}
pub fn parent_layer(&self) -> Option<TopologyLayer> {
match self {
Self::Universe => None,
Self::Galaxy => Some(Self::Universe),
Self::Sector => Some(Self::Galaxy),
Self::World => Some(Self::Sector),
Self::Realm => Some(Self::World),
Self::Region => Some(Self::Realm),
Self::Area => Some(Self::Region),
Self::Location => Some(Self::Area),
Self::Room => Some(Self::Location),
Self::Point => Some(Self::Room),
}
}
pub fn depth(&self) -> u8 {
*self as u8
}
pub fn cell_size(self) -> i32 {
match self {
Self::Universe | Self::Galaxy => 1_048_576,
Self::Sector | Self::World => 65_536,
Self::Realm | Self::Region => 4_096,
Self::Area | Self::Location => 128,
Self::Room | Self::Point => 1,
}
}
pub fn index(self) -> u32 {
self as u32
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TopologyNode {
pub layer: TopologyLayer,
pub id: String,
pub name: String,
pub parent_id: Option<String>,
pub properties: HashMap<String, PropertyValue>,
pub tags: Vec<String>,
}
#[derive(Debug, Clone, Default)]
pub struct TopologyTree {
nodes: HashMap<String, TopologyNode>,
children: HashMap<String, Vec<String>>,
}
impl TopologyTree {
pub fn new() -> Self {
Self::default()
}
pub fn insert(&mut self, node: TopologyNode) -> Result<(), WaymarkError> {
if let Some(ref pid) = node.parent_id {
if let Some(parent) = self.nodes.get(pid) {
if node.layer <= parent.layer {
return Err(WaymarkError::Topology(TopologyError::InvalidParentChildRelation {
parent: pid.clone(),
child: node.id.clone(),
}));
}
}
self.children.entry(pid.clone()).or_default().push(node.id.clone());
}
self.nodes.insert(node.id.clone(), node);
Ok(())
}
pub fn get(&self, id: &str) -> Option<&TopologyNode> {
self.nodes.get(id)
}
pub fn children_of(&self, id: &str) -> Vec<&str> {
self.children
.get(id)
.map(|v| v.iter().map(|s| s.as_str()).collect())
.unwrap_or_default()
}
pub fn ancestor_chain(&self, id: &str) -> Vec<&TopologyNode> {
let mut chain = Vec::new();
let mut current = id;
while let Some(node) = self.nodes.get(current) {
chain.push(node);
match &node.parent_id {
Some(pid) => current = pid,
None => break,
}
}
chain
}
pub fn resolve_property(&self, node_id: &str, key: &str) -> Option<PropertyValue> {
for node in self.ancestor_chain(node_id) {
if let Some(val) = node.properties.get(key) {
return Some(val.clone());
}
}
None
}
pub fn resolve_property_with_default(
&self,
node_id: &str,
key: &str,
registry: &FunctionRegistry,
) -> PropertyValue {
if let Some(val) = self.resolve_property(node_id, key) {
return val;
}
registry
.find(key)
.map(|f| f.default_value.clone())
.unwrap_or(PropertyValue::Float(0.0))
}
pub fn len(&self) -> usize {
self.nodes.len()
}
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
pub fn nodes_mut(&mut self) -> &mut HashMap<String, TopologyNode> {
&mut self.nodes
}
pub fn validate(&self) -> Vec<WaymarkError> {
let mut errors = Vec::new();
for node in self.nodes.values() {
if let Some(ref pid) = node.parent_id {
if !self.nodes.contains_key(pid) {
errors.push(WaymarkError::Topology(TopologyError::InvalidParentChildRelation {
parent: pid.clone(),
child: node.id.clone(),
}));
}
}
}
errors
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn layer_all_count() {
assert_eq!(TopologyLayer::ALL.len(), 10);
}
#[test]
fn layer_ordering() {
assert!(TopologyLayer::Universe < TopologyLayer::Galaxy);
assert!(TopologyLayer::Galaxy < TopologyLayer::World);
assert!(TopologyLayer::Room < TopologyLayer::Point);
}
#[test]
fn layer_parent() {
assert_eq!(TopologyLayer::Galaxy.parent_layer(), Some(TopologyLayer::Universe));
assert_eq!(TopologyLayer::Universe.parent_layer(), None);
}
#[test]
fn tree_insert_and_get() {
let mut tree = TopologyTree::new();
tree.insert(TopologyNode {
layer: TopologyLayer::Universe,
id: "u1".into(),
name: "Test".into(),
parent_id: None,
properties: HashMap::new(),
tags: vec![],
})
.unwrap();
assert!(tree.get("u1").is_some());
}
#[test]
fn tree_ancestor_chain() {
let mut tree = TopologyTree::new();
tree.insert(TopologyNode {
layer: TopologyLayer::Universe,
id: "u".into(),
name: "U".into(),
parent_id: None,
properties: HashMap::new(),
tags: vec![],
})
.unwrap();
tree.insert(TopologyNode {
layer: TopologyLayer::Galaxy,
id: "g".into(),
name: "G".into(),
parent_id: Some("u".into()),
properties: HashMap::new(),
tags: vec![],
})
.unwrap();
tree.insert(TopologyNode {
layer: TopologyLayer::World,
id: "w".into(),
name: "W".into(),
parent_id: Some("g".into()),
properties: HashMap::new(),
tags: vec![],
})
.unwrap();
let chain = tree.ancestor_chain("w");
assert_eq!(chain.len(), 3);
assert_eq!(chain[0].id, "w");
assert_eq!(chain[2].id, "u");
}
#[test]
fn resolve_property_inheritance() {
let mut tree = TopologyTree::new();
let mut props = HashMap::new();
props.insert("gravity.planetary".into(), PropertyValue::Float(3.71));
tree.insert(TopologyNode {
layer: TopologyLayer::World,
id: "w".into(),
name: "W".into(),
parent_id: None,
properties: props,
tags: vec![],
})
.unwrap();
tree.insert(TopologyNode {
layer: TopologyLayer::Region,
id: "r".into(),
name: "R".into(),
parent_id: Some("w".into()),
properties: HashMap::new(),
tags: vec![],
})
.unwrap();
let val = tree.resolve_property("r", "gravity.planetary");
assert_eq!(val, Some(PropertyValue::Float(3.71)));
}
#[test]
fn resolve_property_local_override() {
let mut tree = TopologyTree::new();
let mut world_props = HashMap::new();
world_props.insert("gravity.planetary".into(), PropertyValue::Float(9.81));
let mut region_props = HashMap::new();
region_props.insert("gravity.planetary".into(), PropertyValue::Float(1.62));
tree.insert(TopologyNode {
layer: TopologyLayer::World,
id: "w".into(),
name: "W".into(),
parent_id: None,
properties: world_props,
tags: vec![],
})
.unwrap();
tree.insert(TopologyNode {
layer: TopologyLayer::Region,
id: "r".into(),
name: "R".into(),
parent_id: Some("w".into()),
properties: region_props,
tags: vec![],
})
.unwrap();
let val = tree.resolve_property("r", "gravity.planetary");
assert_eq!(val, Some(PropertyValue::Float(1.62)));
}
#[test]
fn resolve_with_function_default() {
let tree = TopologyTree::new();
let reg = FunctionRegistry::with_builtins();
let val = tree.resolve_property_with_default("nonexistent", "gravity.local", ®);
assert_eq!(val, PropertyValue::Float(9.81));
}
#[test]
fn cell_size_cosmic_layers() {
assert_eq!(TopologyLayer::Universe.cell_size(), 1_048_576);
assert_eq!(TopologyLayer::Galaxy.cell_size(), 1_048_576);
}
#[test]
fn cell_size_world_layers() {
assert_eq!(TopologyLayer::Sector.cell_size(), 65_536);
assert_eq!(TopologyLayer::World.cell_size(), 65_536);
}
#[test]
fn cell_size_region_layers() {
assert_eq!(TopologyLayer::Realm.cell_size(), 4_096);
assert_eq!(TopologyLayer::Region.cell_size(), 4_096);
}
#[test]
fn cell_size_local_layers() {
assert_eq!(TopologyLayer::Area.cell_size(), 128);
assert_eq!(TopologyLayer::Location.cell_size(), 128);
assert_eq!(TopologyLayer::Room.cell_size(), 1);
assert_eq!(TopologyLayer::Point.cell_size(), 1);
}
#[test]
fn index_matches_repr() {
for layer in TopologyLayer::ALL {
assert_eq!(layer.index(), layer.depth() as u32);
}
}
#[test]
fn validate_orphaned_node() {
let mut tree = TopologyTree::new();
tree.insert(TopologyNode {
layer: TopologyLayer::Region,
id: "r".into(),
name: "R".into(),
parent_id: Some("missing".into()),
properties: HashMap::new(),
tags: vec![],
})
.unwrap();
let errors = tree.validate();
assert_eq!(errors.len(), 1);
}
}