refactor(state-machine): support multiple lifecycles per blueprint
Blueprint.stateMachine becomes stateMachines[], the viewer renders each machine in its own panel, and StateMachine draws self-loops (a === b) for recurring transitions that don't change state. Existing blueprints move to the array form unchanged.
This commit is contained in:
@@ -164,15 +164,22 @@ const funcCount = computed(() => props.blueprint.functions.length)
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- COMPANION 2: state machine -->
|
<!-- COMPANION 2: state machine(s) -->
|
||||||
<template v-if="blueprint.stateMachine">
|
<template v-if="blueprint.stateMachines?.length">
|
||||||
<h2 class="section">Lifecycle — state machine</h2>
|
<h2 class="section">
|
||||||
<div class="panel">
|
Lifecycle — state machine{{ blueprint.stateMachines.length > 1 ? "s" : "" }}
|
||||||
|
</h2>
|
||||||
|
<div
|
||||||
|
v-for="(m, i) in blueprint.stateMachines"
|
||||||
|
:key="i"
|
||||||
|
class="panel"
|
||||||
|
:class="{ stacked: i > 0 }"
|
||||||
|
>
|
||||||
<div class="cap">
|
<div class="cap">
|
||||||
<span>{{ blueprint.stateMachine?.caption ?? "Composed lifecycle" }}</span
|
<span>{{ m.caption ?? "Composed lifecycle" }}</span
|
||||||
><span class="id">{{ blueprint.stateMachine?.field ?? "loadState : LoadState" }}</span>
|
><span class="id">{{ m.field ?? "loadState : LoadState" }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="body svg-wrap"><StateMachine :machine="blueprint.stateMachine" /></div>
|
<div class="body svg-wrap"><StateMachine :machine="m" /></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -398,6 +405,10 @@ code {
|
|||||||
.svg-wrap {
|
.svg-wrap {
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
|
/* stacked state-machine panels (a blueprint with more than one lifecycle) */
|
||||||
|
.panel.stacked {
|
||||||
|
margin-top: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
/* full-view readout */
|
/* full-view readout */
|
||||||
.rblock {
|
.rblock {
|
||||||
|
|||||||
@@ -46,6 +46,26 @@ const edges = computed(() =>
|
|||||||
props.machine.edges.map((e) => {
|
props.machine.edges.map((e) => {
|
||||||
const a = byId.value[e.from]
|
const a = byId.value[e.from]
|
||||||
const b = byId.value[e.to]
|
const b = byId.value[e.to]
|
||||||
|
// Self-loop (a === b): a small arc off the top edge of the node, arrowhead
|
||||||
|
// curling back in. Used by the recurring transitions that don't change state
|
||||||
|
// — BLE's discover(p) on Scanning and read/write/subscribe on Ready.
|
||||||
|
if (a === b) {
|
||||||
|
const cx = a.x + a.w / 2
|
||||||
|
const top = a.y
|
||||||
|
const r = Math.min(a.w / 3.2, 20)
|
||||||
|
const h = 30
|
||||||
|
const sx = cx - r
|
||||||
|
const tx = cx + r
|
||||||
|
return {
|
||||||
|
key: `${e.from}-${e.to}-${e.label}`,
|
||||||
|
d: `M${sx} ${top} C ${sx} ${top - h} ${tx} ${top - h} ${tx} ${top}`,
|
||||||
|
color: SM_COLORS[e.kind],
|
||||||
|
marker: `url(#ar-${e.kind})`,
|
||||||
|
label: e.label,
|
||||||
|
lx: cx,
|
||||||
|
ly: top - h - 3,
|
||||||
|
}
|
||||||
|
}
|
||||||
const [acx, acy] = center(a)
|
const [acx, acy] = center(a)
|
||||||
const [bcx, bcy] = center(b)
|
const [bcx, bcy] = center(b)
|
||||||
const [sx, sy] = edgePoint(a, bcx, bcy)
|
const [sx, sy] = edgePoint(a, bcx, bcy)
|
||||||
|
|||||||
@@ -144,6 +144,9 @@ export interface Blueprint {
|
|||||||
functions: BlueprintFunction[]
|
functions: BlueprintFunction[]
|
||||||
coreInvariants: string[]
|
coreInvariants: string[]
|
||||||
composition?: Composition
|
composition?: Composition
|
||||||
stateMachine?: StateMachine
|
// Zero or more lifecycles. Most blueprints have one (or reuse LOADSTATE_MACHINE);
|
||||||
|
// some — like BLE, which is a central with an adapter/scan lifecycle *and* a
|
||||||
|
// per-peripheral link lifecycle — render several, each in its own panel.
|
||||||
|
stateMachines?: StateMachine[]
|
||||||
related?: RelatedRef[]
|
related?: RelatedRef[]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -204,5 +204,5 @@ export const calendar: Blueprint = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
stateMachine: CALENDAR_MACHINE,
|
stateMachines: [CALENDAR_MACHINE],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -202,5 +202,5 @@ export const grid: Blueprint = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
stateMachine: LOADSTATE_MACHINE,
|
stateMachines: [LOADSTATE_MACHINE],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -226,5 +226,5 @@ export const list: Blueprint = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
stateMachine: LOADSTATE_MACHINE,
|
stateMachines: [LOADSTATE_MACHINE],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -333,7 +333,7 @@ export const passwordAuth: Blueprint = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
stateMachine: PASSWORD_AUTH_MACHINE,
|
stateMachines: [PASSWORD_AUTH_MACHINE],
|
||||||
|
|
||||||
related: [
|
related: [
|
||||||
{ name: "Form", relation: "extends · base" },
|
{ name: "Form", relation: "extends · base" },
|
||||||
|
|||||||
@@ -301,7 +301,7 @@ export const rememberMe: Blueprint = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
stateMachine: REMEMBER_ME_MACHINE,
|
stateMachines: [REMEMBER_ME_MACHINE],
|
||||||
|
|
||||||
related: [
|
related: [
|
||||||
{ name: "PasswordAuth", relation: "host · requires" },
|
{ name: "PasswordAuth", relation: "host · requires" },
|
||||||
|
|||||||
Reference in New Issue
Block a user