Let a chain stage be switched off without taking it apart
Trying a stage out meant removing it and building it again from scratch, settings and all, to see what the picture looked like without it. A stage now carries an `enabled` flag and can simply be switched off: it keeps its place in the chain and everything it was set to. `enabledStages` is the one place that reads the flag, and renderChain, chainNeedsSources, chainTitle and chainSlug all go through it. A disabled stage is skipped whole — handed no image and drawing nothing from the shared rng — so switching one off renders byte for byte what the chain rendered before that stage was added, which is the only thing that makes the switch worth anything while comparing. Whichever stage runs first is the one handed the sources, so disabling a leading scanlines lets blur lead and the chain needs no images at all. The status line and the generated file name follow too: they name what was rendered, not what sits in the sidebar. A chain with every stage disabled throws rather than quietly rendering nothing. In the interface it is a check on the chain row: the row dims, the params heading says "(off)" and the settings stay editable — a stage being worked on is not the same as one being deleted. On the command line it is `--disable NAME`, which picks its stage by effect name exactly as the params do. A project writes `"enabled": false` only for a stage that is off, so existing project files are unchanged and an ordinary one stays uncluttered. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -889,6 +889,50 @@ int main() {
|
||||
scanlinesThrew = true;
|
||||
}
|
||||
check(scanlinesThrew, "scanlines still refuses to run without sources");
|
||||
|
||||
// A stage switched off is skipped whole: it is handed no image and
|
||||
// draws nothing from the shared rng, so the cascade comes out exactly
|
||||
// as it would have without that stage in it at all.
|
||||
ChainStage blurOff = blur;
|
||||
blurOff.enabled = false;
|
||||
ChainStage scanlinesOff = scanlines;
|
||||
scanlinesOff.enabled = false;
|
||||
|
||||
Rng skipping(5);
|
||||
const auto skipped = renderChain({scanlines, blurOff}, sources, 400, 300, skipping);
|
||||
check(sameBytes(skipped, direct), "a disabled stage is skipped, its rng draws with it");
|
||||
check(chainTitle({scanlines, blurOff}) == "Scanlines", "the title names only what ran");
|
||||
check(chainSlug({scanlines, blurOff}) == "scanlines", "and so does the slug");
|
||||
|
||||
// Whoever leads the chain is handed the sources, and that is the first
|
||||
// stage still switched on.
|
||||
check(!chainNeedsSources({scanlinesOff, blur}),
|
||||
"a disabled leading stage leaves the question to the stage behind it");
|
||||
Rng leading(5);
|
||||
const auto ledByBlur = renderChain({scanlinesOff, blur}, {}, 200, 150, leading);
|
||||
check(sameBytes(ledByBlur, sourceless), "the stage behind it leads instead");
|
||||
|
||||
bool allOffThrew = false;
|
||||
try {
|
||||
Rng rng(1);
|
||||
renderChain({scanlinesOff, blurOff}, sources, 100, 100, rng);
|
||||
} catch (const EffectError&) {
|
||||
allOffThrew = true;
|
||||
}
|
||||
check(allOffThrew, "a chain with every stage disabled throws");
|
||||
|
||||
// The command line switches a stage off by effect name, the same way
|
||||
// the params pick out the stage they belong to.
|
||||
const auto cli =
|
||||
parsedOptions({"-e", "scanlines", "-e", "blur", "--disable", "blur", "--radius", "4"});
|
||||
check(cli.has_value(), "--disable is accepted");
|
||||
const auto cliStages = buildChain(*cli);
|
||||
check(cliStages.size() == 2 && cliStages[0].enabled && !cliStages[1].enabled,
|
||||
"--disable keeps the stage in the chain and switches it off");
|
||||
check(paramInt(cliStages[1].values, "radius", 0) == 4,
|
||||
"a switched-off stage still takes its flags");
|
||||
check(!parsedOptions({"-e", "blur", "--disable", "nope"}).has_value(),
|
||||
"--disable refuses an unknown effect");
|
||||
}
|
||||
|
||||
std::cout << "project\n";
|
||||
@@ -949,6 +993,21 @@ int main() {
|
||||
check(stripped.stages[0].values.find("radius") == stripped.stages[0].values.end(),
|
||||
"a stage only keeps the params its effect declares");
|
||||
|
||||
// A stage switched off keeps its place and its settings in the file and
|
||||
// comes back switched off; nothing is written for the ordinary case.
|
||||
Project toggled = project;
|
||||
toggled.stages[1].enabled = false;
|
||||
const auto offFile = dir / "disabled.json";
|
||||
saveProject(offFile.string(), toggled);
|
||||
const auto offBack = loadProject(offFile.string());
|
||||
check(offBack.stages.size() == 2 && offBack.stages[0].enabled && !offBack.stages[1].enabled,
|
||||
"a switched-off stage comes back switched off");
|
||||
check(paramInt(offBack.stages[1].values, "radius", 0) == 3,
|
||||
"and with the settings it was switched off with");
|
||||
check(projectToJson(toggled).find("\"enabled\"") != std::string::npos &&
|
||||
projectToJson(project).find("\"enabled\"") == std::string::npos,
|
||||
"only a switched-off stage is written out as one");
|
||||
|
||||
int refusals = 0;
|
||||
for (const char* bad : {"{\"chain\": [{\"effect\": \"nope\"}]}", "{\"chain\": []}",
|
||||
"{\"chain\": [{\"effect\": \"blur\"},]}", "not json at all"}) {
|
||||
|
||||
Reference in New Issue
Block a user