/** * fan-in 等待态(27-P4 T10):一个 pending 节点在等哪几个上游、各自是什么状态。 * * 并行(27-P4 T4/T6)落地之后一张图可能同时有好几个分支在跑,「这个节点为什么还没轮到」 * 不再是一眼能看出来的事——以前只有一条链,pending 的下一个必然是紧邻上游那一个; * 现在它可能在等三个并行分支里最慢的那一个。§4.3 决策要求「run 层面要能回答『我卡在等 * 哪几个节点』」,这里就是那句话的数据源。 * * 判据与引擎 `graph-engine.activateSuccessTargets`/`allIncomingSatisfied` 同源: * 只看 forward 的 success 入边——backward 边是异常流程,不参与「汇合点」这道闸, * fail 边同理。跟 io-model.js 的 inboundEdges 用的是同一条筛选条件,别改出分叉。 */ /** * @param {object} node getRunState 的合并节点(要 status 与 defNodeId 两个键) * @param {Array} nodes 同图全部节点(拿名字与上游状态用) * @param {Array} edges 同图全部边 * @returns {null | { * waiting: Array<{defNodeId, name, status}>, * total: number, done: number, satisfied: boolean, * isFanIn: boolean, // 2+ 条 forward success 入边才算真正的 fan-in;单一入边只是普通顺序等待 * blockedBy: Array<{defNodeId, name, status}>, * }} */ export function resolveJoinWait(node, nodes = [], edges = []) { if (!node || node.status !== 'pending') return null; const incoming = (edges || []).filter((e) => ( String(e.to) === String(node.defNodeId) && (e.condition || 'success') === 'success' && (e.edgeKind || 'forward') !== 'backward' )); if (!incoming.length) return null; // 没有 forward success 入边:不是在等 fan-in,是别的原因还没轮到 const byId = new Map((nodes || []).map((n) => [String(n.defNodeId), n])); const waiting = incoming.map((e) => { const src = byId.get(String(e.from)); return { defNodeId: e.from, name: (src && (src.name || src.defNodeId)) || e.from, status: (src && src.status) || 'pending', }; }); const done = waiting.filter((w) => w.status === 'success').length; return { waiting, total: waiting.length, done, satisfied: done === waiting.length, isFanIn: waiting.length > 1, blockedBy: waiting.filter((w) => w.status !== 'success'), }; } /** * 画布上的紧凑摘要——克制原则同 `edgeContractLabel`:只占一个小徽标的位置, * 完整明细留给 title。`satisfied` 时不显示(该节点这一刻就会被点亮,不算「在等」); * 非 fan-in(只有一条入边)时也不显示——普通顺序等待,看紧邻上游的状态就够了, * 加一个「1/1」徽标只会制造噪声。 */ export function joinWaitLabel(wait) { if (!wait || !wait.isFanIn || wait.satisfied) return null; return { text: `⏳${wait.done}/${wait.total}`, title: `在等:${wait.waiting.map((w) => `${w.name}(${w.status})`).join(' · ')}`, }; }