mirror of
https://github.com/Such-Software/wownerogue.git
synced 2026-10-24 01:28:33 +02:00
fix(tavern): repaint permitted avatars and prioritize the live arena
This commit is contained in:
@@ -195,13 +195,14 @@ describe('renderer controls and failed skin fallback', () => {
|
||||
test('a normal character still waits for its atlas and resolves the render callback', () => {
|
||||
let atlasReady;
|
||||
let atlasLoaded = false;
|
||||
const loadAtlas = jest.fn(callback => { atlasReady = callback; });
|
||||
const RK = {
|
||||
CHARS: { 'char-villager': { frame: 1 } },
|
||||
normalizeAppearance: appearance => ({ avatar: typeof appearance === 'string' ? appearance : appearance.avatar || 'default' }),
|
||||
appearance: appearance => ({ label: appearance.avatar }),
|
||||
isChar: id => id === 'char-villager',
|
||||
charAtlas: () => atlasLoaded ? {} : null,
|
||||
loadCharAtlas: callback => { atlasReady = callback; },
|
||||
loadCharAtlas: loadAtlas,
|
||||
charAppearance: appearance => ({ avatar: appearance.avatar, tint: 'none', colors: {}, equipment: {} }),
|
||||
charFrame: () => ({ squash: 1, bob: 0 }),
|
||||
drawCharTileCanvas: jest.fn(),
|
||||
@@ -218,7 +219,10 @@ describe('renderer controls and failed skin fallback', () => {
|
||||
const ready = jest.fn();
|
||||
const canvas = { beginPath() {}, ellipse() {}, stroke() {} };
|
||||
const entity = { id: 'me', x: 1, y: 1, facing: 'down', you: true };
|
||||
expect(RK.avatarVisuals.drawTopdownWorld(canvas, visual, entity, { screenX: 1, screenY: 1, cell: 24 }, { onReady: ready })).toBe(false);
|
||||
for (let i = 0; i < 12; i++) {
|
||||
expect(RK.avatarVisuals.drawTopdownWorld(canvas, visual, entity, { screenX: 1, screenY: 1, cell: 24 }, { onReady: ready })).toBe(false);
|
||||
}
|
||||
expect(loadAtlas).toHaveBeenCalledTimes(1);
|
||||
|
||||
atlasLoaded = true;
|
||||
atlasReady();
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const vm = require('vm');
|
||||
|
||||
function loadTileRenderer(options) {
|
||||
options = options || {};
|
||||
const frames = [];
|
||||
const callbacks = [];
|
||||
let atlasReady = false;
|
||||
let skinReady = false;
|
||||
let skinFailed = false;
|
||||
const sprites = [];
|
||||
let renderPasses = 0;
|
||||
const ctx = {
|
||||
fillRect() {}, strokeRect() {}, save() {}, restore() {}, beginPath() {}, ellipse() {},
|
||||
arc() {}, fill() {}, stroke() {}, clearRect() {}, drawImage() {}, fillText() {}
|
||||
};
|
||||
const canvas = { className: '', width: 0, height: 0, parentNode: null, getContext: () => ctx };
|
||||
const window = {
|
||||
requestAnimationFrame(callback) { frames.push(callback); return frames.length; },
|
||||
cancelAnimationFrame() {},
|
||||
RK: {
|
||||
themeAtlas: () => null,
|
||||
tileMap: () => ({}),
|
||||
roomReady: () => false,
|
||||
charAtlas: () => atlasReady ? {} : null,
|
||||
skinSheet: () => skinReady ? {} : null,
|
||||
skinSheetFailed: () => skinFailed,
|
||||
avatarVisuals: {
|
||||
resolve: appearance => ({
|
||||
kind: appearance.avatar === 'generated-test-skin' ? 'skin' : 'char',
|
||||
allowed: true,
|
||||
appearance
|
||||
}),
|
||||
drawTopdownWorld(_ctx, visual, entity, _viewport, opts) {
|
||||
if (visual.kind === 'skin' && skinReady) { sprites.push(entity.id); return true; }
|
||||
if (visual.kind === 'skin' && skinFailed && atlasReady) { sprites.push(entity.id); return true; }
|
||||
if (visual.kind === 'char' && atlasReady) { sprites.push(entity.id); return true; }
|
||||
if (visual.kind === 'skin' && options.transitionToChar) {
|
||||
callbacks.push(opts.onReady);
|
||||
return false;
|
||||
}
|
||||
if (options.completeWithoutReady) { opts.onReady(); return false; }
|
||||
if (options.completeWithReady) { atlasReady = true; opts.onReady(); return false; }
|
||||
callbacks.push(opts.onReady);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const document = { createElement: () => canvas };
|
||||
const context = { window, document, RK: window.RK, Date };
|
||||
vm.createContext(context);
|
||||
vm.runInContext(fs.readFileSync(path.join(__dirname, '../html/js/render/tileRenderer.js'), 'utf8'), context);
|
||||
const host = { appendChild(child) { child.parentNode = this; }, removeChild(child) { child.parentNode = null; } };
|
||||
const renderer = new window.RK.TileRenderer(host);
|
||||
renderer._collectEmitters = () => {};
|
||||
renderer._snapshot = () => { renderPasses += 1; };
|
||||
renderer._ensureEmbers = () => {};
|
||||
renderer._composite = () => {};
|
||||
renderer._startLoop = () => {};
|
||||
function scene(id) {
|
||||
return {
|
||||
cols: 1, rows: 1, grid: [['floor']], legend: { floor: { color: '#654321' } },
|
||||
entities: [{ id, kind: 'avatar', x: 0, y: 0, appearance: { avatar: 'default' } }]
|
||||
};
|
||||
}
|
||||
return {
|
||||
callbacks, frames, renderer, scene, sprites,
|
||||
ready() { atlasReady = true; },
|
||||
failSkin() { skinFailed = true; },
|
||||
get renderPasses() { return renderPasses; }
|
||||
};
|
||||
}
|
||||
|
||||
describe('TileRenderer character atlas repaint', () => {
|
||||
test('repaints the latest retained scene once when the free character atlas becomes ready', () => {
|
||||
const kit = loadTileRenderer();
|
||||
const first = kit.scene('first');
|
||||
first.entities.push({ id: 'first-other', kind: 'avatar', x: 0, y: 0, appearance: { avatar: 'default' } });
|
||||
const latest = kit.scene('latest');
|
||||
latest.entities.push({ id: 'latest-other', kind: 'avatar', x: 0, y: 0, appearance: { avatar: 'default' } });
|
||||
kit.renderer.render(first);
|
||||
kit.renderer.render(latest);
|
||||
expect(new Set(kit.callbacks).size).toBe(1);
|
||||
expect(kit.sprites).toEqual([]);
|
||||
|
||||
kit.ready();
|
||||
kit.callbacks.forEach(callback => callback());
|
||||
expect(kit.frames).toHaveLength(1);
|
||||
kit.frames.shift()();
|
||||
|
||||
expect(kit.sprites).toEqual(['latest', 'latest-other']);
|
||||
expect(kit.renderPasses).toBe(3);
|
||||
expect(kit.frames).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('does not repaint after destruction, including when readiness was already queued', () => {
|
||||
const kit = loadTileRenderer();
|
||||
kit.renderer.render(kit.scene('removed'));
|
||||
kit.ready();
|
||||
kit.callbacks[0]();
|
||||
expect(kit.frames).toHaveLength(1);
|
||||
|
||||
kit.renderer.destroy();
|
||||
kit.frames.shift()();
|
||||
expect(kit.renderPasses).toBe(1);
|
||||
expect(kit.sprites).toEqual([]);
|
||||
});
|
||||
|
||||
test('does not retry a loader callback that completes without a drawable atlas', () => {
|
||||
const kit = loadTileRenderer({ completeWithoutReady: true });
|
||||
kit.renderer.render(kit.scene('unavailable'));
|
||||
expect(kit.frames).toHaveLength(0);
|
||||
expect(kit.renderPasses).toBe(1);
|
||||
});
|
||||
|
||||
test('retains the current scene before a loader completion callback can repaint it', () => {
|
||||
const kit = loadTileRenderer({ completeWithReady: true });
|
||||
kit.renderer.render(kit.scene('synchronous-ready'));
|
||||
expect(kit.frames).toHaveLength(1);
|
||||
kit.frames.shift()();
|
||||
expect(kit.sprites).toEqual(['synchronous-ready']);
|
||||
expect(kit.renderPasses).toBe(2);
|
||||
expect(kit.frames).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('repaints after a failed skin so the free character fallback can load and draw', () => {
|
||||
const kit = loadTileRenderer({ transitionToChar: true });
|
||||
const skinScene = kit.scene('free-fallback');
|
||||
skinScene.entities[0].appearance = { avatar: 'generated-test-skin' };
|
||||
kit.renderer.render(skinScene);
|
||||
expect(kit.callbacks).toHaveLength(1);
|
||||
expect(kit.frames).toHaveLength(0);
|
||||
|
||||
kit.failSkin();
|
||||
kit.callbacks[0]();
|
||||
expect(kit.frames).toHaveLength(1);
|
||||
kit.frames.shift()();
|
||||
expect(kit.callbacks).toHaveLength(2);
|
||||
expect(kit.sprites).toEqual([]);
|
||||
|
||||
kit.ready();
|
||||
kit.callbacks[1]();
|
||||
expect(kit.frames).toHaveLength(1);
|
||||
kit.frames.shift()();
|
||||
expect(kit.sprites).toEqual(['free-fallback']);
|
||||
expect(kit.renderPasses).toBe(3);
|
||||
expect(kit.frames).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user