0.2.6-dev: impostor review follow-ups (#542) — a skeleton stays in the clip-bounds queue only while an actor uses it (the last one destroyed drops its pending clips, the clip in progress and its scratch skeleton; a later actor queues them again), a clip whose sampling throws is recorded as not measurable and drawn directly while it plays instead of counting as empty, and a frozen model's first update counts as posed only when it succeeded; DESIGN §31.6 updated; golden: unchanged
This commit is contained in:
1 parent
4c2fe9431d
commit
47004a3651
4 files changed
+197
-78
No files matched your search
@@ -104,6 +104,43 @@ describe('clip bounds (render/spine.js): what a skeleton draws, measured once pe
|
||||
assert.equal(spine.stepClipBounds(), 0, 'an empty queue costs nothing');
|
||||
});
|
||||
|
||||
test('a record leaves the queue when its last actor is destroyed — pending clips, the clip in progress and its scratch skeleton —; a later actor queues them again', () => {
|
||||
const data = skeleton();
|
||||
/** A SpineActor stand-in on the real runtime skeleton (clipBounds / destroy are the real methods). */
|
||||
const actor = () => Object.assign(Object.create(spine.SpineActor.prototype), { spine: { spineData: data, skeleton: new rt.Skeleton(data), destroy() {} } });
|
||||
const a = actor(), b = actor();
|
||||
assert.equal(a.clipBounds(['Wave', 'Flash']), null, 'queued');
|
||||
assert.equal(b.clipBounds(['Wave', 'Flash']), null);
|
||||
spine.stepClipBounds(0); // one pose of Wave: a clip in progress
|
||||
const rec = spine.requestClipBounds(data, rt.Skeleton, []);
|
||||
assert.ok(rec.job && rec.skel, 'sampling Wave on its scratch skeleton');
|
||||
a.destroy();
|
||||
assert.equal(spine.pendingClipBounds(), 2, 'b still uses the skeleton: still queued');
|
||||
b.destroy(); // e.g. the battlefield torn down with its units
|
||||
assert.equal(spine.pendingClipBounds(), 0, 'nothing left to sample');
|
||||
assert.ok(rec.job === null && rec.todo.length === 0 && rec.skel === null, 'no clip in progress, no todo, no scratch skeleton held');
|
||||
const c = actor(); // the same skeleton in the next scene
|
||||
assert.equal(c.clipBounds(['Wave', 'Flash']), null, 'queued again (no todo left behind to block it)');
|
||||
assert.equal(spine.pendingClipBounds(), 2);
|
||||
drain();
|
||||
near(c.clipBounds(['Wave', 'Flash']).get('Wave'), [-50, -200, 425, 25], 'measured');
|
||||
c.destroy();
|
||||
});
|
||||
|
||||
test('a clip whose sampling throws is recorded as not measurable (false), never as drawing nothing', () => {
|
||||
const data = skeleton();
|
||||
const wave = data.findAnimation('Wave');
|
||||
const apply = wave.apply.bind(wave);
|
||||
wave.apply = (skel, last, t, ...rest) => { if (t > 0.5) throw new Error('a broken timeline'); return apply(skel, last, t, ...rest); };
|
||||
const rec = spine.requestClipBounds(data, rt.Skeleton, ['Wave', 'Still']);
|
||||
drain();
|
||||
assert.equal(rec.rects.get('Wave'), false, 'not measurable');
|
||||
near(rec.rects.get('Still'), [-50, -200, 125, 25], 'the other clip is measured');
|
||||
assert.equal(rec.rects.get('Nope'), undefined);
|
||||
const b = units.impostorBox([-50, -200, 125, 25], [['Wave', false, false], ['Still', rec.rects.get('Still'), false]]);
|
||||
assert.deepEqual([...b.over], ['Wave'], 'drawn directly while it plays');
|
||||
});
|
||||
|
||||
test('SpineActor.clipBounds: null while queued, then a Map clip → rect; false for a runtime that cannot be posed', () => {
|
||||
const data = skeleton();
|
||||
const stand = { spine: { spineData: data, skeleton: new rt.Skeleton(data) } };
|
||||
@@ -144,6 +181,17 @@ describe('a model frozen before its first update shows its clip\'s first frame,
|
||||
assert.deepEqual(posed, [0, 0.1]);
|
||||
});
|
||||
|
||||
test('a first update that throws is tried again: the frozen model is posed once an update succeeds', () => {
|
||||
const { a, posed } = actor({ animations: [{ name: 'Idle' }, { name: 'Die' }] }, ENTRY);
|
||||
let broken = true;
|
||||
a.spine.update = (dt) => { posed.push(dt); if (broken) { broken = false; throw new Error('a broken skeleton'); } };
|
||||
a.setBase('stun');
|
||||
a.update(0.1); // throws inside pixi-spine (caught): not posed yet
|
||||
a.update(0.1); // tried again at the clip's first frame: posed
|
||||
a.update(0.1); // held
|
||||
assert.deepEqual(posed, [0, 0]);
|
||||
});
|
||||
|
||||
test('stunned after it was posed: the pose it had holds (unchanged)', () => {
|
||||
const { a, posed } = actor({ animations: [{ name: 'Idle' }, { name: 'Die' }] }, ENTRY);
|
||||
a.update(0.2);
|
||||
@@ -355,6 +403,28 @@ describe('UnitView impostor mode (render/units.js)', () => {
|
||||
assert.equal(log.alloc, c0.alloc, 'the same slot');
|
||||
});
|
||||
|
||||
test('a clip whose sampling threw is drawn directly while it plays — the attack, or even the idle clip —, never cut by the slot', async () => {
|
||||
const broken = new Map(MEASURED).set('Attack', false);
|
||||
const { v, step, log } = await operator(() => broken);
|
||||
step(3);
|
||||
assert.ok(v.imp && v._box.over.has('Attack'));
|
||||
const draws = log.draw;
|
||||
v.actor._play('Attack', false, { mix: 0 });
|
||||
step(2);
|
||||
assert.equal(v.imp.bypass, true, 'the attack is drawn directly');
|
||||
assert.equal(log.draw, draws, 'not into the slot');
|
||||
const idleBroken = new Map(MEASURED).set('Idle', false);
|
||||
const w = (await operator(() => idleBroken)).v;
|
||||
w.update(1 / 60, cam(), 0);
|
||||
assert.ok(w._box.over.has('Idle'), 'an unmeasurable idle clip: drawn directly while it idles');
|
||||
const b = w._box, c = MEASURED.get('Attack');
|
||||
assert.ok(b.x0 <= c[0] && b.x0 + b.w >= c[2], 'the box built from the clips that were measured');
|
||||
const none = new Map([...MEASURED.keys()].map((n) => [n, false]));
|
||||
const z = (await operator(() => none)).v;
|
||||
z.update(1 / 60, cam(), 0);
|
||||
assert.deepEqual([...z._box.over].sort(), ['Attack', 'Die', 'Idle', 'Start'], 'nothing measurable: every clip drawn directly');
|
||||
});
|
||||
|
||||
test('a runtime whose clips cannot be measured keeps the box from the pose shown (the pre-#542 rule)', async () => {
|
||||
const { v, step } = await operator(() => false);
|
||||
step(3);
|
||||
|
||||
Reference in new issue
Block a user