diff --git a/src/components/App.jsx b/src/components/App.jsx
index 04d0c18..edaf23d 100644
--- a/src/components/App.jsx
+++ b/src/components/App.jsx
@@ -20,7 +20,6 @@ import Menu from './Menu';
import UI from './UI';
import ExpandMenuButton from './ExpandMenuButton';
import ModalRoot from './ModalRoot';
-import ChristmasButton from './ChristmasButton';
const App = () => (
@@ -36,7 +35,6 @@ const App = () => (
-
);
diff --git a/src/components/ChristmasButton.jsx b/src/components/ChristmasButton.jsx
deleted file mode 100644
index d82fd7b..0000000
--- a/src/components/ChristmasButton.jsx
+++ /dev/null
@@ -1,55 +0,0 @@
-/**
- *
- * @flow
- */
-
-import React, { useState } from 'react';
-import { GiPineTree } from 'react-icons/gi';
-
-import SnowStorm from '../ui/snow';
-
-const videoIds = [
- '-YUH8Xfz-jg',
- 'yXQViqx6GMY',
- 'is4NQkUN3AI',
- 'xjLTDaCUYuQ',
-];
-
-const snowStorm = new SnowStorm(window, document);
-
-const ChristmasButton = () => {
- const [playing, setPlaying] = useState(false);
- const prot = window.location.protocol;
-
- const video = videoIds[Math.floor(Math.random() * videoIds.length)];
- const url = `${prot}//www.youtube.com/embed/${video}?autoplay=1&loop=1`;
-
- return (
- {
- setPlaying(!playing);
- snowStorm.toggleSnow();
- }}
- style={{
- boxShadow: (playing)
- ? '0 0 9px 6px rgba(0, 189, 47, 0.8)'
- : '0 0 9px 6px rgba(189, 0, 0, 0.8)',
- }}
- >
-
- {(playing) && (
-
- )}
-
- );
-};
-
-export default ChristmasButton;
diff --git a/src/core/PixelCache.js b/src/core/PixelCache.js
new file mode 100644
index 0000000..eff9556
--- /dev/null
+++ b/src/core/PixelCache.js
@@ -0,0 +1,68 @@
+/*
+ * Caching pixels for a few ms before sending them
+ * in bursts per chunk
+ * @flow
+ */
+
+import webSockets from '../socket/websockets';
+
+class PixelCache {
+ PXL_CACHE: Map;
+
+ constructor() {
+ this.PXL_CACHE = new Map();
+ this.flushCache = this.flushCache.bind(this);
+ }
+
+ /*
+ * append pixel to cache
+ * @param canvasId canvas id
+ * @param i Chunk coordinates
+ * @param j Chunk coordinates
+ * @param offset Offset of pixel within chunk
+ * @param color color index of pixel
+ */
+ append(
+ canvasId: number,
+ color: number,
+ i: number,
+ j: number,
+ offset: number,
+ ) {
+ const { PXL_CACHE } = this;
+ const chunkCanvasId = (canvasId << 16) | (i << 8) | j;
+
+ const pxls = PXL_CACHE.get(chunkCanvasId);
+ const newpxl = Buffer.allocUnsafe(4);
+
+ newpxl.writeUInt8(offset >>> 16, 0);
+ newpxl.writeUInt16BE(offset & 0x00FFFF, 1);
+ newpxl.writeUInt8(color, 3);
+
+ if (typeof pxls === 'undefined') {
+ PXL_CACHE.set(chunkCanvasId, newpxl);
+ } else {
+ PXL_CACHE.set(
+ chunkCanvasId,
+ Buffer.concat([pxls, newpxl]),
+ );
+ }
+ }
+
+ flushCache() {
+ const { PXL_CACHE: cache } = this;
+ this.PXL_CACHE = new Map();
+
+ cache.forEach((pxls, chunkCanvasId) => {
+ const canvasId = chunkCanvasId & 0xFF0000 >> 16;
+ const chunkId = chunkCanvasId & 0x00FFFF;
+ webSockets.broadcastPixels(canvasId, chunkId, pxls);
+ });
+ }
+}
+
+const pixelCache = new PixelCache();
+// send pixels from cache to websockets every 20ms
+setInterval(pixelCache.flushCache, 20);
+
+export default pixelCache;
diff --git a/src/core/Player.js b/src/core/Player.js
deleted file mode 100644
index 59f0877..0000000
--- a/src/core/Player.js
+++ /dev/null
@@ -1,16 +0,0 @@
-/* @flow */
-
-
-class Player {
- wait: ?number; // date
-
- constructor() {
- this.wait = null;
- }
-
- setWait(wait) {
- this.wait = wait;
- }
-}
-
-export default Player;
diff --git a/src/core/Void.js b/src/core/Void.js
index 8b9e230..b33a763 100644
--- a/src/core/Void.js
+++ b/src/core/Void.js
@@ -178,23 +178,25 @@ class Void extends WebSocketEvents {
const {
i: pi,
j: pj,
- offset: off,
- color,
+ pixels,
} = PixelUpdate.hydrate(buffer);
- if (color <= 2 || color === 25) {
- const { i, j } = this;
- // 3x3 chunk area (this is hardcoded on multiple places)
- if (pi >= i - 1 && pi <= i + 1 && pj >= j - 1 && pj <= j + 1) {
- const uOff = (pi - i + 1) * TILE_SIZE;
- const vOff = (pj - j + 1) * TILE_SIZE;
- const x = uOff + off % TILE_SIZE;
- const y = vOff + Math.floor(off / TILE_SIZE);
- if (this.isSet(x, y, true)) {
- this.pixelStack.push([x, y]);
- } else {
- this.userArea[x + y * TILE_SIZE * 3] = color;
+ const { i, j } = this;
+ // 3x3 chunk area (this is hardcoded on multiple places)
+ if (pi >= i - 1 && pi <= i + 1 && pj >= j - 1 && pj <= j + 1) {
+ pixels.forEach((pxl) => {
+ const [off, color] = pxl;
+ if (color <= 2 || color === 25) {
+ const uOff = (pi - i + 1) * TILE_SIZE;
+ const vOff = (pj - j + 1) * TILE_SIZE;
+ const x = uOff + off % TILE_SIZE;
+ const y = vOff + Math.floor(off / TILE_SIZE);
+ if (this.isSet(x, y, true)) {
+ this.pixelStack.push([x, y]);
+ } else {
+ this.userArea[x + y * TILE_SIZE * 3] = color;
+ }
}
- }
+ });
}
}
}
diff --git a/src/core/draw.js b/src/core/draw.js
index 17b0412..8e51283 100644
--- a/src/core/draw.js
+++ b/src/core/draw.js
@@ -36,7 +36,6 @@ import { THREE_CANVAS_HEIGHT, THREE_TILE_SIZE, TILE_SIZE } from './constants';
export async function drawByOffsets(
user: User,
canvasId: number,
- color: ColorIndex,
i: number,
j: number,
pixels: Array,
@@ -44,6 +43,7 @@ export async function drawByOffsets(
let wait = 0;
let coolDown = 0;
let retCode = 0;
+ let pxlCnt = 0;
const canvas = canvases[canvasId];
if (!canvas) {
@@ -54,9 +54,11 @@ export async function drawByOffsets(
retCode: 1,
};
}
- const { size: canvasSize, v: is3d } = canvas;
+ const { size: canvasSize, v: is3d } = canvas;
try {
+ wait = await user.getWait(canvasId);
+
const tileSize = (is3d) ? THREE_TILE_SIZE : TILE_SIZE;
/*
* canvas/chunk validation
@@ -82,72 +84,82 @@ export async function drawByOffsets(
}
const isAdmin = (user.userlvl === 1);
- /*
- * TODO benchmark if requesting by pixel or chunk
- * is better
- */
- const chunk = await RedisCanvas.getChunk(canvasId, i, j);
- const setColor = await RedisCanvas.getPixelByOffset(canvasId, i, j, offset);
-
- /*
- * pixel validation
- */
- const maxSize = (is3d) ? tileSize * tileSize * THREE_CANVAS_HEIGHT
- : tileSize * tileSize;
- if (offset >= maxSize) {
- // z out of bounds or weird stuff
- throw new Error(4);
- }
- if (color >= canvas.colors.length
- || (color < canvas.cli && !(canvas.v && color === 0))
- ) {
- // color out of bounds
- throw new Error(5);
- }
-
- if (setColor & 0x80
- /* 3D Canvas Minecraft Avatars */
- // && x >= 96 && x <= 128 && z >= 35 && z <= 100
- // 96 - 128 on x
- // 32 - 128 on z
- || (canvas.v && i === 19 && j >= 17 && j < 20 && !isAdmin)
- ) {
- // protected pixel
- throw new Error(8);
- }
-
- coolDown = (setColor & 0x3F) < canvas.cli ? canvas.bcd : canvas.pcd;
- if (isAdmin) {
- coolDown = 0.0;
- } else if (rpgEvent.success) {
+ let coolDownFactor = 1;
+ if (rpgEvent.success) {
if (rpgEvent.success === 1) {
- // if HOURLY_EVENT got won
- coolDown /= 2;
+ // if hourly event got won
+ coolDownFactor = 0.5;
} else {
- // if HOURLY_EVENT got lost
- coolDown *= 2;
+ // if hourly event got lost
+ coolDownFactor = 2;
}
}
- const now = Date.now();
- wait = await user.getWait(canvasId);
- if (!wait) wait = now;
- wait += coolDown;
- const waitLeft = wait - now;
- if (waitLeft > canvas.cds) {
- // cooldown stack used
- wait = waitLeft - coolDown;
- coolDown = canvas.cds - waitLeft;
- throw new Error(9);
- }
+ /*
+ * TODO benchmark if requesting by pixel or chunk is better
+ */
- setPixelByOffset(canvasId, color, i, j, offset);
+ while (pixels.length) {
+ const [offset, color] = pixels.pop();
- user.setWait(waitLeft, canvasId);
- if (canvas.ranked) {
- user.incrementPixelcount();
+
+ const [x, y, z] = getPixelFromChunkOffset(i, j, offset, canvasSize, is3d);
+ pixelLogger.info(
+ `${user.ip} ${user.id} ${canvasId} ${x} ${y} ${z} ${color} ${retCode}`,
+ );
+
+ // eslint-disable-next-line no-await-in-loop
+ const setColor = await RedisCanvas.getPixelByOffset(
+ canvasId,
+ i, j,
+ offset,
+ );
+
+ /*
+ * pixel validation
+ */
+ const maxSize = (is3d) ? tileSize * tileSize * THREE_CANVAS_HEIGHT
+ : tileSize * tileSize;
+ if (offset >= maxSize) {
+ // z out of bounds or weird stuff
+ throw new Error(4);
+ }
+ if (color >= canvas.colors.length
+ || (color < canvas.cli && !(canvas.v && color === 0))
+ ) {
+ // color out of bounds
+ throw new Error(5);
+ }
+
+ if (setColor & 0x80
+ /* 3D Canvas Minecraft Avatars */
+ // && x >= 96 && x <= 128 && z >= 35 && z <= 100
+ // 96 - 128 on x
+ // 32 - 128 on z
+ || (canvas.v && i === 19 && j >= 17 && j < 20 && !isAdmin)
+ ) {
+ // protected pixel
+ throw new Error(8);
+ }
+
+ coolDown = (setColor & 0x3F) < canvas.cli ? canvas.bcd : canvas.pcd;
+ if (isAdmin) {
+ coolDown = 0.0;
+ } else {
+ coolDown *= coolDownFactor;
+ }
+
+ wait += coolDown;
+ if (wait > canvas.cds) {
+ // cooldown stack used
+ coolDown = canvas.cds - wait;
+ wait -= coolDown;
+ throw new Error(9);
+ }
+
+ setPixelByOffset(canvasId, color, i, j, offset);
+ pxlCnt += 1;
}
- wait = waitLeft;
} catch (e) {
retCode = parseInt(e.message, 10);
if (Number.isNaN(retCode)) {
@@ -155,9 +167,12 @@ export async function drawByOffsets(
}
}
- const [x, y, z] = getPixelFromChunkOffset(i, j, offset, canvasSize, is3d);
- // eslint-disable-next-line max-len
- pixelLogger.info(`${user.ip} ${user.id} ${canvasId} ${x} ${y} ${z} ${color} ${retCode}`);
+ if (pxlCnt) {
+ user.setWait(wait, canvasId);
+ if (canvas.ranked) {
+ user.incrementPixelcount(pxlCnt);
+ }
+ }
return {
wait,
@@ -387,7 +402,8 @@ export function drawSafeByCoords(
export function drawSafeByOffsets(
user: User,
canvasId: number,
- color: ColorIndex,
+ i: number,
+ j: number,
pixels: Array,
): Promise