a more robust system for syncing chunks
as long as the server tells you there are more chunks, there are definitely more chunks. just wait for them a bit.
This commit is contained in:
parent
2f7bcbb14e
commit
2594afcc1b
|
@ -1,4 +1,4 @@
|
|||
use std::sync::Arc;
|
||||
use std::{collections::HashSet, sync::Arc};
|
||||
|
||||
use axum::{
|
||||
extract::{
|
||||
|
@ -194,8 +194,11 @@ struct SessionLoop {
|
|||
wall: Arc<Wall>,
|
||||
chunk_encoder: Arc<ChunkEncoder>,
|
||||
handle: SessionHandle,
|
||||
|
||||
render_commands_tx: mpsc::Sender<RenderCommand>,
|
||||
|
||||
viewport_chunks: ChunkIterator,
|
||||
sent_chunks: HashSet<ChunkPosition>,
|
||||
}
|
||||
|
||||
enum RenderCommand {
|
||||
|
@ -238,6 +241,7 @@ impl SessionLoop {
|
|||
handle,
|
||||
render_commands_tx,
|
||||
viewport_chunks: ChunkIterator::new(ChunkPosition::new(0, 0), ChunkPosition::new(0, 0)),
|
||||
sent_chunks: HashSet::new(),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -316,10 +320,16 @@ impl SessionLoop {
|
|||
let mut chunk_infos = vec![];
|
||||
let mut packet = vec![];
|
||||
|
||||
// Number of chunks iterated is limited to 300 per packet, so as not to let the client
|
||||
// Number of chunks iterated is limited per packet, so as not to let the client
|
||||
// stall the server by sending in a huge viewport.
|
||||
for _ in 0..300 {
|
||||
let start = Instant::now();
|
||||
let mut iterated = 0;
|
||||
for i in 0..12000 {
|
||||
if let Some(position) = self.viewport_chunks.next() {
|
||||
if self.sent_chunks.contains(&position) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(encoded) = self.chunk_encoder.encoded(position).await {
|
||||
let offset = packet.len();
|
||||
packet.extend_from_slice(&encoded);
|
||||
|
@ -335,25 +345,22 @@ impl SessionLoop {
|
|||
// execute. We cap it to 256KiB in hopes that noone has Internet slow enough for
|
||||
// this to cause a disconnect.
|
||||
if packet.len() >= 256 * 1024 {
|
||||
iterated = i;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Length 0 indicates the server acknowledged the chunk, but it has no
|
||||
// image data.
|
||||
// This is used by clients to know that the chunk doesn't need downloading.
|
||||
chunk_infos.push(ChunkInfo {
|
||||
position,
|
||||
offset: 0,
|
||||
length: 0,
|
||||
});
|
||||
}
|
||||
|
||||
self.sent_chunks.insert(position);
|
||||
} else {
|
||||
iterated = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
info!(elapsed = ?start.elapsed(), iterated, "send_chunks");
|
||||
|
||||
ws.send(to_message(&Notify::Chunks {
|
||||
chunks: chunk_infos,
|
||||
has_more: self.viewport_chunks.clone().next().is_some(),
|
||||
}))
|
||||
.await?;
|
||||
ws.send(Message::Binary(packet)).await?;
|
||||
|
|
|
@ -94,6 +94,12 @@ pub struct ChunkInfo {
|
|||
rename_all_fields = "camelCase"
|
||||
)]
|
||||
pub enum Notify {
|
||||
Wall { wall_event: wall::Event },
|
||||
Chunks { chunks: Vec<ChunkInfo> },
|
||||
Wall {
|
||||
wall_event: wall::Event,
|
||||
},
|
||||
|
||||
Chunks {
|
||||
chunks: Vec<ChunkInfo>,
|
||||
has_more: bool,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -17,13 +17,14 @@ export class BrushEditor extends HTMLElement {
|
|||
|
||||
this.textArea = this.appendChild(document.createElement("pre"));
|
||||
this.textArea.classList.add("text-area");
|
||||
this.textArea.textContent = defaultBrush;
|
||||
this.textArea.textContent = localStorage.getItem("rkgk.brushEditor.code") ?? defaultBrush;
|
||||
this.textArea.contentEditable = true;
|
||||
this.textArea.spellcheck = false;
|
||||
this.textArea.addEventListener("input", () => {
|
||||
localStorage.setItem("rkgk.brushEditor.code", this.code);
|
||||
this.dispatchEvent(
|
||||
Object.assign(new Event(".codeChanged"), {
|
||||
newCode: this.textArea.value,
|
||||
newCode: this.code,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
|
|
@ -87,8 +87,13 @@ class CanvasRenderer extends HTMLElement {
|
|||
|
||||
let chunk = this.wall.getChunk(chunkX, chunkY);
|
||||
if (chunk != null) {
|
||||
this.ctx.globalCompositeOperation = "source-over";
|
||||
this.ctx.drawImage(chunk.canvas, x, y);
|
||||
}
|
||||
|
||||
this.ctx.globalCompositeOperation = "difference";
|
||||
this.ctx.fillStyle = "white";
|
||||
this.ctx.fillText(`${chunkX}, ${chunkY}`, x, y + 12);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -83,54 +83,42 @@ reticleRenderer.connectViewport(canvasRenderer.viewport);
|
|||
}
|
||||
});
|
||||
|
||||
let pendingChunks = 0;
|
||||
let chunkDownloadStates = new Map();
|
||||
|
||||
function sendViewportUpdate() {
|
||||
let sendViewportUpdate = debounce(updateInterval, () => {
|
||||
let visibleRect = canvasRenderer.getVisibleChunkRect();
|
||||
session.sendViewport(visibleRect);
|
||||
|
||||
for (let chunkY = visibleRect.top; chunkY < visibleRect.bottom; ++chunkY) {
|
||||
for (let chunkX = visibleRect.left; chunkX < visibleRect.right; ++chunkX) {
|
||||
let key = Wall.chunkKey(chunkX, chunkY);
|
||||
let currentState = chunkDownloadStates.get(key);
|
||||
if (currentState == null) {
|
||||
chunkDownloadStates.set(key, "requested");
|
||||
pendingChunks += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
console.info("pending chunks after viewport update", pendingChunks);
|
||||
}
|
||||
|
||||
console.log("visibleRect", visibleRect);
|
||||
});
|
||||
canvasRenderer.addEventListener(".viewportUpdate", sendViewportUpdate);
|
||||
sendViewportUpdate();
|
||||
|
||||
session.addEventListener("chunks", (event) => {
|
||||
let { chunkInfo, chunkData } = event;
|
||||
session.addEventListener("chunks", async (event) => {
|
||||
let { chunkInfo, chunkData, hasMore } = event;
|
||||
|
||||
console.info("received data for chunks", {
|
||||
chunkInfoLength: chunkInfo.length,
|
||||
chunkInfo,
|
||||
chunkDataSize: chunkData.size,
|
||||
});
|
||||
|
||||
let updatePromises = [];
|
||||
for (let info of event.chunkInfo) {
|
||||
let key = Wall.chunkKey(info.position.x, info.position.y);
|
||||
if (chunkDownloadStates.get(key) == "requested") {
|
||||
pendingChunks -= 1;
|
||||
}
|
||||
chunkDownloadStates.set(key, "downloaded");
|
||||
|
||||
if (info.length > 0) {
|
||||
let blob = chunkData.slice(info.offset, info.offset + info.length, "image/webp");
|
||||
createImageBitmap(blob).then((bitmap) => {
|
||||
let chunk = wall.getOrCreateChunk(info.position.x, info.position.y);
|
||||
chunk.ctx.globalCompositeOperation = "copy";
|
||||
chunk.ctx.drawImage(bitmap, 0, 0);
|
||||
chunk.syncToPixmap();
|
||||
});
|
||||
updatePromises.push(
|
||||
createImageBitmap(blob).then((bitmap) => {
|
||||
let chunk = wall.getOrCreateChunk(info.position.x, info.position.y);
|
||||
chunk.ctx.globalCompositeOperation = "copy";
|
||||
chunk.ctx.drawImage(bitmap, 0, 0);
|
||||
chunk.syncToPixmap();
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(updatePromises);
|
||||
if (hasMore) {
|
||||
console.info("more chunks are pending; requesting more");
|
||||
session.sendMoreChunks();
|
||||
}
|
||||
});
|
||||
|
||||
let reportCursor = debounce(updateInterval, (x, y) => session.sendCursor(x, y));
|
||||
|
|
|
@ -208,6 +208,7 @@ class Session extends EventTarget {
|
|||
Object.assign(new Event("chunks"), {
|
||||
chunkInfo: notify.chunks,
|
||||
chunkData,
|
||||
hasMore: notify.hasMore,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue