use std::collections::{HashMap, HashSet};
use std::fmt;
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use crate::types::EntityId;
use bevy_gearbox_protocol::components as c;
pub(crate) type TypePathString = String;
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub(crate) struct DirtyFlags {
pub(crate) components: bool,
pub(crate) structure: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub(crate) struct ComponentEntry {
pub(crate) type_path: TypePathString,
pub(crate) value_json: JsonValue,
pub(crate) dirty: bool,
pub(crate) validation_errors: Vec<String>,
pub(crate) server_version: Option<u64>,
}
impl ComponentEntry {
pub(crate) fn new(type_path: impl Into<TypePathString>, value_json: JsonValue) -> Self {
Self {
type_path: type_path.into(),
value_json,
dirty: false,
validation_errors: Vec::new(),
server_version: None,
}
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub(crate) struct ComponentBag {
pub(crate) entries: HashMap<TypePathString, ComponentEntry>,
}
impl ComponentBag {
pub(crate) fn contains(&self, type_path: &str) -> bool { self.entries.contains_key(type_path) }
pub(crate) fn get(&self, type_path: &str) -> Option<&ComponentEntry> { self.entries.get(type_path) }
pub(crate) fn get_mut(&mut self, type_path: &str) -> Option<&mut ComponentEntry> { self.entries.get_mut(type_path) }
pub(crate) fn insert(&mut self, entry: ComponentEntry) { self.entries.insert(entry.type_path.clone(), entry); }
pub(crate) fn remove(&mut self, type_path: &str) -> Option<ComponentEntry> { self.entries.remove(type_path) }
pub(crate) fn keys(&self) -> impl Iterator<Item=&str> { self.entries.keys().map(|s| s.as_str()) }
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub(crate) struct StateNode {
pub(crate) id: EntityId,
pub(crate) dirty: DirtyFlags,
pub(crate) server_version: Option<u64>,
}
impl StateNode {
pub(crate) fn new(id: EntityId) -> Self {
Self {
id,
dirty: DirtyFlags::default(),
server_version: None,
}
}
}
impl Default for StateNode {
fn default() -> Self {
Self::new(EntityId(0))
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub(crate) struct Edge {
pub(crate) id: EntityId,
pub(crate) source: EntityId,
pub(crate) target: EntityId,
pub(crate) components: ComponentBag,
pub(crate) display_label: Option<String>,
pub(crate) dirty: DirtyFlags,
pub(crate) server_version: Option<u64>,
}
impl Edge {
pub(crate) fn new(id: EntityId, source: EntityId, target: EntityId) -> Self {
Self {
id,
source,
target,
components: ComponentBag::default(),
display_label: None,
dirty: DirtyFlags::default(),
server_version: None,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub(crate) struct StateMachineGraph {
pub(crate) root: EntityId,
pub(crate) nodes: HashMap<EntityId, StateNode>,
pub(crate) edges: HashMap<EntityId, Edge>,
pub(crate) adjacency_out: HashMap<EntityId, Vec<EntityId>>,
pub(crate) adjacency_in: HashMap<EntityId, Vec<EntityId>>,
pub(crate) entity_data: HashMap<EntityId, ComponentBag>,
pub(crate) active_nodes: std::collections::HashSet<EntityId>,
}
impl StateMachineGraph {
pub(crate) fn new(root: StateNode) -> Self {
let root_id = root.id;
let mut nodes = HashMap::new();
nodes.insert(root_id, root);
Self {
root: root_id,
nodes,
edges: HashMap::new(),
adjacency_out: HashMap::new(),
adjacency_in: HashMap::new(),
entity_data: HashMap::new(),
active_nodes: std::collections::HashSet::new(),
}
}
pub(crate) fn component_bag(&self, id: &EntityId) -> Option<&ComponentBag> {
self.entity_data.get(id)
}
pub(crate) fn has_component(&self, id: &EntityId, type_path: &str) -> bool {
self.entity_data.get(id).map_or(false, |b| b.contains(type_path))
}
pub(crate) fn get_label_for(&self, id: &EntityId) -> String {
if let Some(bag) = self.entity_data.get(id) {
if let Some(name) = extract_name_from_bag(bag) { return name; }
let edge_label = choose_edge_label_bag(bag);
return edge_label;
}
format!("{}", id.0)
}
pub(crate) fn get_display_name(&self, id: &EntityId) -> String { self.get_label_for(id) }
pub(crate) fn get_children(&self, id: &EntityId) -> Vec<EntityId> {
let mut out: Vec<EntityId> = Vec::new();
if let Some(bag) = self.entity_data.get(id) {
if let Some(entry) = bag.get(c::STATE_CHILDREN) {
if let serde_json::Value::Array(arr) = &entry.value_json {
for v in arr.iter() {
if let Some(s) = v.as_str() {
if let Ok(u) = s.parse::<u64>() { out.push(EntityId(u)); }
}
}
}
}
}
out
}
pub(crate) fn get_parent(&self, id: &EntityId) -> Option<EntityId> {
for (candidate, _node) in self.nodes.iter() {
if self.get_children(candidate).contains(id) { return Some(*candidate); }
}
None
}
pub(crate) fn is_active(&self, id: &EntityId) -> bool {
self.active_nodes.contains(id)
}
pub(crate) fn set_active<I: IntoIterator<Item = EntityId>>(&mut self, ids: I) {
self.active_nodes.clear();
self.active_nodes.extend(ids);
}
}
impl fmt::Display for StateMachineGraph {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut names: HashMap<EntityId, String> = HashMap::new();
let mut get_node_name = |id: &EntityId| -> String {
if let Some(n) = names.get(id) { return n.clone(); }
let name = self.get_display_name(id);
names.insert(*id, name.clone());
name
};
let mut ordered: Vec<(EntityId, usize)> = Vec::new();
let mut stack: Vec<(EntityId, usize)> = Vec::new();
let mut visited: HashSet<EntityId> = HashSet::new();
if self.nodes.contains_key(&self.root) { stack.push((self.root, 0)); }
while let Some((id, depth)) = stack.pop() {
if !visited.insert(id) { continue; }
ordered.push((id, depth));
let mut kids = self.get_children(&id);
kids.reverse();
for child in kids.into_iter() { stack.push((child, depth + 1)); }
}
let mut edges_formatted: Vec<String> = Vec::new();
for (state, _depth) in &ordered {
if let Some(out_ids) = self.adjacency_out.get(state) {
for e_id in out_ids {
if let Some(edge) = self.edges.get(e_id) {
let source_name = get_node_name(&edge.source);
let mut target_name = get_node_name(&edge.target);
if target_name.is_empty() {
if let Some(s) = self.component_bag(&edge.id).and_then(|b| extract_name_from_bag(b)) {
if let Some(arrow) = s.find("->") {
let rhs = s[arrow+2..].trim();
let rhs = if let Some(paren) = rhs.find('(') { &rhs[..paren] } else { rhs };
let rhs = rhs.trim();
if !rhs.is_empty() { target_name = rhs.to_string(); }
}
}
}
let label = self.component_bag(&edge.id).map(choose_edge_label_bag).unwrap_or_else(|| "Edge".to_string());
edges_formatted.push(format!("{} - {} -> {}", source_name, label, target_name));
}
}
} else {
for edge in self.edges.values() {
if &edge.source == state {
let source_name = get_node_name(&edge.source);
let mut target_name = get_node_name(&edge.target);
if target_name.is_empty() {
if let Some(s) = self.component_bag(&edge.id).and_then(|b| extract_name_from_bag(b)) {
if let Some(arrow) = s.find("->") {
let rhs = s[arrow+2..].trim();
let rhs = if let Some(paren) = rhs.find('(') { &rhs[..paren] } else { rhs };
let rhs = rhs.trim();
if !rhs.is_empty() { target_name = rhs.to_string(); }
}
}
}
let label = self.component_bag(&edge.id).map(choose_edge_label_bag).unwrap_or_else(|| "Edge".to_string());
edges_formatted.push(format!("{} - {} -> {}", source_name, label, target_name));
}
}
}
}
let header = get_node_name(&self.root);
writeln!(f, "{}", header)?;
for (id, depth) in ordered.iter().skip(1) {
let name = get_node_name(id);
for _ in 0..*depth { write!(f, " ")?; }
writeln!(f, "{}", name)?;
}
writeln!(f)?;
for line in edges_formatted {
writeln!(f, "{}", line)?;
}
Ok(())
}
}
fn extract_name_from_bag(bag: &ComponentBag) -> Option<String> {
let val = bag.entries.get(c::NAME)?.value_json.clone();
if let Some(s) = val.as_str() { return Some(s.to_string()); }
if let JsonValue::Object(obj) = val {
for v in obj.values() { if let Some(s) = v.as_str() { return Some(s.to_string()); } }
}
None
}
pub(crate) fn choose_edge_label_bag(bag: &ComponentBag) -> String {
if let Some(name_val) = bag.entries.get(c::NAME).map(|e| e.value_json.clone()) {
if let Some(s) = name_val.as_str() {
let text = s.trim();
if !text.is_empty() { return text.to_string(); }
} else if let JsonValue::Object(obj) = name_val {
for v in obj.values() {
if let Some(s) = v.as_str() {
let text = s.trim();
if !text.is_empty() { return text.to_string(); }
}
}
}
}
let keys: HashSet<String> = bag.entries.keys().cloned().collect();
let mut event_edge_types: Vec<&String> = keys.iter().filter(|s| s.contains(c::MESSAGE_EDGE_SUBSTR)).collect();
event_edge_types.sort();
if let Some(ty) = event_edge_types.first() {
let s = ty.as_str();
if let (Some(start), Some(end)) = (s.find('<'), s.rfind('>')) {
if end > start + 1 {
let inner = &s[start + 1..end];
return simple_generic_name(inner);
}
}
if let Some(simple) = s.rsplit("::").next() { return simple.to_string(); }
return (*ty).clone();
}
if keys.contains(c::ALWAYS_EDGE) { return "Always".to_string(); }
"Edge".to_string()
}
fn simple_generic_name(s: &str) -> String {
let base = match s.find('<') {
Some(pos) => &s[..pos],
None => s,
};
base.rsplit("::").next().unwrap_or(base).to_string()
}