use super::errors::{TopologyError, WaymarkError};
use super::topology::{TopologyLayer, TopologyTree};
use crate::physics::properties::PropertyValue;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum TransitionMode {
SeamlessStream,
PhaseOverlay,
CutScene,
FadeBlack,
Portal,
Teleport,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TransitionDef {
pub id: String,
pub name: String,
pub from_layer: TopologyLayer,
pub from_id: String,
pub to_layer: TopologyLayer,
pub to_id: String,
pub mode: TransitionMode,
pub properties: HashMap<String, PropertyValue>,
}
#[derive(Debug, Clone, Default)]
pub struct TransitionRegistry {
transitions: Vec<TransitionDef>,
id_index: HashMap<String, usize>,
}
impl TransitionRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn register(&mut self, def: TransitionDef) -> Result<(), WaymarkError> {
if self.id_index.contains_key(&def.id) {
return Err(WaymarkError::Topology(TopologyError::InvalidTransition {
from: def.from_id.clone(),
to: def.to_id.clone(),
}));
}
let idx = self.transitions.len();
self.id_index.insert(def.id.clone(), idx);
self.transitions.push(def);
Ok(())
}
pub fn find(&self, id: &str) -> Option<&TransitionDef> {
self.id_index.get(id).map(|&i| &self.transitions[i])
}
pub fn from_node(&self, node_id: &str) -> Vec<&TransitionDef> {
self.transitions.iter().filter(|t| t.from_id == node_id).collect()
}
pub fn to_node(&self, node_id: &str) -> Vec<&TransitionDef> {
self.transitions.iter().filter(|t| t.to_id == node_id).collect()
}
pub fn validate(&self, tree: &TopologyTree) -> Vec<WaymarkError> {
let mut errors = Vec::new();
for t in &self.transitions {
if tree.get(&t.from_id).is_none() {
errors.push(WaymarkError::Topology(TopologyError::InvalidTransition {
from: t.from_id.clone(),
to: t.to_id.clone(),
}));
}
if tree.get(&t.to_id).is_none() {
errors.push(WaymarkError::Topology(TopologyError::InvalidTransition {
from: t.from_id.clone(),
to: t.to_id.clone(),
}));
}
let diff = (t.from_layer.depth() as i8 - t.to_layer.depth() as i8).unsigned_abs();
if diff > 1 && t.from_layer != TopologyLayer::Realm && t.to_layer != TopologyLayer::Realm {
errors.push(WaymarkError::Topology(TopologyError::InvalidTransition {
from: format!("{:?}:{}", t.from_layer, t.from_id),
to: format!("{:?}:{}", t.to_layer, t.to_id),
}));
}
}
errors
}
pub fn len(&self) -> usize {
self.transitions.len()
}
pub fn is_empty(&self) -> bool {
self.transitions.is_empty()
}
}
impl TransitionMode {
pub const ALL: &[TransitionMode] = &[
Self::SeamlessStream,
Self::PhaseOverlay,
Self::CutScene,
Self::FadeBlack,
Self::Portal,
Self::Teleport,
];
pub fn name(&self) -> &'static str {
match self {
Self::SeamlessStream => "Seamless Stream",
Self::PhaseOverlay => "Phase Overlay",
Self::CutScene => "Cut Scene",
Self::FadeBlack => "Fade Black",
Self::Portal => "Portal",
Self::Teleport => "Teleport",
}
}
}
#[cfg(test)]
mod tests {
use super::super::topology::TopologyNode;
use super::*;
fn make_tree() -> TopologyTree {
let mut tree = TopologyTree::new();
tree.insert(TopologyNode {
layer: TopologyLayer::Location,
id: "loc1".into(),
name: "L1".into(),
parent_id: None,
properties: HashMap::new(),
tags: vec![],
})
.unwrap();
tree.insert(TopologyNode {
layer: TopologyLayer::Room,
id: "room1".into(),
name: "R1".into(),
parent_id: Some("loc1".into()),
properties: HashMap::new(),
tags: vec![],
})
.unwrap();
tree
}
#[test]
fn valid_adjacent_transition() {
let tree = make_tree();
let mut reg = TransitionRegistry::new();
reg.register(TransitionDef {
id: "t1".into(),
name: "T1".into(),
from_layer: TopologyLayer::Location,
from_id: "loc1".into(),
to_layer: TopologyLayer::Room,
to_id: "room1".into(),
mode: TransitionMode::SeamlessStream,
properties: HashMap::new(),
})
.unwrap();
let errors = reg.validate(&tree);
assert!(errors.is_empty());
}
#[test]
fn invalid_missing_node() {
let tree = make_tree();
let mut reg = TransitionRegistry::new();
reg.register(TransitionDef {
id: "t2".into(),
name: "T2".into(),
from_layer: TopologyLayer::Location,
from_id: "loc1".into(),
to_layer: TopologyLayer::Room,
to_id: "missing".into(),
mode: TransitionMode::Portal,
properties: HashMap::new(),
})
.unwrap();
let errors = reg.validate(&tree);
assert!(!errors.is_empty());
}
#[test]
fn transition_mode_all() {
assert_eq!(TransitionMode::ALL.len(), 6);
}
#[test]
fn from_node_query() {
let mut reg = TransitionRegistry::new();
reg.register(TransitionDef {
id: "t3".into(),
name: "T3".into(),
from_layer: TopologyLayer::Location,
from_id: "loc1".into(),
to_layer: TopologyLayer::Room,
to_id: "room1".into(),
mode: TransitionMode::FadeBlack,
properties: HashMap::new(),
})
.unwrap();
assert_eq!(reg.from_node("loc1").len(), 1);
assert_eq!(reg.to_node("room1").len(), 1);
}
}