From 9b8fb83b3625390eadb3f291fc65a93a64dd77b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D1=82=D0=BE=D0=BD=20=D0=A2=D1=80=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Sat, 1 Aug 2026 23:36:30 +0300 Subject: [PATCH] multiplayer optimisations --- src/main.js | 53 +++++++++++++++++++++++++++++++++++++++++++++++++- src/terrain.js | 2 +- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/main.js b/src/main.js index c920574..702efe0 100644 --- a/src/main.js +++ b/src/main.js @@ -38,6 +38,10 @@ let netSnapCurAt = 0; const reservedPositions = new Set(); let winner; +const seenClientExplosions = new Map(); +const carveQueue = []; +let clientCollapseState = null; + let score = 0; let round = 0; let totalRounds = 1; @@ -801,6 +805,52 @@ function applyWorld(snap) { } } +function simulateClientTerrain() { + const cur = netSnapCur; + if (!cur) return; + + if (cur.explosions) { + const curKeys = new Set(); + for (const e of cur.explosions) { + const k = `${e.type}:${e.x}:${e.y}`; + curKeys.add(k); + if (e.type !== 'tracer' && e.type !== 'napalm') { + seenClientExplosions.set(k, {...e}); + } + } + for (const [k, e] of seenClientExplosions) { + if (!curKeys.has(k)) { + carveQueue.push(e); + seenClientExplosions.delete(k); + } + } + } + + for (let i = carveQueue.length - 1; i >= 0; i--) { + const job = carveQueue[i]; + const rate = job.type === 'dirtCone' ? 60 : 30; + job.cr += dt * rate; + const done = job.type === 'dirtCone' + ? job.cr >= Math.min(job.r, job.y) + : job.cr >= job.r; + if (done) { + EXPLOSION_TYPES[job.type].clip(job, terrain); + cacheImageData(terrain); + carveQueue.splice(i, 1); + } + } + + if (state === 'land-collapse' && carveQueue.length === 0) { + if (!clientCollapseState) clientCollapseState = startCollapseTerrain(terrain); + for (let i = 0; i < 2; i++) { + if (collapseTerrainStep(terrain, clientCollapseState)) { + clientCollapseState = null; + break; + } + } + } +} + function interpolateWorld() { const cur = netSnapCur; if (!cur) return; @@ -1354,6 +1404,7 @@ function update() { return; } interpolateWorld(); + simulateClientTerrain(); if (state !== 'aim') return; } @@ -1386,6 +1437,7 @@ function update() { else if (state === 'start-turn') { if (networkMode && netIsHost()) lastRemoteCommandAt = performance.now(); + if (networkMode && netIsHost()) broadcastTerrain(true); state = 'aim'; } @@ -1744,7 +1796,6 @@ function update() { lastBroadcastAt = now; broadcastWorld(); } - broadcastTerrain(false); } } diff --git a/src/terrain.js b/src/terrain.js index 57785a1..b885833 100644 --- a/src/terrain.js +++ b/src/terrain.js @@ -4,7 +4,7 @@ import {sample} from './utils.js?v=26'; let cachedImageData; -function cacheImageData(ctx) { +export function cacheImageData(ctx) { const {width, height} = ctx.canvas; cachedImageData = ctx.getImageData(0, 0, width, height); }