pixelplanet/src/core/tileserver.js

209 lines
5.3 KiB
JavaScript
Raw Normal View History

/*
* creation of zoom tiles
2020-01-02 16:58:06 +00:00
*
*/
import fs from 'fs';
2022-04-04 03:09:39 +00:00
import { Worker } from 'worker_threads';
2020-01-02 16:58:06 +00:00
import logger from './logger';
import canvases from './canvases';
2022-09-09 22:35:28 +00:00
import socketEvents from '../socket/socketEvents';
2020-01-02 16:58:06 +00:00
import { TILE_FOLDER } from './config';
2020-01-04 06:00:47 +00:00
import {
TILE_SIZE,
TILE_ZOOM_LEVEL,
} from './constants';
import { mod, getMaxTiledZoom } from './utils';
2020-01-02 16:58:06 +00:00
const CanvasUpdaters = {};
/*
* worker thread
*/
const worker = new Worker('./workers/tilewriter.js');
2022-06-21 02:39:07 +00:00
/*
* 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
*/
2020-01-02 16:58:06 +00:00
class CanvasUpdater {
TileLoadingQueues;
id;
canvas;
firstZoomtileWidth;
canvasTileFolder;
constructor(id) {
2020-01-02 16:58:06 +00:00
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) {
2020-01-02 16:58:06 +00:00
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) {
2022-06-21 02:39:07 +00:00
enqueueTask({
task: 'createZoomTileFromChunk',
args: [
this.id,
this.canvas,
this.canvasTileFolder,
[cx, cy],
],
});
2020-01-02 16:58:06 +00:00
} else if (zoom !== this.maxTiledZoom) {
2022-06-21 02:39:07 +00:00
enqueueTask({
task: 'createZoomedTile',
args: [
this.canvas,
this.canvasTileFolder,
[zoom, cx, cy],
],
});
2020-01-02 16:58:06 +00:00
}
if (zoom === 0) {
2022-06-21 02:39:07 +00:00
enqueueTask({
task: 'createTexture',
args: [
this.id,
this.canvas,
this.canvasTileFolder,
],
});
2020-01-02 16:58:06 +00:00
} else {
2022-06-30 12:39:03 +00:00
const [ucx, ucy] = [cx, cy].map((z) => Math.floor(z / TILE_ZOOM_LEVEL));
2020-01-02 16:58:06 +00:00
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) {
2020-01-02 16:58:06 +00:00
const queue = this.TileLoadingQueues[Math.max(this.maxTiledZoom - 1, 0)];
if (typeof queue === 'undefined') return;
2022-06-30 12:29:15 +00:00
const [cx, cy] = chunk.map((z) => Math.floor(z / TILE_ZOOM_LEVEL));
2020-01-02 16:58:06 +00:00
const chunkOffset = cx + cy * this.firstZoomtileWidth;
if (~queue.indexOf(chunkOffset)) return;
queue.push(chunkOffset);
2022-09-11 12:54:13 +00:00
/*
2020-01-07 17:50:56 +00:00
logger.info(
`Tiling: Enqueued ${cx}, ${cy} / ${this.id} for basezoom reload`,
);
2022-09-11 12:54:13 +00:00
*/
2020-01-02 16:58:06 +00:00
}
/*
* initialize queues and start loops for updating tiles
*/
2022-06-21 02:39:07 +00:00
initialize() {
2020-01-02 16:58:06 +00:00
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',
);
2022-06-21 02:39:07 +00:00
enqueueTask({
task: 'initializeTiles',
args: [
this.id,
this.canvas,
this.canvasTileFolder,
false,
],
});
2020-01-02 16:58:06 +00:00
}
for (let c = 0; c < this.maxTiledZoom; c += 1) {
this.TileLoadingQueues.push([]);
2022-06-27 21:47:22 +00:00
const invZoom = this.maxTiledZoom - c;
2022-06-27 12:03:52 +00:00
// eslint-disable-next-line max-len
2022-06-27 21:47:22 +00:00
const timeout = TILE_ZOOM_LEVEL ** (2 * invZoom) * (6 / TILE_ZOOM_LEVEL ** 2) * 1000;
2020-01-02 16:58:06 +00:00
logger.info(
`Tiling: Set interval for zoomlevel ${c} update to ${timeout / 1000}`,
);
2022-06-27 09:27:37 +00:00
setTimeout(() => {
setInterval(this.updateZoomlevelTiles, timeout, c);
}, Math.floor(Math.random() * timeout));
2020-01-02 16:58:06 +00:00
}
if (this.maxTiledZoom === 0) {
2020-01-03 03:12:21 +00:00
// in the case of canvasSize == 256
2020-01-02 16:58:06 +00:00
this.TileLoadingQueues.push([]);
2022-06-27 09:27:37 +00:00
const timeout = 5 * 60 * 1000;
setTimeout(() => {
setInterval(this.updateZoomlevelTiles, timeout, 0);
}, Math.floor(Math.random() * timeout));
2020-01-02 16:58:06 +00:00
}
}
}
2022-09-09 22:35:28 +00:00
socketEvents.on('chunkUpdate', (canvasId, chunk) => {
if (CanvasUpdaters[canvasId]) {
CanvasUpdaters[canvasId].registerChunkChange(chunk);
}
2022-09-09 22:35:28 +00:00
});
2020-01-02 16:58:06 +00:00
/*
* starting update loops for canvases
*/
2022-09-09 22:35:28 +00:00
export default function startAllCanvasLoops() {
2020-01-02 16:58:06 +00:00
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);
2022-06-21 02:39:07 +00:00
updater.initialize();
2022-06-20 14:47:23 +00:00
CanvasUpdaters[id] = updater;
}
2020-01-02 16:58:06 +00:00
}
}