mirror of
https://github.com/Such-Software/wownerogue.git
synced 2026-10-25 09:08:34 +01:00
152 lines
6.3 KiB
JavaScript
152 lines
6.3 KiB
JavaScript
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);
|
|
});
|
|
});
|