A test that waits for a deadline somebody guessed is waiting for the wrong thing, so it waits for the editor (#93)
game-maker / suite (push) Successful in 6m48s
game-maker / suite (push) Successful in 6m48s
The suite spent 88% of its step time asleep: 564 s of steps, 496 s of
them in sleep(n) - fixed spans estimated by hand and then copied from
step to step. That is why it was slow, and why it flickered.
The signal is afterFrames(count, timeout) in ui/render.ts, exposed as
__gm.afterFrames(). It wakes the loop itself, because an editor that
draws nothing would never redeem a promise of a frame, and it gives up
after a timeout instead of hanging the run. settle() in the harness
waits two drawn frames (~32 ms) instead of 150-300 ms and falls back to
sleep(150) when the hook is missing (an old build): silently not
waiting would make every later check a coin toss.
Every action goes through it now - click, doubleClick, wheel, drag,
dragAsset, press, typeText, select, mutate, undo, redo - and with that
about 550 sleep() calls behind those actions became redundant, helpers
that are nothing but a mutate in a coat included. What a frame cannot
answer got its own condition instead: waitUntil(expr), play()/stop()
waiting on __gm.state.mode rather than on 300 ms, saved() for the
autosave (the two hand-written polling loops sampled at 300 ms for
something that arrives after a second), and showTab() as a real tool -
47 places called evaluate("__gm.showTab(...)") raw, and 24 of them
waited for nothing at all and read the old panel.
564 s -> 243 s of step time, the waiting share 88% -> 76%. Three kinds
of wait were kept, and each one bit when it was taken away: loading
bytes (textures, fonts), game time that a repaint cannot stand in for
(time scale, shader and particle clocks, the live values a running
graph carries), and the inertia the scroll step measures on purpose.
Two things the new speed uncovered, both real: 25-animation read the
frame menu unfiltered and had frozen an incomplete list (the CI failure
on c990dbc), and 33-render-on-change measured idleness while the
autosave of the step before was still painting - it waits for
saveStatus to leave dirty/saving now, so it measures that nothing
happens any more instead of that it waited long enough.
This commit is contained in:
1 parent
c990dbcb26
commit
7e5003b590
41 files changed
+362
-732
No files matched your search
@@ -35,44 +35,24 @@ const logicGraph = {
|
||||
};
|
||||
|
||||
export async function variableNodes(t) {
|
||||
const {
|
||||
check,
|
||||
sleep,
|
||||
evaluate,
|
||||
json,
|
||||
shot,
|
||||
click,
|
||||
locate,
|
||||
undo,
|
||||
entity,
|
||||
asset,
|
||||
mutate,
|
||||
stop,
|
||||
typeText,
|
||||
press,
|
||||
has,
|
||||
} = t;
|
||||
const { check, sleep, json, shot, click, locate, undo, entity, asset, mutate, stop, typeText, press, has } =
|
||||
t;
|
||||
const portAt = (nodeId, key, isOutput) =>
|
||||
json(`__gm.graph.portAt(${JSON.stringify(nodeId)}, ${JSON.stringify(key)}, ${isOutput})`);
|
||||
const portsOf = async (nodeId) =>
|
||||
(await asset('vp-logic')).graph.nodes.find((n) => n.id === nodeId).ports ?? [];
|
||||
const select = async (nodeId) => {
|
||||
await evaluate(
|
||||
`__gm.state.select({ kind: 'node', assetId: 'vp-logic', nodeId: ${JSON.stringify(nodeId)}, nodeIds: [${JSON.stringify(nodeId)}] })`,
|
||||
);
|
||||
await sleep(300);
|
||||
await t.select({ kind: 'node', assetId: 'vp-logic', nodeId, nodeIds: [nodeId] });
|
||||
};
|
||||
/** The inspector is drawn over the graph, so the last match is the row rather than the port. */
|
||||
const clickLast = async (text) => {
|
||||
const p = await locate(text, true);
|
||||
await click(p.x, p.y);
|
||||
await sleep(200);
|
||||
};
|
||||
/** The Set checkbox of the newest variable row: the control right of the last "Set" label. */
|
||||
const clickLastSet = async () => {
|
||||
const box = await json(`__gm.inspectorField('Set', true)`);
|
||||
await click(box.x, box.y);
|
||||
await sleep(300);
|
||||
return box;
|
||||
};
|
||||
|
||||
@@ -93,12 +73,10 @@ export async function variableNodes(t) {
|
||||
${MAKE_OBJECT}
|
||||
scene.objects.push(make('vp-box', 'crate', '', 900, 'Speedy box'), make('vp-brain', 'vp-logic', 'vp-box', 900, 'Speeds'));
|
||||
`);
|
||||
await sleep(400);
|
||||
|
||||
// 23b. The run: both variables are written by one node and read back by another, and the two
|
||||
// speeds stay apart – 2 to the right for every 3 down.
|
||||
await t.play();
|
||||
await sleep(300);
|
||||
const before = await entity('vp-box');
|
||||
await sleep(1000);
|
||||
const after = await entity('vp-box');
|
||||
@@ -110,7 +88,6 @@ export async function variableNodes(t) {
|
||||
`moved (${dx.toFixed(1)}, ${dy.toFixed(1)}) px in ~1 s – (200, 300) means both ports carried their own variable`,
|
||||
);
|
||||
await stop();
|
||||
await sleep(300);
|
||||
|
||||
// 23c. Every variable is an output; a written one is an input too, and only a writing node has
|
||||
// flow pins.
|
||||
@@ -134,9 +111,7 @@ export async function variableNodes(t) {
|
||||
check('the node offers its variables in the inspector', Boolean(add), JSON.stringify(add));
|
||||
await shot('01-variable-ports');
|
||||
await click(add.x, add.y);
|
||||
await sleep(200);
|
||||
await clickLast('vz');
|
||||
await sleep(300);
|
||||
const grown = await portsOf('vp-set');
|
||||
check(
|
||||
'picking one adds it as a read port, typed from the variable',
|
||||
@@ -161,18 +136,14 @@ export async function variableNodes(t) {
|
||||
);
|
||||
await shot('02-variable-set');
|
||||
await undo();
|
||||
await sleep(300);
|
||||
await undo();
|
||||
await sleep(300);
|
||||
check('undo takes both back', (await portsOf('vp-set')).length === 2);
|
||||
|
||||
// 23e. Pointing a port at another variable keeps the edge hanging on it – a correction, not a
|
||||
// rewiring. `vp-e3` runs from this port to Move's dx.
|
||||
await select('vp-get');
|
||||
await clickLast('vx');
|
||||
await sleep(200);
|
||||
await clickLast('vz');
|
||||
await sleep(300);
|
||||
const swapped = await portsOf('vp-get');
|
||||
const edgeStill = await json(
|
||||
`(__gm.state.project.assets.find((a) => a.id === 'vp-logic').graph.edges.find((e) => e.id === 'vp-e3') || {}).from`,
|
||||
@@ -183,7 +154,6 @@ export async function variableNodes(t) {
|
||||
JSON.stringify({ swapped, edgeStill }),
|
||||
);
|
||||
await undo();
|
||||
await sleep(300);
|
||||
check('and undo points it back', (await portsOf('vp-get'))[0].key === 'vx');
|
||||
|
||||
// 23f. Collapsed, the ports are out of sight, so the title says what the node does – and only then.
|
||||
@@ -191,7 +161,6 @@ export async function variableNodes(t) {
|
||||
const toggle = async (id) => {
|
||||
const chevron = await json(`__gm.graph.chevronAt(${JSON.stringify(id)})`);
|
||||
await click(chevron.x, chevron.y);
|
||||
await sleep(250);
|
||||
};
|
||||
/** `locate` matches exactly, so a title cut short by a too-narrow node is not found. */
|
||||
const drawnWhole = async (text) => {
|
||||
@@ -222,7 +191,6 @@ export async function variableNodes(t) {
|
||||
JSON.stringify({ switched, narrow }),
|
||||
);
|
||||
await undo();
|
||||
await sleep(300);
|
||||
await toggle('vp-set');
|
||||
await toggle('vp-get');
|
||||
const open = [await titleOf('vp-set'), await titleOf('vp-get')];
|
||||
@@ -250,16 +218,13 @@ export async function variableNodes(t) {
|
||||
a.props.shapes = [{ x: 0, y: 0 }];
|
||||
a.graph.nodes.push(${JSON.stringify(node('vp-pack', 'PackStruct', 560, 400))});
|
||||
`);
|
||||
await sleep(300);
|
||||
|
||||
await select('vp-pack');
|
||||
const from = await locate('From variable');
|
||||
check('a Pack offers the shapes its behavior declares', Boolean(from), JSON.stringify(from));
|
||||
await click(from.x, from.y);
|
||||
await sleep(200);
|
||||
await shot('03-from-variable');
|
||||
await clickLast('piece');
|
||||
await sleep(300);
|
||||
const seeded = await portsOf('vp-pack');
|
||||
check(
|
||||
'picking a struct variable declares every field at once, each of its own kind',
|
||||
@@ -270,15 +235,12 @@ export async function variableNodes(t) {
|
||||
check('and the node grew those inputs', Boolean(await portAt('vp-pack', 'cells', false)));
|
||||
await shot('04-fields-seeded');
|
||||
await undo();
|
||||
await sleep(300);
|
||||
check('one undo takes all of them back', (await portsOf('vp-pack')).length === 0);
|
||||
|
||||
// 23h. A list declares nothing, so the shape comes off the struct items in it – the bundle a Pack
|
||||
// builds for such a list.
|
||||
await click(from.x, from.y);
|
||||
await sleep(200);
|
||||
await clickLast('shapes (item)');
|
||||
await sleep(300);
|
||||
const fromItem = await portsOf('vp-pack');
|
||||
check(
|
||||
'a list offers the shape of the struct items it holds',
|
||||
@@ -286,7 +248,6 @@ export async function variableNodes(t) {
|
||||
JSON.stringify(fromItem),
|
||||
);
|
||||
await undo();
|
||||
await sleep(300);
|
||||
|
||||
// 23i. A behavior's own inputs are not fields of a bundle, so the helper stays out of that section.
|
||||
await mutate(`
|
||||
@@ -294,7 +255,6 @@ export async function variableNodes(t) {
|
||||
${JSON.stringify(node('vp-start', 'BehaviorStart', 560, 600))},
|
||||
);
|
||||
`);
|
||||
await sleep(200);
|
||||
await select('vp-start');
|
||||
// `locate` throws when the text is absent, which is the wrong shape for "is it there?".
|
||||
const helper = await json(`__gm.locate('From variable', false)`);
|
||||
@@ -311,12 +271,9 @@ export async function variableNodes(t) {
|
||||
// Bare canvas below every node, so the click cannot land on the Pack.
|
||||
const bareSpot = await json('__gm.graph.emptySpot()');
|
||||
await click(bareSpot.x, bareSpot.y, 'right');
|
||||
await sleep(150);
|
||||
// Enter takes the first hit in category order, so search by a phrase only its description has.
|
||||
await typeText('switched to Set');
|
||||
await sleep(200);
|
||||
await press('Enter');
|
||||
await sleep(300);
|
||||
const added = (await asset('vp-logic')).graph.nodes.find((n) => !idsBefore.includes(n.id));
|
||||
check('the palette offers the Variables node', added?.type === 'Variables', JSON.stringify(added));
|
||||
const variableId = added?.id ?? '';
|
||||
@@ -324,7 +281,6 @@ export async function variableNodes(t) {
|
||||
const bare = !(await portAt(variableId, 'exec', false));
|
||||
await clickLast('+ Variable');
|
||||
await clickLast('vz');
|
||||
await sleep(300);
|
||||
await clickLastSet();
|
||||
check(
|
||||
'it starts without pins, and picking its variable leaves the title Variables',
|
||||
@@ -346,7 +302,6 @@ export async function variableNodes(t) {
|
||||
${JSON.stringify(edge('vp-e9', 'vp-add', 'result', variableId, 'vz'))},
|
||||
);
|
||||
`);
|
||||
await sleep(300);
|
||||
await select('vp-gone');
|
||||
check(
|
||||
'a port naming a variable the behavior does not have is marked in the inspector',
|
||||
@@ -357,7 +312,6 @@ export async function variableNodes(t) {
|
||||
await sleep(1000);
|
||||
const counted = (await entity('vp-brain'))?.props.vz;
|
||||
await stop();
|
||||
await sleep(300);
|
||||
check(
|
||||
'in the run it reads through its own output and writes the sum back, so vz climbs',
|
||||
typeof counted === 'number' && counted > 20,
|
||||
@@ -372,7 +326,6 @@ export async function variableNodes(t) {
|
||||
__gm.state.select(null);
|
||||
__gm.state.preview();
|
||||
`);
|
||||
await sleep(300);
|
||||
const left = await json('__gm.state.project.assets.filter((a) => a.id === "vp-logic").length');
|
||||
check('the step leaves the fixture as it found it', left === 0, String(left));
|
||||
}
|
||||
Reference in new issue
Block a user