pixelplanet/src/core/tileserver.js
2022-09-11 14:54:13 +02:00

209 lines
5.3 KiB
JavaScript

/*
* creation of zoom tiles
*
*/
import fs from 'fs';
import { Worker } from 'worker_threads';
import logger from './logger';
import canvases from './canvases';
import socketEvents from '../socket/socketEvents';
import { TILE_FOLDER } from './config';
import {
TILE_SIZE,
TILE_ZOOM_LEVEL,
} from './constants';
import { mod, getMaxTiledZoom } from './utils';
const CanvasUpdaters = {};
/*
* worker thread
*/
const worker = new Worker('./workers/tilewriter.js');
/*
* queue of tasks that is worked on in FIFO
*/
const taskQueue = [];
function enqueueTask(task) {
if (!taskQueue.length) {
worker.postMessage(task);
}
taskQueue.push(task);
}
worker.on('message', () => {
taskQueue.shift();
if (taskQueue.length) {
worker.postMessage(taskQueue[0]);
}
});
/*
* every canvas gets an instance of this class
*/
class CanvasUpdater {
TileLoadingQueues;
id;
canvas;
firstZoomtileWidth;
canvasTileFolder;
constructor(id) {
this.updateZoomlevelTiles = this.updateZoomlevelTiles.bind(this);
this.TileLoadingQueues = [];
this.id = id;
this.canvas = canvases[id];
this.canvasTileFolder = `${TILE_FOLDER}/${id}`;
this.firstZoomtileWidth = this.canvas.size / TILE_SIZE / TILE_ZOOM_LEVEL;
this.maxTiledZoom = getMaxTiledZoom(this.canvas.size);
}
/*
* @param zoom tilezoomlevel to update
*/
async updateZoomlevelTiles(zoom) {
const queue = this.TileLoadingQueues[zoom];
if (typeof queue === 'undefined') return;
const tile = queue.shift();
if (typeof tile !== 'undefined') {
const width = TILE_ZOOM_LEVEL ** zoom;
const cx = mod(tile, width);
const cy = Math.floor(tile / width);
if (zoom === this.maxTiledZoom - 1) {
enqueueTask({
task: 'createZoomTileFromChunk',
args: [
this.id,
this.canvas,
this.canvasTileFolder,
[cx, cy],
],
});
} else if (zoom !== this.maxTiledZoom) {
enqueueTask({
task: 'createZoomedTile',
args: [
this.canvas,
this.canvasTileFolder,
[zoom, cx, cy],
],
});
}
if (zoom === 0) {
enqueueTask({
task: 'createTexture',
args: [
this.id,
this.canvas,
this.canvasTileFolder,
],
});
} else {
const [ucx, ucy] = [cx, cy].map((z) => Math.floor(z / TILE_ZOOM_LEVEL));
const upperTile = ucx + ucy * (TILE_ZOOM_LEVEL ** (zoom - 1));
const upperQueue = this.TileLoadingQueues[zoom - 1];
if (~upperQueue.indexOf(upperTile)) return;
upperQueue.push(upperTile);
logger.info(`Tiling: Enqueued ${zoom - 1}, ${ucx}, ${ucy} for reload`);
}
}
}
/*
* register changed chunk, queue corespongind tile to reload
* @param chunk Chunk coordinates
*/
registerChunkChange(chunk) {
const queue = this.TileLoadingQueues[Math.max(this.maxTiledZoom - 1, 0)];
if (typeof queue === 'undefined') return;
const [cx, cy] = chunk.map((z) => Math.floor(z / TILE_ZOOM_LEVEL));
const chunkOffset = cx + cy * this.firstZoomtileWidth;
if (~queue.indexOf(chunkOffset)) return;
queue.push(chunkOffset);
/*
logger.info(
`Tiling: Enqueued ${cx}, ${cy} / ${this.id} for basezoom reload`,
);
*/
}
/*
* initialize queues and start loops for updating tiles
*/
initialize() {
logger.info(`Tiling: Using folder ${this.canvasTileFolder}`);
if (!fs.existsSync(`${this.canvasTileFolder}/0`)) {
if (!fs.existsSync(this.canvasTileFolder)) {
fs.mkdirSync(this.canvasTileFolder);
}
logger.warn(
'Tiling: tiledir empty, will initialize it, this can take some time',
);
enqueueTask({
task: 'initializeTiles',
args: [
this.id,
this.canvas,
this.canvasTileFolder,
false,
],
});
}
for (let c = 0; c < this.maxTiledZoom; c += 1) {
this.TileLoadingQueues.push([]);
const invZoom = this.maxTiledZoom - c;
// eslint-disable-next-line max-len
const timeout = TILE_ZOOM_LEVEL ** (2 * invZoom) * (6 / TILE_ZOOM_LEVEL ** 2) * 1000;
logger.info(
`Tiling: Set interval for zoomlevel ${c} update to ${timeout / 1000}`,
);
setTimeout(() => {
setInterval(this.updateZoomlevelTiles, timeout, c);
}, Math.floor(Math.random() * timeout));
}
if (this.maxTiledZoom === 0) {
// in the case of canvasSize == 256
this.TileLoadingQueues.push([]);
const timeout = 5 * 60 * 1000;
setTimeout(() => {
setInterval(this.updateZoomlevelTiles, timeout, 0);
}, Math.floor(Math.random() * timeout));
}
}
}
socketEvents.on('chunkUpdate', (canvasId, chunk) => {
if (CanvasUpdaters[canvasId]) {
CanvasUpdaters[canvasId].registerChunkChange(chunk);
}
});
/*
* starting update loops for canvases
*/
export default function startAllCanvasLoops() {
if (!fs.existsSync(`${TILE_FOLDER}`)) fs.mkdirSync(`${TILE_FOLDER}`);
const ids = Object.keys(canvases);
for (let i = 0; i < ids.length; i += 1) {
const id = parseInt(ids[i], 10);
const canvas = canvases[id];
if (!canvas.v) {
// just 2D canvases
const updater = new CanvasUpdater(id);
updater.initialize();
CanvasUpdaters[id] = updater;
}
}
}