From e34609c0467e85f50a3fd1ae8c89ba7e5d2ae4a2 Mon Sep 17 00:00:00 2001 From: HF Date: Fri, 4 Dec 2020 21:11:11 +0100 Subject: [PATCH] mass-request pixels pt.1 --- src/client.js | 1 - src/core/draw.js | 68 +++++++++++++++---------- src/socket/ProtocolClient.js | 13 +++-- src/socket/packets/PixelUpdateClient.js | 52 +++++++++++++++---- src/socket/packets/PixelUpdateServer.js | 46 +++++++++++++---- 5 files changed, 125 insertions(+), 55 deletions(-) diff --git a/src/client.js b/src/client.js index f0dde8d..88d59a0 100644 --- a/src/client.js +++ b/src/client.js @@ -135,7 +135,6 @@ document.addEventListener('DOMContentLoaded', () => { // on captcha received -// TODO: this really isn't beautiful window.onCaptcha = async function onCaptcha(token: string) { const body = JSON.stringify({ token, diff --git a/src/core/draw.js b/src/core/draw.js index e1734e8..17b0412 100644 --- a/src/core/draw.js +++ b/src/core/draw.js @@ -28,15 +28,18 @@ import { THREE_CANVAS_HEIGHT, THREE_TILE_SIZE, TILE_SIZE } from './constants'; * @param canvasId * @param i Chunk coordinates * @param j - * @param offset Offset of pixel withing chunk + * @param pixels Array of indiviual pixels within the chunk, with: + * [[offset, color], [offset2, color2],...] + * Offset is the offset of the pixel within the chunk + * @return Promise */ -export async function drawByOffset( +export async function drawByOffsets( user: User, canvasId: number, color: ColorIndex, i: number, j: number, - offset: number, + pixels: Array, ): Promise { let wait = 0; let coolDown = 0; @@ -51,10 +54,13 @@ export async function drawByOffset( retCode: 1, }; } - const { size: canvasSize, v: is3d } = canvas; + const { size: canvasSize, v: is3d } = canvas; try { const tileSize = (is3d) ? THREE_TILE_SIZE : TILE_SIZE; + /* + * canvas/chunk validation + */ if (i >= canvasSize / tileSize) { // x out of bounds throw new Error(2); @@ -63,19 +69,6 @@ export async function drawByOffset( // y out of bounds throw new Error(3); } - 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 (canvas.req !== -1) { if (user.id === null) { // not logged in @@ -89,8 +82,29 @@ export async function drawByOffset( } 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 @@ -331,7 +345,6 @@ export async function drawByCoords( * @param x * @param y * @param z (optional for 3d canvas) - * @returns {Promise.} */ export function drawSafeByCoords( user: User, @@ -340,7 +353,7 @@ export function drawSafeByCoords( x: number, y: number, z: number = null, -): Promise { +): Promise { // can just check for one unique occurence, // we use ip, because id for logged out users is // always null @@ -364,20 +377,19 @@ export function drawSafeByCoords( * * @param user * @param canvasId - * @param color * @param i Chunk coordinates * @param j - * @param offset Offset of pixel withing chunk - * @returns {Promise.} + * @param pixels Array of indiviual pixels within the chunk, with: + * [[offset, color], [offset2, color2],...] + * Offset is the offset of the pixel within the chunk + * @return Promise */ -export function drawSafeByOffset( +export function drawSafeByOffsets( user: User, canvasId: number, color: ColorIndex, - i: number, - j: number, - offset: number, -): Promise { + pixels: Array, +): Promise { // can just check for one unique occurence, // we use ip, because id for logged out users is // always null @@ -387,7 +399,7 @@ export function drawSafeByOffset( using( redlock.disposer(`locks:${userId}`, 5000, logger.error), async () => { - const ret = await drawByOffset(user, canvasId, color, i, j, offset); + const ret = await drawByOffsets(user, canvasId, i, j, pixels); resolve(ret); }, ); // <-- unlock is automatically handled by bluebird diff --git a/src/socket/ProtocolClient.js b/src/socket/ProtocolClient.js index 5b2f62d..5009b7b 100644 --- a/src/socket/ProtocolClient.js +++ b/src/socket/ProtocolClient.js @@ -1,5 +1,3 @@ -/** - * * @flow */ @@ -125,11 +123,16 @@ class ProtocolClient extends EventEmitter { if (~pos) chunks.splice(pos, 1); } + /* + * Send pixel request + * @param i, j chunk coordinates + * @param pixel Array of [[offset, color],...] pixels within chunk + */ requestPlacePixel( - i, j, offset, - color, + i: number, j: number, + pixels: Array, ) { - const buffer = PixelUpdate.dehydrate(i, j, offset, color); + const buffer = PixelUpdate.dehydrate(i, j, pixels); this.sendWhenReady(buffer); } diff --git a/src/socket/packets/PixelUpdateClient.js b/src/socket/packets/PixelUpdateClient.js index 79d5206..731f798 100644 --- a/src/socket/packets/PixelUpdateClient.js +++ b/src/socket/packets/PixelUpdateClient.js @@ -1,4 +1,9 @@ -/* @flow */ +/* + * Packet for sending and receiving pixels per chunk + * Multiple pixels can be sent at once + * + * @flow + */ import type { ColorIndex } from '../../core/Palette'; @@ -6,7 +11,7 @@ import type { ColorIndex } from '../../core/Palette'; type PixelUpdatePacket = { x: number, y: number, - color: ColorIndex, + pixels: Array, }; const OP_CODE = 0xC1; @@ -14,25 +19,50 @@ const OP_CODE = 0xC1; export default { OP_CODE, hydrate(data: DataView): PixelUpdatePacket { + /* + * chunk coordinates + */ const i = data.getUint8(1); const j = data.getUint8(2); - const offset = (data.getUint8(3) << 16) | data.getUint16(4); - const color = data.getUint8(6); + /* + * offset and color of every pixel + * 3 bytes offset + * 1 byte color + */ + const pixels = []; + let off = data.byteLength; + while (off >= 3) { + const color = data.getUint8(off -= 1); + const offsetL = data.getUint16(off -= 2); + const offsetH = data.getUint8(off -= 1) << 16; + const pixels.push([offsetH | offsetL, color]); + } return { - i, j, offset, color, + i, j, pixels, }; }, - dehydrate(i, j, offset, color): Buffer { - const buffer = new ArrayBuffer(1 + 1 + 1 + 1 + 2 + 1); + dehydrate(i, j, pixels): Buffer { + const buffer = new ArrayBuffer(1 + 1 + 1 + pixels.length * 4); const view = new DataView(buffer); view.setUint8(0, OP_CODE); - + /* + * chunk coordinates + */ view.setUint8(1, i); view.setUint8(2, j); - view.setUint8(3, offset >>> 16); - view.setUint16(4, offset & 0x00FFFF); - view.setUint8(6, color); + /* + * offset and color of every pixel + * 3 bytes offset + * 1 byte color + */ + let cnt = 2; + for (let i = 0; i < pixels.length; i += 1) { + const [offset, color] = pixels[i]; + view.setUint8(cnt += 1, offset >>> 16); + view.setUint16(cnt += 1, offset & 0x00FFFF); + view.setUint8(cnt += 2, color); + } return buffer; }, diff --git a/src/socket/packets/PixelUpdateServer.js b/src/socket/packets/PixelUpdateServer.js index 6d0ce47..70a7740 100644 --- a/src/socket/packets/PixelUpdateServer.js +++ b/src/socket/packets/PixelUpdateServer.js @@ -6,7 +6,7 @@ import type { ColorIndex } from '../../core/Palette'; type PixelUpdatePacket = { x: number, y: number, - color: ColorIndex, + pixels: Array, }; const OP_CODE = 0xC1; @@ -14,23 +14,49 @@ const OP_CODE = 0xC1; export default { OP_CODE, hydrate(data: Buffer): PixelUpdatePacket { + /* + * chunk coordinates + */ const i = data.readUInt8(1); const j = data.readUInt8(2); - const offset = (data.readUInt8(3) << 16) | data.readUInt16BE(4); - const color = data.readUInt8(6); + /* + * offset and color of every pixel + * 3 bytes offset + * 1 byte color + */ + const pixels = []; + let off = data.length; + while (off >= 3) { + const color = data.readUInt8(off -= 1); + const offsetL = data.readUInt16BE(off -= 2); + const offsetH = data.readUInt8(off -= 1) << 16; + const pixels.push([offsetH | offsetL, color]); + } return { - i, j, offset, color, + i, j, pixels, }; }, - dehydrate(i, j, offset, color): Buffer { - const buffer = Buffer.allocUnsafe(1 + 1 + 1 + 1 + 2 + 1); - buffer.writeUInt8(OP_CODE, 0); + dehydrate(i, j, pixels): Buffer { + const buffer = Buffer.allocUnsafe(1 + 1 + 1 + pixels.length * 4); + buffer.writeUInt8(OP_CODE, 0); + /* + * chunk coordinates + */ buffer.writeUInt8(i, 1); buffer.writeUInt8(j, 2); - buffer.writeUInt8(offset >>> 16, 3); - buffer.writeUInt16BE(offset & 0x00FFFF, 4); - buffer.writeUInt8(color, 6); + /* + * offset and color of every pixel + * 3 bytes offset + * 1 byte color + */ + let cnt = 2; + for (let i = 0; i < pixels.length; i += 1) { + const [offset, color] = pixels[i]; + buffer.writeUInt8(offset >>> 16, cnt += 1); + buffer.writeUInt16BE(offset & 0x00FFFF, cnt += 1); + buffer.writeUInt8(color, cnt += 2); + } return buffer; },