Merge branch 'devel'

This commit is contained in:
HF 2022-06-25 21:36:22 +02:00
commit a069bb9d75
6 changed files with 237 additions and 91 deletions

View File

@ -20,33 +20,145 @@ import { TILE_SIZE, TILE_ZOOM_LEVEL } from './constants';
/* /*
* Deletes a subtile from a tile (paints it in color 0), * Deletes a subtile from a tile (paints it in color 0),
* if we wouldn't do it, it would be black * if we wouldn't do it, it would be black
* @param tileSize size of the tile within the chunk
* @param palette Palette to use * @param palette Palette to use
* @param subtilesInTile how many subtiles are in a tile (per dimension) * @param subtilesInTile how many subtiles are in a tile (per dimension)
* @param cell subtile to delete [dx, dy] * @param cell subtile to delete [dx, dy]
* @param buffer Uint8Array for RGB values of tile * @param buffer Uint8Array for RGB values of tile
*/ */
function deleteSubtilefromTile( function deleteSubtilefromTile(
tileSize,
palette, palette,
subtilesInTile, subtilesInTile,
cell, cell,
buffer, buffer,
) { ) {
const [dx, dy] = cell; const [dx, dy] = cell;
const offset = (dx + dy * TILE_SIZE * subtilesInTile) * TILE_SIZE; const offset = (dx + dy * tileSize * subtilesInTile) * tileSize;
for (let row = 0; row < TILE_SIZE; row += 1) { const [blankR, blankG, blankB] = palette.rgb;
let channelOffset = (offset + row * TILE_SIZE * subtilesInTile) * 3; for (let row = 0; row < tileSize; row += 1) {
const max = channelOffset + TILE_SIZE * 3; let channelOffset = (offset + row * tileSize * subtilesInTile) * 3;
const max = channelOffset + tileSize * 3;
while (channelOffset < max) { while (channelOffset < max) {
// eslint-disable-next-line prefer-destructuring buffer[channelOffset++] = blankR;
buffer[channelOffset++] = palette.rgb[0]; buffer[channelOffset++] = blankG;
// eslint-disable-next-line prefer-destructuring buffer[channelOffset++] = blankB;
buffer[channelOffset++] = palette.rgb[1];
// eslint-disable-next-line prefer-destructuring
buffer[channelOffset++] = palette.rgb[2];
} }
} }
} }
function addShrunkenSubtileToTile(
subtilesInTile,
cell,
subtile,
buffer,
) {
const tileSize = TILE_SIZE;
const [dx, dy] = cell;
const offset = (dx + dy * subtilesInTile * tileSize / 4) * tileSize / 4;
const target = tileSize / 4;
let tr;
let tg;
let tb;
let pos;
let tmp;
const linePad = (tileSize * 3 - 1) * 3;
for (let row = 0; row < target; row += 1) {
let channelOffset = (offset + row * target * subtilesInTile) * 3;
const max = channelOffset + target * 3;
pos = row * tileSize * 12;
while (channelOffset < max) {
tr = subtile[pos++];
tg = subtile[pos++];
tb = subtile[pos++];
pos += 9;
tmp = pos + linePad;
buffer[channelOffset++] = (subtile[tmp++] + tr) / 2;
buffer[channelOffset++] = (subtile[tmp++] + tg) / 2;
buffer[channelOffset++] = (subtile[tmp++] + tb) / 2;
}
}
}
/*
* this was a failed try, it ended up being slow
* and low quality
function addShrunkenIndexedSubtilesToTile(
palette,
tileSize,
subtilesInTile,
cell,
inpTile,
buffer,
) {
const [dx, dy] = cell;
const subtileSize = tileSize / subtilesInTile;
const inpTileLength = inpTile.length;
const offset = (dx + dy * tileSize) * subtileSize;
const { rgb } = palette;
let tr;
let tg;
let tb;
let channelOffset;
let posA;
let posB;
let clr;
const linePad = (tileSize + 1) * (subtilesInTile - 1);
let amountFullRows = Math.floor(inpTileLength / subtilesInTile);
// use available data
for (let row = 0; row < amountFullRows; row += 1) {
channelOffset = (offset + row * tileSize) * 3;
const max = channelOffset + subtileSize * 3;
posA = row * tileSize * subtilesInTile;
posB = posA + linePad;
while (channelOffset < max) {
clr = (inpTile[posA] & 0x3F) * 3;
tr = rgb[clr++];
tg = rgb[clr++];
tb = rgb[clr];
posA += subtilesInTile;
clr = (inpTile[posB] & 0x3F) * 3;
buffer[channelOffset++] = (rgb[clr++] + tr) / 2;
buffer[channelOffset++] = (rgb[clr++] + tg) / 2;
buffer[channelOffset++] = (rgb[clr] + tb) / 2;
posB += subtilesInTile;
}
}
// padding the rest
[tr, tg, tb] = rgb;
if (inpTileLength % subtilesInTile) {
channelOffset = (offset + amountFullRows * tileSize) * 3;
const max = channelOffset + subtileSize * 3;
posA = amountFullRows * tileSize * subtilesInTile;
while (channelOffset < max) {
if (posA < inpTileLength) {
clr = (inpTile[posA] & 0x3F) * 3;
buffer[channelOffset++] = (rgb[clr++] + tr) / 2;
buffer[channelOffset++] = (rgb[clr++] + tg) / 2;
buffer[channelOffset++] = (rgb[clr] + tb) / 2;
posA += subtilesInTile;
} else {
buffer[channelOffset++] = tr;
buffer[channelOffset++] = tg;
buffer[channelOffset++] = tb;
}
}
amountFullRows += 1;
}
if (amountFullRows < subtileSize) {
for (let row = amountFullRows; row < subtileSize; row += 1) {
channelOffset = (offset + row * tileSize) * 3;
const max = channelOffset + subtileSize * 3;
while (channelOffset < max) {
buffer[channelOffset++] = tr;
buffer[channelOffset++] = tg;
buffer[channelOffset++] = tb;
}
}
}
}
*/
/* /*
* @param subtilesInTile how many subtiles are in a tile (per dimension) * @param subtilesInTile how many subtiles are in a tile (per dimension)
* @param cell where to add the tile [dx, dy] * @param cell where to add the tile [dx, dy]
@ -59,12 +171,13 @@ function addRGBSubtiletoTile(
subtile, subtile,
buffer, buffer,
) { ) {
const tileSize = TILE_SIZE;
const [dx, dy] = cell; const [dx, dy] = cell;
const chunkOffset = (dx + dy * subtilesInTile * TILE_SIZE) * TILE_SIZE; const chunkOffset = (dx + dy * subtilesInTile * tileSize) * tileSize;
let pos = 0; let pos = 0;
for (let row = 0; row < TILE_SIZE; row += 1) { for (let row = 0; row < tileSize; row += 1) {
let channelOffset = (chunkOffset + row * TILE_SIZE * subtilesInTile) * 3; let channelOffset = (chunkOffset + row * tileSize * subtilesInTile) * 3;
const max = channelOffset + TILE_SIZE * 3; const max = channelOffset + tileSize * 3;
while (channelOffset < max) { while (channelOffset < max) {
buffer[channelOffset++] = subtile[pos++]; buffer[channelOffset++] = subtile[pos++];
buffer[channelOffset++] = subtile[pos++]; buffer[channelOffset++] = subtile[pos++];
@ -87,8 +200,9 @@ function addIndexedSubtiletoTile(
subtile, subtile,
buffer, buffer,
) { ) {
const tileSize = TILE_SIZE;
const [dx, dy] = cell; const [dx, dy] = cell;
const chunkOffset = (dx + dy * subtilesInTile * TILE_SIZE) * TILE_SIZE; const chunkOffset = (dx + dy * subtilesInTile * tileSize) * tileSize;
const { rgb } = palette; const { rgb } = palette;
const emptyR = rgb[0]; const emptyR = rgb[0];
@ -97,9 +211,9 @@ function addIndexedSubtiletoTile(
let pos = 0; let pos = 0;
let clr; let clr;
for (let row = 0; row < TILE_SIZE; row += 1) { for (let row = 0; row < tileSize; row += 1) {
let channelOffset = (chunkOffset + row * TILE_SIZE * subtilesInTile) * 3; let channelOffset = (chunkOffset + row * tileSize * subtilesInTile) * 3;
const max = channelOffset + TILE_SIZE * 3; const max = channelOffset + tileSize * 3;
while (channelOffset < max) { while (channelOffset < max) {
if (pos < subtile.length) { if (pos < subtile.length) {
clr = (subtile[pos++] & 0x3F) * 3; clr = (subtile[pos++] & 0x3F) * 3;
@ -183,7 +297,13 @@ export async function createZoomTileFromChunk(
if (na.length !== TILE_ZOOM_LEVEL * TILE_ZOOM_LEVEL) { if (na.length !== TILE_ZOOM_LEVEL * TILE_ZOOM_LEVEL) {
na.forEach((element) => { na.forEach((element) => {
deleteSubtilefromTile(palette, TILE_ZOOM_LEVEL, element, tileRGBBuffer); deleteSubtilefromTile(
TILE_SIZE,
palette,
TILE_ZOOM_LEVEL,
element,
tileRGBBuffer,
);
}); });
const filename = tileFileName(canvasTileFolder, [maxTiledZoom - 1, x, y]); const filename = tileFileName(canvasTileFolder, [maxTiledZoom - 1, x, y]);
@ -227,7 +347,7 @@ export async function createZoomedTile(
) { ) {
const palette = gPalette || new Palette(canvas.colors); const palette = gPalette || new Palette(canvas.colors);
const tileRGBBuffer = new Uint8Array( const tileRGBBuffer = new Uint8Array(
TILE_SIZE * TILE_SIZE * TILE_ZOOM_LEVEL * TILE_ZOOM_LEVEL * 3, TILE_SIZE * TILE_SIZE * 3,
); );
const [z, x, y] = cell; const [z, x, y] = cell;
@ -243,7 +363,12 @@ export async function createZoomedTile(
} }
try { try {
const chunk = await sharp(chunkfile).removeAlpha().raw().toBuffer(); const chunk = await sharp(chunkfile).removeAlpha().raw().toBuffer();
addRGBSubtiletoTile(TILE_ZOOM_LEVEL, [dx, dy], chunk, tileRGBBuffer); addShrunkenSubtileToTile(
TILE_ZOOM_LEVEL,
[dx, dy],
chunk,
tileRGBBuffer,
);
} catch (error) { } catch (error) {
console.error( console.error(
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
@ -255,7 +380,13 @@ export async function createZoomedTile(
if (na.length !== TILE_ZOOM_LEVEL * TILE_ZOOM_LEVEL) { if (na.length !== TILE_ZOOM_LEVEL * TILE_ZOOM_LEVEL) {
na.forEach((element) => { na.forEach((element) => {
deleteSubtilefromTile(palette, TILE_ZOOM_LEVEL, element, tileRGBBuffer); deleteSubtilefromTile(
TILE_SIZE / TILE_ZOOM_LEVEL,
palette,
TILE_ZOOM_LEVEL,
element,
tileRGBBuffer,
);
}); });
const filename = tileFileName(canvasTileFolder, [z, x, y]); const filename = tileFileName(canvasTileFolder, [z, x, y]);
@ -265,12 +396,12 @@ export async function createZoomedTile(
tileRGBBuffer.buffer, tileRGBBuffer.buffer,
), { ), {
raw: { raw: {
width: TILE_SIZE * TILE_ZOOM_LEVEL, width: TILE_SIZE,
height: TILE_SIZE * TILE_ZOOM_LEVEL, height: TILE_SIZE,
channels: 3, channels: 3,
}, },
}, },
).resize(TILE_SIZE).toFile(filename); ).toFile(filename);
} catch (error) { } catch (error) {
console.error( console.error(
`Tiling: Error on createZoomedTile: ${error.message}`, `Tiling: Error on createZoomedTile: ${error.message}`,
@ -402,7 +533,7 @@ export async function createTexture(
} }
na.forEach((element) => { na.forEach((element) => {
deleteSubtilefromTile(palette, amount, element, textureBuffer); deleteSubtilefromTile(TILE_SIZE, palette, amount, element, textureBuffer);
}); });
const filename = `${canvasTileFolder}/texture.png`; const filename = `${canvasTileFolder}/texture.png`;

View File

@ -1,5 +1,3 @@
/* @flow
*/
// allow the websocket to be noisy on the console // allow the websocket to be noisy on the console
/* eslint-disable no-console */ /* eslint-disable no-console */
@ -15,36 +13,36 @@ import RegisterChunk from './packets/RegisterChunk';
import RegisterMultipleChunks from './packets/RegisterMultipleChunks'; import RegisterMultipleChunks from './packets/RegisterMultipleChunks';
import DeRegisterChunk from './packets/DeRegisterChunk'; import DeRegisterChunk from './packets/DeRegisterChunk';
import ChangedMe from './packets/ChangedMe'; import ChangedMe from './packets/ChangedMe';
import Ping from './packets/Ping';
const chunks = []; const chunks = [];
class SocketClient extends EventEmitter { class SocketClient extends EventEmitter {
url: string;
ws: WebSocket;
canvasId: number;
channelId: number;
timeConnected: number;
isConnected: number;
isConnecting: boolean;
msgQueue: Array;
constructor() { constructor() {
super(); super();
console.log('Creating WebSocketClient'); console.log('Creating WebSocketClient');
this.isConnecting = false;
this.isConnected = false;
this.ws = null; this.ws = null;
this.canvasId = '0'; this.canvasId = '0';
this.channelId = 0; this.channelId = 0;
/*
* properties set in connect and open:
* this.timeLastConnecting
* this.timeLastPing
* this.timeLastSent
*/
this.readyState = WebSocket.CLOSED;
this.msgQueue = []; this.msgQueue = [];
this.checkHealth = this.checkHealth.bind(this);
setInterval(this.checkHealth, 2000);
} }
async connect() { async connect() {
this.isConnecting = true; this.readyState = WebSocket.CONNECTING;
if (this.ws) { if (this.ws) {
console.log('WebSocket already open, not starting'); console.log('WebSocket already open, not starting');
} }
this.timeConnected = Date.now(); this.timeLastConnecting = Date.now();
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const url = `${protocol}//${window.location.hostname}${ const url = `${protocol}//${window.location.hostname}${
window.location.port ? `:${window.location.port}` : '' window.location.port ? `:${window.location.port}` : ''
@ -54,18 +52,34 @@ class SocketClient extends EventEmitter {
this.ws.onopen = this.onOpen.bind(this); this.ws.onopen = this.onOpen.bind(this);
this.ws.onmessage = this.onMessage.bind(this); this.ws.onmessage = this.onMessage.bind(this);
this.ws.onclose = this.onClose.bind(this); this.ws.onclose = this.onClose.bind(this);
this.ws.onerror = this.onError.bind(this); this.ws.onerror = (err) => {
console.error('Socket encountered error, closing socket', err);
};
}
checkHealth() {
if (this.readyState === WebSocket.OPEN) {
const now = Date.now();
if (now - 14000 > this.timeLastPing) {
// server didn't send anything, probably dead
console.log('Server is silent, killing websocket');
this.readyState = WebSocket.CLOSING;
this.ws.close();
}
if (now - 10000 > this.timeLastSent) {
// make sure we send something at least all 12s
this.sendWhenReady(Ping.dehydrate());
}
}
} }
sendWhenReady(msg) { sendWhenReady(msg) {
if (this.isConnected) { this.timeLastSent = Date.now();
if (this.readyState === WebSocket.OPEN) {
this.ws.send(msg); this.ws.send(msg);
} else { } else {
console.log('Tried sending message when websocket was closed!'); console.log('Tried sending message when websocket was closed!');
this.msgQueue.push(msg); this.msgQueue.push(msg);
if (!this.isConnecting) {
this.connect();
}
} }
} }
@ -76,8 +90,11 @@ class SocketClient extends EventEmitter {
} }
onOpen() { onOpen() {
this.isConnecting = false; const now = Date.now();
this.isConnected = true; this.timeLastPing = now;
this.timeLastSent = now;
this.readyState = WebSocket.OPEN;
this.emit('open', {}); this.emit('open', {});
if (this.canvasId !== null) { if (this.canvasId !== null) {
this.ws.send(RegisterCanvas.dehydrate(this.canvasId)); this.ws.send(RegisterCanvas.dehydrate(this.canvasId));
@ -87,11 +104,6 @@ class SocketClient extends EventEmitter {
this.ws.send(RegisterMultipleChunks.dehydrate(chunks)); this.ws.send(RegisterMultipleChunks.dehydrate(chunks));
} }
onError(err) {
console.error('Socket encountered error, closing socket', err);
this.ws.close();
}
setCanvas(canvasId) { setCanvas(canvasId) {
/* canvasId can be string or integer, thanks to /* canvasId can be string or integer, thanks to
* JSON not allowing integer keys * JSON not allowing integer keys
@ -111,14 +123,18 @@ class SocketClient extends EventEmitter {
const chunkid = (i << 8) | j; const chunkid = (i << 8) | j;
chunks.push(chunkid); chunks.push(chunkid);
const buffer = RegisterChunk.dehydrate(chunkid); const buffer = RegisterChunk.dehydrate(chunkid);
if (this.isConnected) this.ws.send(buffer); if (this.readyState === WebSocket.OPEN) {
this.ws.send(buffer);
}
} }
deRegisterChunk(cell) { deRegisterChunk(cell) {
const [i, j] = cell; const [i, j] = cell;
const chunkid = (i << 8) | j; const chunkid = (i << 8) | j;
const buffer = DeRegisterChunk.dehydrate(chunkid); const buffer = DeRegisterChunk.dehydrate(chunkid);
if (this.isConnected) this.ws.send(buffer); if (this.readyState === WebSocket.OPEN) {
this.ws.send(buffer);
}
const pos = chunks.indexOf(chunkid); const pos = chunks.indexOf(chunkid);
if (~pos) chunks.splice(pos, 1); if (~pos) chunks.splice(pos, 1);
} }
@ -129,17 +145,15 @@ class SocketClient extends EventEmitter {
* @param pixel Array of [[offset, color],...] pixels within chunk * @param pixel Array of [[offset, color],...] pixels within chunk
*/ */
requestPlacePixels( requestPlacePixels(
i: number, j: number, i, j,
pixels: Array, pixels,
) { ) {
const buffer = PixelUpdate.dehydrate(i, j, pixels); const buffer = PixelUpdate.dehydrate(i, j, pixels);
this.sendWhenReady(buffer); this.sendWhenReady(buffer);
} }
sendChatMessage(message, channelId) { sendChatMessage(message, channelId) {
if (this.isConnected) { this.sendWhenReady(JSON.stringify([message, channelId]));
this.ws.send(JSON.stringify([message, channelId]));
}
} }
onMessage({ data: message }) { onMessage({ data: message }) {
@ -196,6 +210,10 @@ class SocketClient extends EventEmitter {
this.emit('pixelReturn', PixelReturn.hydrate(data)); this.emit('pixelReturn', PixelReturn.hydrate(data));
break; break;
case OnlineCounter.OP_CODE: case OnlineCounter.OP_CODE:
/*
* using online counter as sign-of-life ping
*/
this.timeLastPing = Date.now();
this.emit('onlineCounter', OnlineCounter.hydrate(data)); this.emit('onlineCounter', OnlineCounter.hydrate(data));
break; break;
case CoolDownPacket.OP_CODE: case CoolDownPacket.OP_CODE:
@ -215,9 +233,9 @@ class SocketClient extends EventEmitter {
onClose(e) { onClose(e) {
this.emit('close'); this.emit('close');
this.ws = null; this.ws = null;
this.isConnected = false; this.readyState = WebSocket.CONNECTING;
// reconnect in 1s if last connect was longer than 7s ago, else 5s // reconnect in 1s if last connect was longer than 7s ago, else 5s
const timeout = this.timeConnected < Date.now() - 7000 ? 1000 : 5000; const timeout = this.timeLastConnecting < Date.now() - 7000 ? 1000 : 5000;
console.warn( console.warn(
`Socket is closed. Reconnect will be attempted in ${timeout} ms.`, `Socket is closed. Reconnect will be attempted in ${timeout} ms.`,
e.reason, e.reason,
@ -226,18 +244,14 @@ class SocketClient extends EventEmitter {
setTimeout(() => this.connect(), 5000); setTimeout(() => this.connect(), 5000);
} }
close() {
this.ws.close();
}
reconnect() { reconnect() {
if (this.isConnected) { if (this.readyState === WebSocket.OPEN) {
this.isConnected = false; this.readyState = WebSocket.CLOSING;
console.log('Restarting WebSocket'); console.log('Restarting WebSocket');
this.ws.onclose = null; this.ws.onclose = null;
this.ws.onmessage = null; this.ws.onmessage = null;
this.ws.close();
this.ws = null; this.ws = null;
this.ws.close();
this.connect(); this.connect();
} }
} }

View File

@ -60,8 +60,8 @@ class SocketServer {
this.broadcast = this.broadcast.bind(this); this.broadcast = this.broadcast.bind(this);
this.broadcastPixelBuffer = this.broadcastPixelBuffer.bind(this); this.broadcastPixelBuffer = this.broadcastPixelBuffer.bind(this);
this.reloadUser = this.reloadUser.bind(this); this.reloadUser = this.reloadUser.bind(this);
this.ping = this.ping.bind(this);
this.onlineCounterBroadcast = this.onlineCounterBroadcast.bind(this); this.onlineCounterBroadcast = this.onlineCounterBroadcast.bind(this);
this.checkHealth = this.checkHealth.bind(this);
} }
initialize() { initialize() {
@ -83,9 +83,8 @@ class SocketServer {
}); });
wss.on('connection', async (ws, req) => { wss.on('connection', async (ws, req) => {
ws.isAlive = true; ws.timeLastMsg = Date.now();
ws.canvasId = null; ws.canvasId = null;
ws.startDate = Date.now();
const user = await authenticateClient(req); const user = await authenticateClient(req);
if (!user) { if (!user) {
ws.close(); ws.close();
@ -103,16 +102,13 @@ class SocketServer {
logger.error(`WebSocket Client Error for ${ws.name}: ${e.message}`); logger.error(`WebSocket Client Error for ${ws.name}: ${e.message}`);
}); });
ws.on('pong', () => {
ws.isAlive = true;
});
ws.on('close', () => { ws.on('close', () => {
ipCounter.delete(ip); ipCounter.delete(ip);
this.deleteAllChunks(ws); this.deleteAllChunks(ws);
}); });
ws.on('message', (data, isBinary) => { ws.on('message', (data, isBinary) => {
ws.timeLastMsg = Date.now();
if (isBinary) { if (isBinary) {
this.onBinaryMessage(data, ws); this.onBinaryMessage(data, ws);
} else { } else {
@ -179,7 +175,7 @@ class SocketServer {
}); });
setInterval(this.onlineCounterBroadcast, 10 * 1000); setInterval(this.onlineCounterBroadcast, 10 * 1000);
setInterval(this.ping, 15 * 1000); setInterval(this.checkHealth, 15 * 1000);
} }
verifyClient(info, done) { verifyClient(info, done) {
@ -351,16 +347,15 @@ class SocketServer {
}); });
} }
ping() { checkHealth() {
const ts = Date.now() - 15000;
this.wss.clients.forEach((ws) => { this.wss.clients.forEach((ws) => {
if (!ws.isAlive) { if (
if (ws.user) { ws.readyState === WebSocket.OPEN
logger.info(`Killing dead websocket from ${ws.user.ip}`); && ts > ws.timeLastMsg
} ) {
logger.info(`Killing dead websocket from ${ws.user.ip}`);
ws.terminate(); ws.terminate();
} else {
ws.isAlive = false;
ws.ping(() => {});
} }
}); });
} }

View File

@ -5,9 +5,6 @@ export default {
dehydrate() { dehydrate() {
// Server (sender) // Server (sender)
const buffer = new ArrayBuffer(1); return new Uint8Array([OP_CODE]).buffer;
const view = new DataView(buffer);
view.setInt8(0, OP_CODE);
return buffer;
}, },
}; };

View File

@ -0,0 +1,10 @@
const OP_CODE = 0xB0;
export default {
OP_CODE,
dehydrate() {
// Client (sender)
return new Uint8Array([OP_CODE]).buffer;
},
};

View File

@ -181,7 +181,6 @@ class ChunkLoader {
) { ) {
const chunkKey = `${zoom}:${cx}:${cy}`; const chunkKey = `${zoom}:${cx}:${cy}`;
let chunkRGB = this.chunks.get(chunkKey); let chunkRGB = this.chunks.get(chunkKey);
const { canvasId } = this;
if (chunkRGB) { if (chunkRGB) {
if (chunkRGB.ready) { if (chunkRGB.ready) {
return chunkRGB.image; return chunkRGB.image;
@ -200,7 +199,7 @@ class ChunkLoader {
const preLoad = this.preLoadChunk(zoom, cx, cy, chunkRGB); const preLoad = this.preLoadChunk(zoom, cx, cy, chunkRGB);
if (preLoad) return preLoad; if (preLoad) return preLoad;
} }
return (showLoadingTile) ? loadingTiles.getTile(canvasId) : null; return (showLoadingTile) ? loadingTiles.getTile(this.canvasId) : null;
} }
getHistoricalChunk(cx, cy, fetch, historicalDate, historicalTime = null) { getHistoricalChunk(cx, cy, fetch, historicalDate, historicalTime = null) {