removing server-side brush rendering
brush rendering is now completely client-side. the server only receives edits the client would like to do, in the form of PNG images of chunks, that are then composited onto the wall known issue: it is possible to brush up against the current 256 chunk edit limit pretty easily. I'm not sure it can be solved very easily though. the perfect solution would involve splitting up the interaction into multiple edits, and I tried to do that, but there's a noticable stutter for some reason that I haven't managed to track down yet. so it'll be kinda crap for the time being.
This commit is contained in:
parent
15a1bf8036
commit
bff899c9c0
24 changed files with 613 additions and 1170 deletions
122
static/wall.js
122
static/wall.js
|
@ -9,6 +9,10 @@ export class Chunk {
|
|||
this.renderDirty = false;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.pixmap.destroy();
|
||||
}
|
||||
|
||||
syncFromPixmap() {
|
||||
this.ctx.putImageData(this.pixmap.getImageData(), 0, 0);
|
||||
}
|
||||
|
@ -23,31 +27,117 @@ export class Chunk {
|
|||
}
|
||||
}
|
||||
|
||||
let layerIdCounter = 0;
|
||||
|
||||
export class Layer {
|
||||
chunks = new Map();
|
||||
id = layerIdCounter++;
|
||||
|
||||
constructor({ name, chunkSize, chunkLimit }) {
|
||||
this.name = name;
|
||||
this.chunkSize = chunkSize;
|
||||
this.chunkLimit = chunkLimit;
|
||||
|
||||
console.info("created layer", this.id, this.name);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
for (let { chunk } of this.chunks.values()) {
|
||||
chunk.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
getChunk(x, y) {
|
||||
return this.chunks.get(chunkKey(x, y))?.chunk;
|
||||
}
|
||||
|
||||
getOrCreateChunk(x, y) {
|
||||
let key = chunkKey(x, y);
|
||||
if (this.chunks.has(key)) {
|
||||
return this.chunks.get(key)?.chunk;
|
||||
} else {
|
||||
if (this.chunkLimit != null && this.chunks.size >= this.chunkLimit) return null;
|
||||
|
||||
let chunk = new Chunk(this.chunkSize);
|
||||
this.chunks.set(key, { x, y, chunk });
|
||||
return chunk;
|
||||
}
|
||||
}
|
||||
|
||||
compositeAlpha(src) {
|
||||
for (let { x, y, chunk: srcChunk } of src.chunks.values()) {
|
||||
srcChunk.syncFromPixmap();
|
||||
let dstChunk = this.getOrCreateChunk(x, y);
|
||||
if (dstChunk == null) continue;
|
||||
|
||||
dstChunk.ctx.globalCompositeOperation = "source-over";
|
||||
dstChunk.ctx.drawImage(srcChunk.canvas, 0, 0);
|
||||
dstChunk.syncToPixmap();
|
||||
dstChunk.markModified();
|
||||
}
|
||||
}
|
||||
|
||||
async toEdits() {
|
||||
let edits = [];
|
||||
|
||||
let start = performance.now();
|
||||
|
||||
for (let { x, y, chunk } of this.chunks.values()) {
|
||||
edits.push({
|
||||
chunk: { x, y },
|
||||
data: chunk.canvas.convertToBlob({ type: "image/png" }),
|
||||
});
|
||||
}
|
||||
|
||||
for (let edit of edits) {
|
||||
edit.data = await edit.data;
|
||||
}
|
||||
|
||||
let end = performance.now();
|
||||
console.debug("toEdits done", end - start);
|
||||
|
||||
return edits;
|
||||
}
|
||||
}
|
||||
|
||||
export function chunkKey(x, y) {
|
||||
return `${x},${y}`;
|
||||
}
|
||||
|
||||
export class Wall {
|
||||
#chunks = new Map();
|
||||
layers = []; // do not modify directly; only read
|
||||
#layersById = new Map();
|
||||
|
||||
constructor(wallInfo) {
|
||||
this.chunkSize = wallInfo.chunkSize;
|
||||
this.paintArea = wallInfo.paintArea;
|
||||
this.maxEditSize = wallInfo.maxEditSize;
|
||||
this.onlineUsers = new OnlineUsers(wallInfo);
|
||||
|
||||
this.mainLayer = new Layer({ name: "main", chunkSize: this.chunkSize });
|
||||
this.addLayer(this.mainLayer);
|
||||
}
|
||||
|
||||
static chunkKey(x, y) {
|
||||
return `(${x},${y})`;
|
||||
}
|
||||
|
||||
getChunk(x, y) {
|
||||
return this.#chunks.get(Wall.chunkKey(x, y));
|
||||
}
|
||||
|
||||
getOrCreateChunk(x, y) {
|
||||
let key = Wall.chunkKey(x, y);
|
||||
if (this.#chunks.has(key)) {
|
||||
return this.#chunks.get(key);
|
||||
addLayer(layer) {
|
||||
if (!this.#layersById.get(layer.id)) {
|
||||
this.layers.push(layer);
|
||||
this.#layersById.set(layer.id, layer);
|
||||
} else {
|
||||
let chunk = new Chunk(this.chunkSize);
|
||||
this.#chunks.set(key, chunk);
|
||||
return chunk;
|
||||
console.warn("attempt to add layer more than once", layer);
|
||||
}
|
||||
return layer;
|
||||
}
|
||||
|
||||
removeLayer(layer) {
|
||||
if (this.#layersById.delete(layer.id)) {
|
||||
let index = this.layers.findIndex((x) => x == layer);
|
||||
this.layers.splice(index, 1);
|
||||
} else {
|
||||
console.warn("attempt to remove layer more than once", layer);
|
||||
}
|
||||
}
|
||||
|
||||
getLayerById(id) {
|
||||
return this.#layersById.get(id);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue