use ahash::AHashMap;
use rayon::prelude::*;
use super::phys_obj::AttrsError;
pub type ObjId = usize;
pub type InteractionId = usize;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InteractionOrder {
Ordered,
Unordered,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct InteractionNodes {
pub nodes: Box<[ObjId]>,
}
impl InteractionNodes {
pub fn from_slice(nodes: &[ObjId]) -> Self {
Self {
nodes: nodes.into(),
}
}
pub fn from_pair(i: ObjId, j: ObjId) -> Self {
Self {
nodes: Box::new([i, j]),
}
}
pub fn arity(&self) -> usize {
self.nodes.len()
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum InteractionError {
EmptyNodes,
InvalidObjId {
obj: ObjId,
n_objects: usize,
},
InvalidInteractionId {
id: InteractionId,
n_slots: usize,
},
OrderChangeCollision {
nodes: Box<[ObjId]>,
existing: InteractionId,
incoming: InteractionId,
},
ObjectCountWouldInvalidate {
n_objects: usize,
id: InteractionId,
obj: ObjId,
},
Attrs(
AttrsError,
),
}
impl From<AttrsError> for InteractionError {
fn from(value: AttrsError) -> Self {
Self::Attrs(value)
}
}
#[derive(Debug, Clone)]
pub struct InteractionTopology {
n_objects: usize,
order: InteractionOrder,
id_of_nodes: AHashMap<InteractionNodes, InteractionId>,
nodes_of_id: Vec<Option<InteractionNodes>>,
free_ids: Vec<InteractionId>,
}
impl InteractionTopology {
pub fn new(n_objects: usize) -> Self {
Self::with_order(n_objects, InteractionOrder::Unordered)
}
pub fn with_order(n_objects: usize, order: InteractionOrder) -> Self {
Self {
n_objects,
order,
id_of_nodes: AHashMap::new(),
nodes_of_id: Vec::new(),
free_ids: Vec::new(),
}
}
pub fn n_objects(&self) -> usize {
self.n_objects
}
pub fn order(&self) -> InteractionOrder {
self.order
}
pub fn len(&self) -> usize {
self.id_of_nodes.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn capacity_slots(&self) -> usize {
self.nodes_of_id.len()
}
pub fn free_slot_count(&self) -> usize {
self.free_ids.len()
}
pub fn set_order(&mut self, order: InteractionOrder) -> Result<(), InteractionError> {
if self.order == order {
return Ok(());
}
let mut rebuilt = AHashMap::with_capacity(self.id_of_nodes.len());
let mut rebuilt_nodes = self.nodes_of_id.clone();
for (id, maybe_nodes) in rebuilt_nodes.iter_mut().enumerate() {
let Some(nodes) = maybe_nodes else {
continue;
};
let canonical = canonicalize_nodes(&nodes.nodes, order);
let canonical_nodes = InteractionNodes::from_slice(&canonical);
if let Some(&existing) = rebuilt.get(&canonical_nodes) {
return Err(InteractionError::OrderChangeCollision {
nodes: canonical_nodes.nodes,
existing,
incoming: id,
});
}
*nodes = canonical_nodes.clone();
rebuilt.insert(canonical_nodes, id);
}
self.order = order;
self.id_of_nodes = rebuilt;
self.nodes_of_id = rebuilt_nodes;
Ok(())
}
pub fn set_n_objects(&mut self, n_objects: usize) -> Result<(), InteractionError> {
if n_objects >= self.n_objects {
self.n_objects = n_objects;
return Ok(());
}
for (id, nodes) in self.iter() {
if let Some(&obj) = nodes.nodes.iter().find(|&&obj| obj >= n_objects) {
return Err(InteractionError::ObjectCountWouldInvalidate { n_objects, id, obj });
}
}
self.n_objects = n_objects;
Ok(())
}
pub fn reserve(&mut self, additional: usize) {
self.id_of_nodes.reserve(additional);
self.nodes_of_id.reserve(additional);
}
pub fn prune_n_objects(&mut self, n_objects: usize) -> Vec<InteractionId> {
self.n_objects = n_objects;
let ids_to_remove = self
.nodes_of_id
.iter()
.enumerate()
.filter_map(|(id, maybe_nodes)| {
let nodes = maybe_nodes.as_ref()?;
nodes
.nodes
.iter()
.any(|&obj| obj >= n_objects)
.then_some(id)
})
.collect::<Vec<_>>();
for id in ids_to_remove.iter().copied() {
self.remove_by_id_unchecked(id);
}
ids_to_remove
}
pub fn nodes_of(&self, id: InteractionId) -> Result<&InteractionNodes, InteractionError> {
self.nodes_of_id
.get(id)
.and_then(|nodes| nodes.as_ref())
.ok_or(InteractionError::InvalidInteractionId {
id,
n_slots: self.nodes_of_id.len(),
})
}
pub fn id_of(&self, nodes: &[ObjId]) -> Result<Option<InteractionId>, InteractionError> {
let nodes = self.nodes_from_slice(nodes)?;
Ok(self.id_of_nodes.get(&nodes).copied())
}
pub fn contains(&self, nodes: &[ObjId]) -> Result<bool, InteractionError> {
Ok(self.id_of(nodes)?.is_some())
}
pub fn add(&mut self, nodes: &[ObjId]) -> Result<InteractionId, InteractionError> {
let nodes = self.nodes_from_slice(nodes)?;
Ok(self.add_nodes(nodes))
}
fn add_nodes(&mut self, nodes: InteractionNodes) -> InteractionId {
if let Some(&id) = self.id_of_nodes.get(&nodes) {
return id;
}
let id = if let Some(id) = self.free_ids.pop() {
self.nodes_of_id[id] = Some(nodes.clone());
id
} else {
let id = self.nodes_of_id.len();
self.nodes_of_id.push(Some(nodes.clone()));
id
};
self.id_of_nodes.insert(nodes, id);
id
}
pub fn remove(&mut self, nodes: &[ObjId]) -> Result<Option<InteractionId>, InteractionError> {
let nodes = self.nodes_from_slice(nodes)?;
Ok(self.remove_nodes(nodes))
}
fn remove_nodes(&mut self, nodes: InteractionNodes) -> Option<InteractionId> {
let Some(id) = self.id_of_nodes.remove(&nodes) else {
return None;
};
self.nodes_of_id[id] = None;
self.free_ids.push(id);
Some(id)
}
pub fn clear(&mut self) {
self.id_of_nodes.clear();
self.free_ids.clear();
self.free_ids.extend((0..self.nodes_of_id.len()).rev());
for slot in self.nodes_of_id.iter_mut() {
*slot = None;
}
}
pub fn iter(&self) -> impl Iterator<Item = (InteractionId, &InteractionNodes)> + '_ {
self.nodes_of_id
.iter()
.enumerate()
.filter_map(|(id, nodes)| nodes.as_ref().map(|nodes| (id, nodes)))
}
pub fn id_of_pair(
&self,
i: ObjId,
j: ObjId,
) -> Result<Option<InteractionId>, InteractionError> {
let nodes = self.nodes_from_pair(i, j)?;
Ok(self.id_of_nodes.get(&nodes).copied())
}
pub fn add_pair(&mut self, i: ObjId, j: ObjId) -> Result<InteractionId, InteractionError> {
let nodes = self.nodes_from_pair(i, j)?;
Ok(self.add_nodes(nodes))
}
pub fn remove_pair(
&mut self,
i: ObjId,
j: ObjId,
) -> Result<Option<InteractionId>, InteractionError> {
let nodes = self.nodes_from_pair(i, j)?;
Ok(self.remove_nodes(nodes))
}
fn nodes_from_slice(&self, nodes: &[ObjId]) -> Result<InteractionNodes, InteractionError> {
self.validate_nodes(nodes)?;
Ok(InteractionNodes::from_slice(&canonicalize_nodes(
nodes, self.order,
)))
}
fn nodes_from_pair(&self, i: ObjId, j: ObjId) -> Result<InteractionNodes, InteractionError> {
self.validate_obj(i)?;
self.validate_obj(j)?;
let (i, j) = if self.order == InteractionOrder::Unordered && j < i {
(j, i)
} else {
(i, j)
};
Ok(InteractionNodes::from_pair(i, j))
}
fn validate_nodes(&self, nodes: &[ObjId]) -> Result<(), InteractionError> {
if nodes.is_empty() {
return Err(InteractionError::EmptyNodes);
}
for &obj in nodes {
self.validate_obj(obj)?;
}
Ok(())
}
fn validate_obj(&self, obj: ObjId) -> Result<(), InteractionError> {
if obj >= self.n_objects {
return Err(InteractionError::InvalidObjId {
obj,
n_objects: self.n_objects,
});
}
Ok(())
}
fn remove_by_id_unchecked(&mut self, id: InteractionId) {
if let Some(nodes) = self.nodes_of_id.get_mut(id).and_then(|slot| slot.take()) {
self.id_of_nodes.remove(&nodes);
self.free_ids.push(id);
}
}
}
fn canonicalize_nodes(nodes: &[ObjId], order: InteractionOrder) -> Vec<ObjId> {
if order == InteractionOrder::Unordered {
let mut canonical = nodes.to_vec();
canonical.sort_unstable();
canonical
} else {
nodes.to_vec()
}
}
#[derive(Debug, Clone)]
struct PayloadStore<T> {
slots: Vec<Option<T>>,
free_ids: Vec<InteractionId>,
active_count: usize,
}
impl<T> Default for PayloadStore<T> {
fn default() -> Self {
Self {
slots: Vec::new(),
free_ids: Vec::new(),
active_count: 0,
}
}
}
impl<T> PayloadStore<T> {
pub fn new() -> Self {
Self::default()
}
pub fn capacity_slots(&self) -> usize {
self.slots.len()
}
pub fn reserve(&mut self, additional: usize) {
self.slots.reserve(additional);
}
pub fn set(&mut self, id: InteractionId, payload: T) {
if id >= self.slots.len() {
let old_len = self.slots.len();
self.slots.resize_with(id + 1, || None);
self.free_ids.extend((old_len..id).rev());
}
if self.slots[id].is_none() {
self.active_count += 1;
self.retain_free_id(id);
}
self.slots[id] = Some(payload);
}
pub fn remove(&mut self, id: InteractionId) -> Option<T> {
let slot = self.slots.get_mut(id)?;
let removed = slot.take();
if removed.is_some() {
self.active_count -= 1;
self.free_ids.push(id);
}
removed
}
pub fn get(&self, id: InteractionId) -> Option<&T> {
self.slots.get(id).and_then(|x| x.as_ref())
}
pub fn get_mut(&mut self, id: InteractionId) -> Option<&mut T> {
self.slots.get_mut(id).and_then(|x| x.as_mut())
}
pub fn clear(&mut self) {
self.active_count = 0;
self.free_ids.clear();
self.free_ids.extend((0..self.slots.len()).rev());
for slot in self.slots.iter_mut() {
*slot = None;
}
}
pub fn par_for_each<F>(&self, f: F)
where
T: Sync,
F: Fn(InteractionId, &T) + Send + Sync,
{
self.slots.par_iter().enumerate().for_each(|(id, slot)| {
if let Some(payload) = slot.as_ref() {
f(id, payload);
}
});
}
pub fn par_for_each_mut<F>(&mut self, f: F)
where
T: Send,
F: Fn(InteractionId, &mut T) + Send + Sync,
{
self.slots
.par_iter_mut()
.enumerate()
.for_each(|(id, slot)| {
if let Some(payload) = slot.as_mut() {
f(id, payload);
}
});
}
fn retain_free_id(&mut self, id: InteractionId) {
if let Some(pos) = self.free_ids.iter().position(|&x| x == id) {
self.free_ids.swap_remove(pos);
}
}
}
#[derive(Debug, Clone)]
pub struct Interaction<T> {
topology: InteractionTopology,
payloads: PayloadStore<T>,
}
impl<T> Interaction<T> {
pub fn new(n_objects: usize, order: InteractionOrder) -> Self {
Self {
topology: InteractionTopology::with_order(n_objects, order),
payloads: PayloadStore::new(),
}
}
pub fn with_topology(topology: InteractionTopology) -> Self {
Self {
topology,
payloads: PayloadStore::new(),
}
}
pub fn topology(&self) -> &InteractionTopology {
&self.topology
}
pub fn topology_mut(&mut self) -> &mut InteractionTopology {
&mut self.topology
}
pub fn len(&self) -> usize {
self.payloads.active_count
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn n_objects(&self) -> usize {
self.topology.n_objects()
}
pub fn order(&self) -> InteractionOrder {
self.topology.order()
}
pub fn capacity_slots(&self) -> usize {
self.topology.capacity_slots()
}
pub fn reserve(&mut self, additional: usize) {
self.topology.reserve(additional);
self.payloads.reserve(additional);
}
pub fn set_order(&mut self, order: InteractionOrder) -> Result<(), InteractionError> {
self.topology.set_order(order)
}
pub fn set_n_objects(&mut self, n_objects: usize) -> Result<(), InteractionError> {
self.topology.set_n_objects(n_objects)
}
pub fn prune_n_objects(&mut self, n_objects: usize) -> Vec<(InteractionId, T)> {
self.topology
.prune_n_objects(n_objects)
.into_iter()
.filter_map(|id| self.payloads.remove(id).map(|payload| (id, payload)))
.collect()
}
pub fn contains(&self, nodes: &[ObjId]) -> Result<bool, InteractionError> {
let Some(id) = self.topology.id_of(nodes)? else {
return Ok(false);
};
Ok(self.payloads.get(id).is_some())
}
pub fn set(&mut self, nodes: &[ObjId], payload: T) -> Result<InteractionId, InteractionError> {
let id = self.topology.add(nodes)?;
self.payloads.set(id, payload);
Ok(id)
}
pub fn remove(
&mut self,
nodes: &[ObjId],
) -> Result<Option<(InteractionId, T)>, InteractionError> {
let Some(id) = self.topology.remove(nodes)? else {
return Ok(None);
};
let payload = self
.payloads
.remove(id)
.ok_or(InteractionError::InvalidInteractionId {
id,
n_slots: self.payloads.capacity_slots(),
})?;
Ok(Some((id, payload)))
}
pub fn get(&self, nodes: &[ObjId]) -> Result<Option<&T>, InteractionError> {
let Some(id) = self.topology.id_of(nodes)? else {
return Ok(None);
};
Ok(self.payloads.get(id))
}
pub fn get_mut(&mut self, nodes: &[ObjId]) -> Result<Option<&mut T>, InteractionError> {
let Some(id) = self.topology.id_of(nodes)? else {
return Ok(None);
};
Ok(self.payloads.get_mut(id))
}
pub fn set_pair(
&mut self,
i: ObjId,
j: ObjId,
payload: T,
) -> Result<InteractionId, InteractionError> {
let id = self.topology.add_pair(i, j)?;
self.payloads.set(id, payload);
Ok(id)
}
pub fn get_pair(&self, i: ObjId, j: ObjId) -> Result<Option<&T>, InteractionError> {
self.get(&[i, j])
}
pub fn get_pair_mut(&mut self, i: ObjId, j: ObjId) -> Result<Option<&mut T>, InteractionError> {
self.get_mut(&[i, j])
}
pub fn remove_pair(
&mut self,
i: ObjId,
j: ObjId,
) -> Result<Option<(InteractionId, T)>, InteractionError> {
self.remove(&[i, j])
}
pub fn clear(&mut self) {
self.topology.clear();
self.payloads.clear();
}
pub fn iter(&self) -> impl Iterator<Item = (InteractionId, &InteractionNodes, &T)> {
self.topology
.iter()
.filter_map(|(id, nodes)| self.payloads.get(id).map(|payload| (id, nodes, payload)))
}
pub fn par_for_each_payload_mut<F>(&mut self, f: F)
where
T: Send,
F: Fn(InteractionId, &mut T) + Send + Sync,
{
self.payloads.par_for_each_mut(f);
}
pub fn par_for_each_payload<F>(&self, f: F)
where
T: Sync,
F: Fn(InteractionId, &T) + Send + Sync,
{
self.payloads.par_for_each(f);
}
pub fn par_for_each<F>(&self, f: F)
where
T: Sync,
F: Fn(InteractionId, &InteractionNodes, &T) + Send + Sync,
{
self.payloads.par_for_each(|id, payload| {
let nodes = self.topology.nodes_of(id).expect(
"interaction topology/payload storage out of sync while parallel iterating active entries",
);
f(id, nodes, payload);
});
}
}