Integrate typed ports and schema 3 migration

# Conflicts:
#	crates/oa-core/src/projection.rs
This commit is contained in:
2026-09-05 18:26:48 +03:00
34 changed files with 9915 additions and 408 deletions
+72 -13
View File
@@ -20,6 +20,7 @@ pub struct ProjectedEdge {
pub source: Id,
pub target: Id,
pub trace: bool,
pub oriented: bool,
pub source_port: Option<Id>,
pub target_port: Option<Id>,
}
@@ -29,7 +30,36 @@ pub struct ProjectedPort {
/// Placement identity of the owning visible function/participant.
pub owner: Id,
pub position: Position,
pub direction: PortDirection,
pub side: PortAnchorSide,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PortAnchorSide {
Left,
Right,
Bottom,
}
pub fn port_anchor_side(e: &Element) -> Option<PortAnchorSide> {
let Some(SharedBody::Port { contract, legacy }) = e.shared() else {
return None;
};
Some(match contract {
PortContract::FunctionInput { .. } => PortAnchorSide::Left,
PortContract::FunctionOutput { .. } => PortAnchorSide::Right,
PortContract::Component {
kind: ComponentPortKind::Flow,
orientation: ComponentPortOrientation::In,
} => PortAnchorSide::Left,
PortContract::Component {
kind: ComponentPortKind::Flow,
orientation: ComponentPortOrientation::Out,
} => PortAnchorSide::Right,
PortContract::Legacy => match legacy.as_ref().map(|l| l.direction) {
Some(PortDirection::In) => PortAnchorSide::Left,
Some(PortDirection::Out) => PortAnchorSide::Right,
_ => PortAnchorSide::Bottom,
},
_ => PortAnchorSide::Bottom,
})
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
pub struct ProjectedView {
@@ -38,10 +68,25 @@ pub struct ProjectedView {
pub ports: Vec<ProjectedPort>,
}
pub(crate) fn minimum_node_size(p: &Project, element: Id, mut size: Size) -> Size {
let count = [PortDirection::In, PortDirection::Out].into_iter().map(|direction| p.children(element).filter(|port| matches!(port.shared(), Some(SharedBody::Port {direction:d,..}) if *d==direction)).count()).max().unwrap_or(0);
let count = [PortAnchorSide::Left, PortAnchorSide::Right]
.into_iter()
.map(|side| {
p.children(element)
.filter(|port| port_anchor_side(port) == Some(side))
.count()
})
.max()
.unwrap_or(0);
if count > 0 {
size.height = size.height.max(76. + 28. * (count + 1) as f32);
}
let bottom = p
.children(element)
.filter(|port| port_anchor_side(port) == Some(PortAnchorSide::Bottom))
.count();
if bottom > 0 {
size.width = size.width.max(28. * (bottom + 1) as f32);
}
size
}
pub(crate) fn port_footer(p: &Project, element: Id) -> f32 {
@@ -250,8 +295,15 @@ pub fn project_diagram(p: &Project, d: &Diagram) -> ProjectedView {
.filter(|e| e.kind() == Kind::Port)
.collect();
children.sort_by_key(|e| (&e.alias, e.id));
for direction in [PortDirection::In, PortDirection::Out, PortDirection::InOut] {
let side: Vec<_> = children.iter().filter(|e| matches!(e.shared(), Some(SharedBody::Port {direction: d, ..}) if *d == direction)).collect();
for anchor_side in [
PortAnchorSide::Left,
PortAnchorSide::Right,
PortAnchorSide::Bottom,
] {
let side: Vec<_> = children
.iter()
.filter(|e| port_anchor_side(e) == Some(anchor_side))
.collect();
for (index, port) in side.iter().enumerate() {
let fraction = (index + 1) as f32 / (side.len() + 1) as f32;
let y = if p.elements[&node.element].kind().is_performer()
@@ -263,16 +315,16 @@ pub fn project_diagram(p: &Project, d: &Diagram) -> ProjectedView {
} else {
node.position.y + 64. + (node.size.height - 76.).max(0.) * fraction
};
let position = match direction {
PortDirection::In => Position {
let position = match anchor_side {
PortAnchorSide::Left => Position {
x: node.position.x,
y,
},
PortDirection::Out => Position {
PortAnchorSide::Right => Position {
x: node.position.x + node.size.width,
y,
},
PortDirection::InOut => Position {
PortAnchorSide::Bottom => Position {
x: node.position.x + node.size.width * fraction,
y: node.position.y + node.size.height,
},
@@ -281,7 +333,7 @@ pub fn project_diagram(p: &Project, d: &Diagram) -> ProjectedView {
element: port.id,
owner: node.id,
position,
direction,
side: anchor_side,
});
}
}
@@ -324,10 +376,9 @@ pub fn project_diagram(p: &Project, d: &Diagram) -> ProjectedView {
source_port,
target_port,
..
} => (*source_port, *target_port),
RelationBody::BoundaryExchange { source, target, .. } => {
(Some(*source), Some(*target))
}
} => (Some(*source_port), Some(*target_port)),
RelationBody::BoundaryExchange { source, target, .. }
| RelationBody::PortAllocation { source, target } => (Some(*source), Some(*target)),
_ => (None, None),
};
edges.push(ProjectedEdge {
@@ -335,6 +386,13 @@ pub fn project_diagram(p: &Project, d: &Diagram) -> ProjectedView {
source,
target,
trace: false,
oriented: !matches!(
&r.body,
RelationBody::BoundaryExchange {
oriented: false,
..
}
),
source_port: source_port.filter(|id| ports.iter().any(|p| p.element == *id)),
target_port: target_port.filter(|id| ports.iter().any(|p| p.element == *id)),
});
@@ -350,6 +408,7 @@ pub fn project_diagram(p: &Project, d: &Diagram) -> ProjectedView {
source,
target,
trace: true,
oriented: true,
source_port: None,
target_port: None,
});