use std::collections::{HashMap, HashSet};
use fbxcel::{
low::v7400::AttributeValue,
tree::v7400::{NodeHandle, NodeId, Tree},
};
use log::{trace, warn};
use string_interner::{DefaultBackend, StringInterner};
use crate::v7400::{
connection::{ConnectedNodeType, Connection, ConnectionIndex, ConnectionLabelSym},
error::{
connection::ConnectionError,
load::{LoadError, StructureError},
},
object::ObjectId,
};
#[derive(Debug, Clone)]
pub(crate) struct ConnectionsCache {
connections: Vec<Connection>,
labels: StringInterner<DefaultBackend<ConnectionLabelSym>>,
conn_indices_by_src: HashMap<ObjectId, Vec<ConnectionIndex>>,
conn_indices_by_dest: HashMap<ObjectId, Vec<ConnectionIndex>>,
}
impl ConnectionsCache {
pub(crate) fn from_tree(tree: &Tree) -> Result<Self, LoadError> {
ConnectionsCacheBuilder::default().load(tree)
}
pub(crate) fn resolve_label(&self, sym: ConnectionLabelSym) -> &str {
self.labels.resolve(sym).unwrap_or_else(|| {
panic!(
"The given connection label symbol is not registered: sym={:?}",
sym
)
})
}
pub(crate) fn outgoing_connections(
&self,
source: ObjectId,
) -> impl Iterator<Item = &Connection> {
self.conn_indices_by_src
.get(&source)
.into_iter()
.flatten()
.map(move |index| &self.connections[index.value()])
}
pub(crate) fn incoming_connections(
&self,
destination: ObjectId,
) -> impl Iterator<Item = &Connection> {
self.conn_indices_by_dest
.get(&destination)
.into_iter()
.flatten()
.map(move |index| &self.connections[index.value()])
}
}
#[derive(Debug)]
struct ConnectionsCacheBuilder {
connections: Vec<(NodeId, Connection)>,
labels: StringInterner<DefaultBackend<ConnectionLabelSym>>,
conn_indices_by_src: HashMap<ObjectId, Vec<ConnectionIndex>>,
conn_indices_by_dest: HashMap<ObjectId, Vec<ConnectionIndex>>,
conn_set: HashSet<(ObjectId, ObjectId, Option<ConnectionLabelSym>)>,
}
impl ConnectionsCacheBuilder {
fn load(mut self, tree: &Tree) -> Result<ConnectionsCache, LoadError> {
let connections_node = tree
.root()
.children_by_name("Connections")
.next()
.ok_or(StructureError::MissingConnectionsNode)?;
for conn_node in connections_node.children_by_name("C") {
self.add_connection(conn_node)?;
}
Ok(self.build())
}
pub(crate) fn add_connection(&mut self, node: NodeHandle<'_>) -> Result<(), ConnectionError> {
let index = ConnectionIndex::new(self.connections.len());
let conn = self.load_connection(node, index)?;
if self
.conn_set
.insert((conn.source_id(), conn.destination_id(), conn.label_sym()))
{
self.connections.push((node.node_id(), conn));
self.conn_indices_by_src
.entry(conn.source_id())
.or_insert_with(Vec::new)
.push(index);
self.conn_indices_by_dest
.entry(conn.destination_id())
.or_insert_with(Vec::new)
.push(index);
} else {
let old_conn = self
.conn_indices_by_src
.get(&conn.source_id())
.expect("Should never fail: entry should exist")
.iter()
.map(|index| &self.connections[index.value()])
.find(|(_, old_conn)| {
old_conn.destination_id() == conn.destination_id()
&& old_conn.label_sym() == conn.label_sym()
})
.expect("Should never fail: entry should exist");
warn!(
"Found duplicated connection node, skipping (node_id={:?}): \
source_id={:?}, destination_id={:?}, label={:?}, first_conn={:?}",
node.node_id(),
conn.source_id(),
conn.destination_id(),
conn.label_sym()
.map(|sym| {
self.labels.resolve(sym).expect(
"Should never fail: connection label symbol should be registered",
)
})
.map(ToOwned::to_owned),
old_conn.0
);
return Ok(());
}
trace!(
"Loaded connection successfully: node_id={:?}, index={:?}",
node.node_id(),
index
);
Ok(())
}
pub(crate) fn load_connection(
&mut self,
node: NodeHandle<'_>,
index: ConnectionIndex,
) -> Result<Connection, ConnectionError> {
trace!(
"Loading connection, node_id={:?}, index={:?}",
node.node_id(),
index
);
let attrs = node.attributes();
let nodes_ty_str = attrs
.get(0)
.ok_or_else(|| ConnectionError::MissingNodeTypes(node.node_id(), index))?
.get_string_or_type()
.map_err(|ty| ConnectionError::InvalidNodeTypesType(node.node_id(), index, ty))?;
let (destination_type, source_type) = match nodes_ty_str {
"OO" => (ConnectedNodeType::Object, ConnectedNodeType::Object),
"OP" => (ConnectedNodeType::Object, ConnectedNodeType::Property),
"PO" => (ConnectedNodeType::Property, ConnectedNodeType::Object),
"PP" => (ConnectedNodeType::Property, ConnectedNodeType::Property),
v => {
return Err(ConnectionError::InvalidNodeTypesValue(
node.node_id(),
index,
v.to_owned(),
));
}
};
let source_id = attrs
.get(1)
.ok_or_else(|| ConnectionError::MissingSourceId(node.node_id(), index))?
.get_i64_or_type()
.map(ObjectId::new)
.map_err(|ty| ConnectionError::InvalidSourceIdType(node.node_id(), index, ty))?;
let destination_id = attrs
.get(2)
.ok_or_else(|| ConnectionError::MissingDestinationId(node.node_id(), index))?
.get_i64_or_type()
.map(ObjectId::new)
.map_err(|ty| ConnectionError::InvalidDestinationIdType(node.node_id(), index, ty))?;
let label = attrs
.get(3)
.map(AttributeValue::get_string_or_type)
.transpose()
.map_err(|ty| ConnectionError::InvalidLabelType(node.node_id(), index, ty))?;
let label_sym = label.map(|s| self.labels.get_or_intern(s));
trace!(
"Successfully loaded connection: node_id={:?}, index={:?}, \
dst_type={:?}, src_type={:?}, src_id={:?}, dest_id={:?}, label={:?}",
node.node_id(),
index,
destination_type,
source_type,
source_id,
destination_id,
label
);
Ok(Connection::new(
source_id,
source_type,
destination_id,
destination_type,
label_sym,
index,
))
}
fn build(self) -> ConnectionsCache {
ConnectionsCache {
connections: self
.connections
.into_iter()
.map(|(_node_id, conn)| conn)
.collect(),
labels: self.labels,
conn_indices_by_src: self.conn_indices_by_src,
conn_indices_by_dest: self.conn_indices_by_dest,
}
}
}
impl Default for ConnectionsCacheBuilder {
fn default() -> Self {
Self {
connections: Default::default(),
labels: StringInterner::new(),
conn_indices_by_src: Default::default(),
conn_indices_by_dest: Default::default(),
conn_set: Default::default(),
}
}
}