Fix nested fixture transition draining

This commit is contained in:
Gabriel Brown
2026-08-17 17:16:36 -04:00
parent 4f93522f7d
commit 9a99c6064e
3 changed files with 100 additions and 3 deletions
@@ -35,10 +35,13 @@ Singleton {
property bool fixtureTransitionDraining: false
property bool fixtureTransitionStreamFinished: false
property bool fixtureTransitionExited: false
property var pendingFixtureTarget: null
property int delayedFixtureExitCode: 0
readonly property var visibleEntities: root.selectedEntities.slice(0, 4)
readonly property int discoveredCount: root.catalog.length
readonly property int configuredCount: root.selectedEntities.length
readonly property bool actionProcessRunning: actionProc.running
// Temporary aliases keep the current Home controls usable until the shelf
// switches to the selected-entity and per-entity action interfaces.
@@ -219,10 +222,14 @@ Singleton {
}
function fixtureActionCommand(action: var): var {
if (root.fixtureProcessMode === "delayed-exit")
return ["/usr/bin/sh", "-c", "printf '%s' '{\"ok\":true}'; exit 7"];
if (root.fixtureProcessMode === "no-output"
&& action.entityId === "light.fixture_kitchen") {
return ["/usr/bin/sh", "-c", "sleep 0.5; exit 7"];
}
if (root.fixtureProcessMode === "slow-success")
return ["/usr/bin/sh", "-c", "sleep 2; printf '%s' '{\"ok\":true}'"];
return ["/usr/bin/sh", "-c", "sleep 0.5; printf '%s' '{\"ok\":true}'"];
}
@@ -240,6 +247,20 @@ Singleton {
}
function handleActionExited(exitCode: int): void {
// This fixture-only delay gives the contract a deterministic window
// where the process stopped but its exit bookkeeping is still pending.
if (root.fixtureMode
&& root.fixtureProcessMode === "delayed-exit"
&& root.activeAction !== null
&& !root.fixtureTransitionDraining) {
root.delayedFixtureExitCode = exitCode;
delayedFixtureExitTimer.restart();
return;
}
root.recordActionExited(exitCode);
}
function recordActionExited(exitCode: int): void {
if (root.fixtureTransitionDraining) {
root.fixtureTransitionExited = true;
fixtureTransitionTimer.restart();
@@ -368,6 +389,9 @@ Singleton {
}
function beginFixtureTransition(): void {
if (root.fixtureTransitionDraining)
return;
const shouldDrain = root.fixtureMode
&& root.fixtureProcessMode !== ""
&& (actionProc.running || root.activeAction !== null);
@@ -397,22 +421,59 @@ Singleton {
if (!root.fixtureTransitionStreamFinished || !root.fixtureTransitionExited)
return;
const target = root.pendingFixtureTarget;
root.pendingFixtureTarget = null;
root.fixtureTransitionDraining = false;
root.fixtureTransitionStreamFinished = false;
root.fixtureTransitionExited = false;
if (target !== null)
root.installFixtureTarget(target);
queueAdvanceTimer.restart();
}
function applyFixture(name: string): void {
if (["ready", "stale", "unavailable", "missing-selected", "action-error",
"process-actions", "process-no-output"].indexOf(name) < 0)
"process-actions", "process-no-output", "process-delayed-exit",
"process-slow-actions"].indexOf(name) < 0)
return;
root.requestFixtureTarget({ fixture: true, name });
}
function requestFixtureTarget(target: var): void {
if (root.fixtureTransitionDraining) {
// Preserve the old process's callback guard and keep only the
// latest replacement requested during that drain.
root.pendingFixtureTarget = target;
root.resetActionState();
return;
}
root.pendingFixtureTarget = target;
root.beginFixtureTransition();
if (root.fixtureTransitionDraining)
return;
root.pendingFixtureTarget = null;
root.installFixtureTarget(target);
}
function installFixtureTarget(target: var): void {
if (target.fixture)
root.installFixture(target.name);
else
root.installLiveState();
}
function installFixture(name: string): void {
root.fixtureMode = true;
root.fixtureProcessMode = name === "process-actions"
? "success"
: (name === "process-no-output" ? "no-output" : "");
: (name === "process-no-output"
? "no-output"
: (name === "process-delayed-exit"
? "delayed-exit"
: (name === "process-slow-actions" ? "slow-success" : "")));
if (name === "unavailable") {
root.catalog = [];
root.fixtureFavorites = [];
@@ -442,7 +503,10 @@ Singleton {
}
function clearFixture(): void {
root.beginFixtureTransition();
root.requestFixtureTarget({ fixture: false, name: "" });
}
function installLiveState(): void {
root.fixtureMode = false;
root.fixtureProcessMode = "";
root.phase = "loading";
@@ -496,6 +560,12 @@ Singleton {
onTriggered: root.tryFinishFixtureTransition()
}
Timer {
id: delayedFixtureExitTimer
interval: 1000
onTriggered: root.recordActionExited(root.delayedFixtureExitCode)
}
Timer {
interval: 60000
repeat: true