pixelplanet/src/core/rollback.js
2022-06-19 23:24:54 +02:00

124 lines
3.6 KiB
JavaScript

/*
* Rolls back an area of the canvas to a specific date
*
* @flow
*/
// Tile creation is allowed to be slow
/* eslint-disable no-await-in-loop */
import fs from 'fs';
import path from 'path';
import sharp from 'sharp';
import RedisCanvas from '../data/redis/RedisCanvas';
import logger from './logger';
import { getChunkOfPixel } from './utils';
import Palette from './Palette';
import { TILE_SIZE } from './constants';
import { BACKUP_DIR } from './config';
// eslint-disable-next-line import/no-unresolved
import canvases from './canvases.json';
export default async function rollbackToDate(
canvasId: number,
x: number,
y: number,
width: number,
height: number,
date: string,
) {
if (!BACKUP_DIR) {
return 0;
}
const dir = path.resolve(__dirname, BACKUP_DIR);
// eslint-disable-next-line max-len
const backupDir = `${dir}/${date.slice(0, 4)}/${date.slice(4, 6)}/${date.slice(6)}/${canvasId}/tiles`;
if (!fs.existsSync(backupDir)) {
return 0;
}
logger.info(
`Rollback area ${width}/${height} to ${x}/${y}/${canvasId} to date ${date}`,
);
const canvas = canvases[canvasId];
const { colors, size } = canvas;
const palette = new Palette(colors);
const canvasMinXY = -(size / 2);
const [ucx, ucy] = getChunkOfPixel(size, x, y);
const [lcx, lcy] = getChunkOfPixel(size, x + width, y + height);
let totalPxlCnt = 0;
logger.info(`Loading to chunks from ${ucx} / ${ucy} to ${lcx} / ${lcy} ...`);
let empty = false;
let emptyBackup = false;
let backupChunk;
for (let cx = ucx; cx <= lcx; cx += 1) {
for (let cy = ucy; cy <= lcy; cy += 1) {
let chunk = null;
try {
chunk = await RedisCanvas.getChunk(canvasId, cx, cy, TILE_SIZE ** 2);
} catch (error) {
logger.error(
// eslint-disable-next-line max-len
`Chunk ch:${canvasId}:${cx}:${cy} could not be loaded from redis, assuming empty.`,
);
}
if (!chunk || !chunk.length) {
chunk = new Uint8Array(TILE_SIZE * TILE_SIZE);
empty = true;
} else {
chunk = new Uint8Array(chunk);
empty = false;
}
try {
emptyBackup = false;
backupChunk = await sharp(`${backupDir}/${cx}/${cy}.png`)
.ensureAlpha()
.raw()
.toBuffer();
backupChunk = new Uint32Array(backupChunk.buffer);
} catch {
logger.info(
// eslint-disable-next-line max-len
`Backup chunk ${backupDir}/${cx}/${cy}.png could not be loaded, assuming empty.`,
);
emptyBackup = true;
}
let pxlCnt = 0;
if (!empty || !emptyBackup) {
// offset of chunk in image
const cOffX = cx * TILE_SIZE + canvasMinXY - x;
const cOffY = cy * TILE_SIZE + canvasMinXY - y;
let cOff = 0;
for (let py = 0; py < TILE_SIZE; py += 1) {
for (let px = 0; px < TILE_SIZE; px += 1) {
const clrX = cOffX + px;
const clrY = cOffY + py;
if (clrX >= 0 && clrY >= 0 && clrX < width && clrY < height) {
const pixel = (emptyBackup)
? 0 : palette.abgr.indexOf(backupChunk[cOff]);
if (pixel !== -1) {
chunk[cOff] = pixel;
pxlCnt += 1;
}
}
cOff += 1;
}
}
}
if (pxlCnt) {
const ret = await RedisCanvas.setChunk(cx, cy, chunk, canvasId);
if (ret) {
logger.info(`Loaded ${pxlCnt} pixels into chunk ${cx}, ${cy}.`);
totalPxlCnt += pxlCnt;
}
}
chunk = null;
}
}
logger.info('Rollback done.');
return totalPxlCnt;
}