use std::collections::HashSet;
use ifc_model::{EntityId, Model, Value};
use crate::error::ScheduleReadError;
use crate::release::ReadRelease;
const SEQUENCE: &str = "IFCRELSEQUENCE";
const LAG_TIME: &str = "IFCLAGTIME";
pub(crate) mod slot {
pub const RELATING: usize = 4;
pub const RELATED: usize = 5;
pub const TIME_LAG: usize = 6;
pub const SEQUENCE_TYPE: usize = 7;
}
pub mod lag_slot {
pub const LAG_VALUE: usize = 3;
pub const DURATION_TYPE: usize = 4;
}
pub const MAX_SEQUENCE_DEPTH: usize = 4096;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum SequenceType {
StartStart,
StartFinish,
FinishStart,
FinishFinish,
UserDefined,
NotDefined,
}
impl SequenceType {
fn parse(token: &str) -> Option<Self> {
Some(match token {
"START_START" => Self::StartStart,
"START_FINISH" => Self::StartFinish,
"FINISH_START" => Self::FinishStart,
"FINISH_FINISH" => Self::FinishFinish,
"USERDEFINED" => Self::UserDefined,
"NOTDEFINED" => Self::NotDefined,
_ => return None,
})
}
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct Lag {
pub id: EntityId,
pub duration: Option<String>,
pub ratio: Option<f64>,
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct Sequence {
pub id: EntityId,
pub predecessor: EntityId,
pub successor: EntityId,
pub sequence_type: Option<SequenceType>,
pub lag: Option<Lag>,
pub time_lag_measure: Option<f64>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct SequenceCycle {
pub repeated: EntityId,
pub path: Vec<EntityId>,
}
pub fn sequences(model: &Model) -> Result<Vec<Sequence>, ScheduleReadError> {
let release = ReadRelease::of(model)?;
let mut out = Vec::new();
for (id, entity) in model.of_type(SEQUENCE) {
let (Some(predecessor), Some(successor)) = (
release.reference(SEQUENCE, entity, "RelatingProcess"),
release.reference(SEQUENCE, entity, "RelatedProcess"),
) else {
continue;
};
let sequence_type = release
.token(SEQUENCE, entity, "SequenceType")
.and_then(SequenceType::parse);
let (lag, time_lag_measure) = match release.value(SEQUENCE, entity, "TimeLag") {
Some(Value::Ref(lag_id)) => (read_lag(model, release, *lag_id), None),
Some(value) => (None, value.unwrap_typed().as_f64()),
None => (None, None),
};
out.push(Sequence {
id,
predecessor,
successor,
sequence_type,
lag,
time_lag_measure,
});
}
Ok(out)
}
fn read_lag(model: &Model, release: ReadRelease, id: EntityId) -> Option<Lag> {
let entity = model.get(id)?;
if !entity.type_name.eq_ignore_ascii_case(LAG_TIME) {
return None;
}
let value = release.value(LAG_TIME, entity, "LagValue");
let duration = value
.and_then(|v| v.unwrap_typed().as_text())
.map(str::to_string);
let ratio = if duration.is_some() {
None
} else {
value.and_then(|v| v.unwrap_typed().as_f64())
};
Some(Lag {
id,
duration,
ratio,
})
}
pub fn predecessors_of(model: &Model, task: EntityId) -> Result<Vec<EntityId>, ScheduleReadError> {
Ok(sequences(model)?
.into_iter()
.filter(|s| s.successor == task)
.map(|s| s.predecessor)
.collect())
}
pub fn successors_of(model: &Model, task: EntityId) -> Result<Vec<EntityId>, ScheduleReadError> {
Ok(sequences(model)?
.into_iter()
.filter(|s| s.predecessor == task)
.map(|s| s.successor)
.collect())
}
pub fn downstream_of(model: &Model, task: EntityId) -> Result<Vec<EntityId>, ScheduleReadError> {
let all = sequences(model)?;
Ok(downstream_in(&all, task)?)
}
fn downstream_in(all: &[Sequence], task: EntityId) -> Result<Vec<EntityId>, SequenceCycle> {
let mut out = Vec::new();
let mut path = Vec::new();
let mut on_path = HashSet::new();
let mut seen = HashSet::new();
walk(all, task, &mut out, &mut path, &mut on_path, &mut seen)?;
Ok(out)
}
fn walk(
all: &[Sequence],
node: EntityId,
out: &mut Vec<EntityId>,
path: &mut Vec<EntityId>,
on_path: &mut HashSet<EntityId>,
seen: &mut HashSet<EntityId>,
) -> Result<(), SequenceCycle> {
if path.len() >= MAX_SEQUENCE_DEPTH {
return Ok(());
}
path.push(node);
on_path.insert(node);
for successor in all
.iter()
.filter(|s| s.predecessor == node)
.map(|s| s.successor)
{
if on_path.contains(&successor) {
let mut cycle = path.clone();
cycle.push(successor);
return Err(SequenceCycle {
repeated: successor,
path: cycle,
});
}
if seen.insert(successor) {
out.push(successor);
walk(all, successor, out, path, on_path, seen)?;
}
}
path.pop();
on_path.remove(&node);
Ok(())
}
pub fn find_cycle(model: &Model) -> Result<Option<SequenceCycle>, ScheduleReadError> {
let all = sequences(model)?;
for (id, _) in model.of_type("IFCTASK") {
if let Err(cycle) = downstream_in(&all, id) {
return Ok(Some(cycle));
}
}
Ok(None)
}