Improve network stack (less traffic)
This commit is contained in:
parent
43874d0f54
commit
ddf68f316e
130
src/game.js
130
src/game.js
@ -55,7 +55,8 @@ const POWERUPS = ["bomb", "flame", "speed"];
|
|||||||
const MAX_PLAYERS = 8;
|
const MAX_PLAYERS = 8;
|
||||||
const NETWORK_PATH = "/ws";
|
const NETWORK_PATH = "/ws";
|
||||||
const PLAYER_NAME_STORAGE_KEY = "gptBomberPlayerName";
|
const PLAYER_NAME_STORAGE_KEY = "gptBomberPlayerName";
|
||||||
const SNAPSHOT_RATE = 24;
|
const SNAPSHOT_RATE = 18;
|
||||||
|
const MAP_RESYNC_SECONDS = 1.2;
|
||||||
|
|
||||||
const canvas = document.getElementById("game");
|
const canvas = document.getElementById("game");
|
||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.getContext("2d");
|
||||||
@ -142,6 +143,8 @@ const network = {
|
|||||||
lastPingAt: 0,
|
lastPingAt: 0,
|
||||||
lobbyPhase: "lobby",
|
lobbyPhase: "lobby",
|
||||||
hasReceivedSnapshot: false,
|
hasReceivedSnapshot: false,
|
||||||
|
lastFullMapSnapshotAt: 0,
|
||||||
|
lastSentMapState: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
let audio = null;
|
let audio = null;
|
||||||
@ -351,6 +354,7 @@ function resetRoundSingle() {
|
|||||||
state.multiplayerRoundKills = {};
|
state.multiplayerRoundKills = {};
|
||||||
state.multiplayerRoundDeathTimes = {};
|
state.multiplayerRoundDeathTimes = {};
|
||||||
state.multiplayerRoundTime = 0;
|
state.multiplayerRoundTime = 0;
|
||||||
|
resetHostSnapshotMapState();
|
||||||
closeMenu();
|
closeMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -384,6 +388,7 @@ function resetRoundMultiplayer(roster) {
|
|||||||
state.multiplayerRoundKills = {};
|
state.multiplayerRoundKills = {};
|
||||||
state.multiplayerRoundDeathTimes = {};
|
state.multiplayerRoundDeathTimes = {};
|
||||||
state.multiplayerRoundTime = 0;
|
state.multiplayerRoundTime = 0;
|
||||||
|
resetHostSnapshotMapState();
|
||||||
closeMenu();
|
closeMenu();
|
||||||
inputState.bombQueued = false;
|
inputState.bombQueued = false;
|
||||||
network.remoteBombPrev.clear();
|
network.remoteBombPrev.clear();
|
||||||
@ -858,6 +863,7 @@ function leaveLobbyInternals() {
|
|||||||
network.inputStateAtClient = { dir: null, bomb: false, bombCell: null };
|
network.inputStateAtClient = { dir: null, bomb: false, bombCell: null };
|
||||||
network.lastInputSentAt = 0;
|
network.lastInputSentAt = 0;
|
||||||
network.hasReceivedSnapshot = false;
|
network.hasReceivedSnapshot = false;
|
||||||
|
resetHostSnapshotMapState();
|
||||||
}
|
}
|
||||||
|
|
||||||
function leaveMultiplayerToMainMenu() {
|
function leaveMultiplayerToMainMenu() {
|
||||||
@ -878,12 +884,68 @@ function hostStartMatchFromLobby() {
|
|||||||
sendSocketMessage("lobby_start");
|
sendSocketMessage("lobby_start");
|
||||||
}
|
}
|
||||||
|
|
||||||
function serializeGameState() {
|
function resetHostSnapshotMapState() {
|
||||||
|
network.lastSentMapState = null;
|
||||||
|
network.lastFullMapSnapshotAt = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function serializeMapTile(tile) {
|
||||||
|
return { type: tile.type, powerup: tile.powerup || null };
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapTileSignature(tile) {
|
||||||
|
return tile.type + "|" + (tile.powerup || "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildMapSignatureState() {
|
||||||
|
return state.map.map((row) => row.map((tile) => mapTileSignature(tile)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function serializeMapPatch() {
|
||||||
|
if (!Array.isArray(network.lastSentMapState) || network.lastSentMapState.length !== state.map.length) {
|
||||||
|
network.lastSentMapState = buildMapSignatureState();
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const patch = [];
|
||||||
|
for (let y = 0; y < state.map.length; y += 1) {
|
||||||
|
const row = state.map[y];
|
||||||
|
const previousRow = network.lastSentMapState[y];
|
||||||
|
for (let x = 0; x < row.length; x += 1) {
|
||||||
|
const tile = row[x];
|
||||||
|
const signature = mapTileSignature(tile);
|
||||||
|
if (!previousRow || previousRow[x] !== signature) {
|
||||||
|
patch.push({ x, y, type: tile.type, powerup: tile.powerup || null });
|
||||||
|
if (previousRow) {
|
||||||
|
previousRow[x] = signature;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return patch;
|
||||||
|
}
|
||||||
|
|
||||||
|
function serializeGameState(includeFullMap = false) {
|
||||||
|
let map;
|
||||||
|
let mapPatch;
|
||||||
|
|
||||||
|
if (includeFullMap || !Array.isArray(network.lastSentMapState)) {
|
||||||
|
map = state.map.map((row) => row.map((tile) => serializeMapTile(tile)));
|
||||||
|
network.lastSentMapState = buildMapSignatureState();
|
||||||
|
} else {
|
||||||
|
const patch = serializeMapPatch();
|
||||||
|
if (patch.length > 0) {
|
||||||
|
mapPatch = patch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: state.status,
|
status: state.status,
|
||||||
message: state.message,
|
message: state.message,
|
||||||
time: state.time,
|
time: state.time,
|
||||||
map: state.map.map((row) => row.map((tile) => ({ type: tile.type, powerup: tile.powerup || null }))),
|
map,
|
||||||
|
mapPatch,
|
||||||
players: state.players.map((player) => ({
|
players: state.players.map((player) => ({
|
||||||
id: player.id,
|
id: player.id,
|
||||||
name: player.name,
|
name: player.name,
|
||||||
@ -957,20 +1019,33 @@ function playSnapshotSfx(snapshot) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let powerupCollected = false;
|
let powerupCollected = false;
|
||||||
for (let y = 0; y < state.map.length; y += 1) {
|
if (Array.isArray(snapshot.map)) {
|
||||||
for (let x = 0; x < state.map[y].length; x += 1) {
|
for (let y = 0; y < state.map.length; y += 1) {
|
||||||
const prevTile = state.map[y][x];
|
for (let x = 0; x < state.map[y].length; x += 1) {
|
||||||
const nextTile = snapshot.map[y]?.[x];
|
const prevTile = state.map[y][x];
|
||||||
if (!prevTile || !nextTile) {
|
const nextTile = snapshot.map[y]?.[x];
|
||||||
continue;
|
if (!prevTile || !nextTile) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (prevTile.type === "floor" && prevTile.powerup && nextTile.type === "floor" && !nextTile.powerup) {
|
||||||
|
powerupCollected = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (prevTile.type === "floor" && prevTile.powerup && nextTile.type === "floor" && !nextTile.powerup) {
|
if (powerupCollected) {
|
||||||
powerupCollected = true;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (powerupCollected) {
|
} else if (Array.isArray(snapshot.mapPatch)) {
|
||||||
break;
|
for (const change of snapshot.mapPatch) {
|
||||||
|
const prevTile = state.map[change.y]?.[change.x];
|
||||||
|
if (!prevTile) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (prevTile.type === "floor" && prevTile.powerup && change.type === "floor" && !change.powerup) {
|
||||||
|
powerupCollected = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -989,7 +1064,21 @@ function applySnapshot(snapshot) {
|
|||||||
state.status = snapshot.status;
|
state.status = snapshot.status;
|
||||||
state.message = snapshot.message;
|
state.message = snapshot.message;
|
||||||
state.time = snapshot.time;
|
state.time = snapshot.time;
|
||||||
state.map = snapshot.map.map((row) => row.map((tile) => ({ type: tile.type, powerup: tile.powerup })));
|
|
||||||
|
if (Array.isArray(snapshot.map)) {
|
||||||
|
state.map = snapshot.map.map((row) => row.map((tile) => ({ type: tile.type, powerup: tile.powerup })));
|
||||||
|
} else if (Array.isArray(snapshot.mapPatch) && state.map.length > 0) {
|
||||||
|
for (const change of snapshot.mapPatch) {
|
||||||
|
if (!inBounds(change.x, change.y)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const row = state.map[change.y];
|
||||||
|
if (!row || !row[change.x]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
row[change.x] = { type: change.type, powerup: change.powerup || null };
|
||||||
|
}
|
||||||
|
}
|
||||||
state.players = snapshot.players.map((player) => {
|
state.players = snapshot.players.map((player) => {
|
||||||
const key = player.ownerId || `id:${player.id}`;
|
const key = player.ownerId || `id:${player.id}`;
|
||||||
const prev = previousPlayers.get(key);
|
const prev = previousPlayers.get(key);
|
||||||
@ -1031,12 +1120,23 @@ function broadcastSnapshot(force = false) {
|
|||||||
if (!network.isHost || state.mode !== "multiplayer" || state.screen !== "game") {
|
if (!network.isHost || state.mode !== "multiplayer" || state.screen !== "game") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const t = now();
|
const t = now();
|
||||||
if (!force && t - network.lastSnapshotSentAt < 1 / SNAPSHOT_RATE) {
|
if (!force && t - network.lastSnapshotSentAt < 1 / SNAPSHOT_RATE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const includeFullMap =
|
||||||
|
force ||
|
||||||
|
!Array.isArray(network.lastSentMapState) ||
|
||||||
|
t - network.lastFullMapSnapshotAt >= MAP_RESYNC_SECONDS;
|
||||||
|
|
||||||
|
if (includeFullMap) {
|
||||||
|
network.lastFullMapSnapshotAt = t;
|
||||||
|
}
|
||||||
|
|
||||||
network.lastSnapshotSentAt = t;
|
network.lastSnapshotSentAt = t;
|
||||||
sendSocketMessage("game_snapshot", { snapshot: serializeGameState(), ts: t });
|
sendSocketMessage("game_snapshot", { snapshot: serializeGameState(includeFullMap), ts: t });
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateLobbyNetwork(dt) {
|
function updateLobbyNetwork(dt) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user