Merge branch 'devel'
This commit is contained in:
commit
a069bb9d75
183
src/core/Tile.js
183
src/core/Tile.js
|
@ -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`;
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(() => {});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
10
src/socket/packets/Ping.js
Normal file
10
src/socket/packets/Ping.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
const OP_CODE = 0xB0;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
OP_CODE,
|
||||||
|
|
||||||
|
dehydrate() {
|
||||||
|
// Client (sender)
|
||||||
|
return new Uint8Array([OP_CODE]).buffer;
|
||||||
|
},
|
||||||
|
};
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user