///|
priv struct PendingFrame {
frame : @sequence.Frame
path : ReadOnlyArray[Int32]
sections : Array[Array[Control]]
}
///|
priv struct EventJob {
event : @sequence.Event
path : Array[Int32]
parent : Int32
section : Int32
depth : Int32
}
///|
fn frame_label(kind : @sequence.FrameKind) -> String {
match kind {
Loop => "loop"
Alt => "alt"
Opt => "opt"
Par => "par"
ParOver => "par_over"
Critical => "critical"
Break => "break"
Rect => "rect"
}
}
///|
/// Message vertices are occurrences, not participants. Frame branches remain
/// explicit; a loop contributes one authored structural iteration, never a
/// participant cycle or an inferred unbounded execution.
pub fn Catalog::sequence(
sequence : @sequence.Sequence,
view_id~ : String = "sequence",
limits~ : Limits = Limits::new(),
) -> CatalogResult {
let build = builder(Sequence(sequence), view_id, limits)
let budget = build.budget
if budget.failed {
return build.finish()
}
let participants = Map([])
for participant in sequence.participants {
if !budget.charge(participant.id.length() + 1) {
return build.finish()
}
if participants.contains(participant.id) {
budget.error(
"animation/invalid-identity", "Duplicate sequence participant identity",
)
return build.finish()
}
participants[participant.id] = true
}
if !budget.charge(sequence.events.length()) {
return build.finish()
}
let pending : Array[PendingFrame] = []
let jobs : Array[EventJob] = []
for reversed in 0.. Unit {
if parent < 0 {
build.controls.push(control)
} else {
pending[parent].sections[section].push(control)
}
}
let start = "control:\{view_id.length()}:\{view_id}:start"
let end = "control:\{view_id.length()}:\{view_id}:end"
build.node({
id: start,
label: "Start",
kind: Start,
occurrence: None,
source_node: None,
swimlane_node: None,
})
build.node({
id: end,
label: "End",
kind: End,
occurrence: None,
source_node: None,
swimlane_node: None,
})
while jobs.pop() is Some(job) {
if !budget.charge(1 + job.path.length()) {
return build.finish()
}
if job.depth > limits.depth {
budget.error(
"animation/plan-limit", "Sequence control depth limit exceeded",
)
return build.finish()
}
match job.event {
Message(message) => {
if !budget.charge(
message.sender.length() +
message.receiver.length() +
message.label.length(),
) {
return build.finish()
}
if !participants.contains(message.sender) ||
!participants.contains(message.receiver) {
budget.error(
"animation/invalid-identity", "Message names a missing participant",
)
return build.finish()
}
let index = build.occurrences.length()
let key = path_key("sequence:\{view_id.length()}:\{view_id}:", job.path)
build.occurrence({
key,
index,
kind: SequenceMessage,
path: ReadOnlyArray::from_array(job.path),
source: None,
flow: None,
message: Some(message),
swimlane_connection: None,
})
build.node({
id: key,
label: message.label,
kind: Message,
occurrence: Some(key),
source_node: None,
swimlane_node: None,
})
append(Message(index), job.parent, job.section)
}
Frame(frame) => {
let index = pending.length()
let key = path_key("frame:\{view_id.length()}:\{view_id}:", job.path)
if !budget.charge(frame.sections.length()) {
return build.finish()
}
pending.push({
frame,
path: ReadOnlyArray::from_array(job.path),
sections: Array::makei(frame.sections.length(), _ => []),
})
append(Frame(index), job.parent, job.section)
build.node({
id: key + ":in",
label: frame_label(frame.kind),
kind: FrameEnter,
occurrence: None,
source_node: None,
swimlane_node: None,
})
build.node({
id: key + ":out",
label: frame_label(frame.kind),
kind: FrameExit,
occurrence: None,
source_node: None,
swimlane_node: None,
})
for reversed in 0.. l.length()).unwrap_or(0) +
1,
) {
return build.finish()
}
for reversed in 0.. ()
}
if budget.failed {
return build.finish()
}
}
for frame in pending {
let sections = Array::makei(frame.sections.length(), i => SectionInfo::{
label: frame.frame.sections[i].label,
controls: ReadOnlyArray::from_array(frame.sections[i]),
})
build.frames.push({
key: path_key("frame:\{view_id.length()}:\{view_id}:", frame.path),
path: frame.path,
kind: frame.frame.kind,
sections: ReadOnlyArray::from_array(sections),
})
}
fn ordered(
controls : ArrayView[Control],
start : String,
end : String,
) -> Unit {
let mut previous = start
for control in controls {
if !budget.charge(1) {
return
}
let (enter, exit) = match control {
Message(i) => (build.occurrences[i].key, build.occurrences[i].key)
Frame(i) => (build.frames[i].key + ":in", build.frames[i].key + ":out")
}
build.edge({
kind: Order,
source: previous,
target: enter,
occurrence: None,
reversed: false,
})
previous = exit
}
build.edge({
kind: Order,
source: previous,
target: end,
occurrence: None,
reversed: false,
})
}
ordered(build.controls, start, end)
for frame in build.frames {
let enter = frame.key + ":in"
let exit = frame.key + ":out"
if frame.kind == Alt ||
frame.kind == Opt ||
frame.kind == Par ||
frame.kind == ParOver {
for section in frame.sections {
ordered(section.controls, enter, exit)
}
if frame.kind == Opt {
build.edge({
kind: Order,
source: enter,
target: exit,
occurrence: None,
reversed: false,
})
}
} else {
let controls = []
for section in frame.sections {
controls.append(section.controls)
}
ordered(controls, enter, exit)
}
}
build.finish()
}