Latency optimization
This commit is contained in:
parent
d10fcdf15e
commit
4846e045bb
118
src/websocket.rs
118
src/websocket.rs
@ -1,4 +1,4 @@
|
|||||||
use std::{sync::Arc, path::PathBuf};
|
use std::{sync::Arc, path::PathBuf, collections::HashMap};
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{ws::{WebSocketUpgrade, WebSocket, Message}, State},
|
extract::{ws::{WebSocketUpgrade, WebSocket, Message}, State},
|
||||||
response::Response,
|
response::Response,
|
||||||
@ -7,14 +7,21 @@ use axum::{
|
|||||||
};
|
};
|
||||||
use flate2::{write::GzEncoder, Compression};
|
use flate2::{write::GzEncoder, Compression};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::{RwLock, Mutex};
|
||||||
use tower_http::services::ServeDir;
|
use tower_http::services::ServeDir;
|
||||||
|
|
||||||
use crate::comms::{RadarData, ArcRwlockRadarData};
|
use crate::comms::{RadarData, ArcRwlockRadarData, EntityData};
|
||||||
|
|
||||||
|
struct ClientState {
|
||||||
|
last_entity_count: usize,
|
||||||
|
ping_ms: u32,
|
||||||
|
high_latency: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct AppState {
|
struct AppState {
|
||||||
data_lock: Arc<RwLock<RadarData>>
|
data_lock: Arc<RwLock<RadarData>>,
|
||||||
|
clients: Arc<Mutex<HashMap<String, ClientState>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn ws_handler(ws: WebSocketUpgrade, State(state): State<AppState>) -> Response {
|
async fn ws_handler(ws: WebSocketUpgrade, State(state): State<AppState>) -> Response {
|
||||||
@ -23,48 +30,81 @@ async fn ws_handler(ws: WebSocketUpgrade, State(state): State<AppState>) -> Resp
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_socket(mut socket: WebSocket, state: AppState) {
|
async fn handle_socket(mut socket: WebSocket, state: AppState) {
|
||||||
|
let client_id = uuid::Uuid::new_v4().to_string();
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut clients = state.clients.lock().await;
|
||||||
|
clients.insert(client_id.clone(), ClientState {
|
||||||
|
last_entity_count: 0,
|
||||||
|
ping_ms: 0,
|
||||||
|
high_latency: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let mut compression_buffer: Vec<u8> = Vec::with_capacity(65536);
|
let mut compression_buffer: Vec<u8> = Vec::with_capacity(65536);
|
||||||
|
let mut frame_counter = 0;
|
||||||
|
let mut skip_frames = false;
|
||||||
|
|
||||||
while let Some(msg) = socket.recv().await {
|
while let Some(msg) = socket.recv().await {
|
||||||
if let Ok(msg) = msg {
|
if let Ok(msg) = msg {
|
||||||
if let Ok(text) = msg.to_text() {
|
if let Ok(text) = msg.to_text() {
|
||||||
if text == "requestInfo" {
|
if text == "requestInfo" {
|
||||||
|
frame_counter += 1;
|
||||||
|
if skip_frames && frame_counter % 2 != 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let radar_data = state.data_lock.read().await;
|
let radar_data = state.data_lock.read().await;
|
||||||
|
let mut clients = state.clients.lock().await;
|
||||||
|
let client_state = clients.get_mut(&client_id).unwrap();
|
||||||
|
|
||||||
if let Ok(json) = serde_json::to_string(&*radar_data) {
|
let entity_count = radar_data.get_entities().len();
|
||||||
compression_buffer.clear();
|
|
||||||
|
|
||||||
let compression_level = if json.len() > 10000 {
|
if entity_count > 5 && !skip_frames && client_state.ping_ms > 100 {
|
||||||
Compression::best()
|
skip_frames = true;
|
||||||
} else {
|
log::info!("Enabling frame skipping for high latency client");
|
||||||
Compression::fast()
|
}
|
||||||
};
|
|
||||||
|
|
||||||
let mut encoder = GzEncoder::new(Vec::new(), compression_level);
|
client_state.last_entity_count = entity_count;
|
||||||
if encoder.write_all(json.as_bytes()).is_ok() {
|
|
||||||
match encoder.finish() {
|
let Ok(json) = serde_json::to_string(&*radar_data) else {
|
||||||
Ok(compressed) => {
|
continue;
|
||||||
if compressed.len() < json.len() {
|
};
|
||||||
let mut message = vec![0x01];
|
|
||||||
message.extend_from_slice(&compressed);
|
compression_buffer.clear();
|
||||||
let _ = socket.send(Message::Binary(message)).await;
|
|
||||||
} else {
|
let compression_level = if json.len() > 20000 || client_state.high_latency {
|
||||||
let mut uncompressed = vec![0x00];
|
Compression::best()
|
||||||
uncompressed.extend_from_slice(json.as_bytes());
|
} else if json.len() > 5000 {
|
||||||
let _ = socket.send(Message::Binary(uncompressed)).await;
|
Compression::default()
|
||||||
}
|
} else {
|
||||||
},
|
Compression::fast()
|
||||||
Err(_) => {
|
};
|
||||||
|
|
||||||
|
let mut encoder = GzEncoder::new(Vec::new(), compression_level);
|
||||||
|
if encoder.write_all(json.as_bytes()).is_ok() {
|
||||||
|
match encoder.finish() {
|
||||||
|
Ok(compressed) => {
|
||||||
|
if compressed.len() < json.len() {
|
||||||
|
let mut message = vec![0x01];
|
||||||
|
message.extend_from_slice(&compressed);
|
||||||
|
let _ = socket.send(Message::Binary(message)).await;
|
||||||
|
} else {
|
||||||
let mut uncompressed = vec![0x00];
|
let mut uncompressed = vec![0x00];
|
||||||
uncompressed.extend_from_slice(json.as_bytes());
|
uncompressed.extend_from_slice(json.as_bytes());
|
||||||
let _ = socket.send(Message::Binary(uncompressed)).await;
|
let _ = socket.send(Message::Binary(uncompressed)).await;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
Err(_) => {
|
||||||
|
let mut uncompressed = vec![0x00];
|
||||||
|
uncompressed.extend_from_slice(json.as_bytes());
|
||||||
|
let _ = socket.send(Message::Binary(uncompressed)).await;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
let mut uncompressed = vec![0x00];
|
|
||||||
uncompressed.extend_from_slice(json.as_bytes());
|
|
||||||
let _ = socket.send(Message::Binary(uncompressed)).await;
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
let mut uncompressed = vec![0x00];
|
||||||
|
uncompressed.extend_from_slice(json.as_bytes());
|
||||||
|
let _ = socket.send(Message::Binary(uncompressed)).await;
|
||||||
}
|
}
|
||||||
} else if text == "toggleMoneyReveal" {
|
} else if text == "toggleMoneyReveal" {
|
||||||
let new_value = {
|
let new_value = {
|
||||||
@ -80,19 +120,35 @@ async fn handle_socket(mut socket: WebSocket, state: AppState) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let _ = socket.send(Message::Text(response.to_string())).await;
|
let _ = socket.send(Message::Text(response.to_string())).await;
|
||||||
|
} else if text.starts_with("ping:") {
|
||||||
|
if let Some(ping_str) = text.strip_prefix("ping:") {
|
||||||
|
if let Ok(ping_ms) = ping_str.parse::<u32>() {
|
||||||
|
let mut clients = state.clients.lock().await;
|
||||||
|
if let Some(client) = clients.get_mut(&client_id) {
|
||||||
|
client.ping_ms = ping_ms;
|
||||||
|
client.high_latency = ping_ms > 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let _ = socket.send(Message::Text("pong".to_string())).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let mut clients = state.clients.lock().await;
|
||||||
|
clients.remove(&client_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(path: PathBuf, port: u16, data_lock: Arc<RwLock<RadarData>>) -> anyhow::Result<()> {
|
pub async fn run(path: PathBuf, port: u16, data_lock: Arc<RwLock<RadarData>>) -> anyhow::Result<()> {
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.nest_service("/", ServeDir::new(path))
|
.nest_service("/", ServeDir::new(path))
|
||||||
.route("/ws", get(ws_handler))
|
.route("/ws", get(ws_handler))
|
||||||
.with_state(AppState { data_lock });
|
.with_state(AppState {
|
||||||
|
data_lock,
|
||||||
|
clients: Arc::new(Mutex::new(HashMap::new()))
|
||||||
|
});
|
||||||
|
|
||||||
let address = format!("0.0.0.0:{}", port);
|
let address = format!("0.0.0.0:{}", port);
|
||||||
log::info!("Starting WebSocket server on {}", address);
|
log::info!("Starting WebSocket server on {}", address);
|
||||||
|
@ -17,6 +17,18 @@ let drawNames = true;
|
|||||||
let drawGuns = true;
|
let drawGuns = true;
|
||||||
let drawMoney = true;
|
let drawMoney = true;
|
||||||
|
|
||||||
|
const NETWORK_SETTINGS = {
|
||||||
|
useInterpolation: true,
|
||||||
|
interpolationAmount: 0.6,
|
||||||
|
pingInterval: 3000
|
||||||
|
};
|
||||||
|
|
||||||
|
let connectionHealthy = true;
|
||||||
|
let lastResponseTime = 0;
|
||||||
|
let requestTimeoutTimer = null;
|
||||||
|
let reconnecting = false;
|
||||||
|
let retryCount = 0;
|
||||||
|
|
||||||
let isRequestPending = false;
|
let isRequestPending = false;
|
||||||
let frameCounter = 0;
|
let frameCounter = 0;
|
||||||
let fpsStartTime = 0;
|
let fpsStartTime = 0;
|
||||||
@ -25,6 +37,10 @@ let currentFps = 0;
|
|||||||
let temporarilyDisableRotation = false;
|
let temporarilyDisableRotation = false;
|
||||||
let rotationDisabledUntilRespawn = false;
|
let rotationDisabledUntilRespawn = false;
|
||||||
let lastKnownPositions = {};
|
let lastKnownPositions = {};
|
||||||
|
let entityInterpolationData = {};
|
||||||
|
let lastUpdateTime = 0;
|
||||||
|
let networkLatencyHistory = [];
|
||||||
|
let lastPingSent = 0;
|
||||||
|
|
||||||
let focusedPlayerYaw = 0;
|
let focusedPlayerYaw = 0;
|
||||||
let focusedPlayerName = "YOU";
|
let focusedPlayerName = "YOU";
|
||||||
@ -73,6 +89,29 @@ const websocketAddr = location.protocol === 'https:'
|
|||||||
// Util functions
|
// Util functions
|
||||||
const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
|
const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
|
||||||
const degreesToRadians = (degrees) => degrees * (Math.PI / 180);
|
const degreesToRadians = (degrees) => degrees * (Math.PI / 180);
|
||||||
|
const lerp = (start, end, t) => start * (1 - t) + end * t;
|
||||||
|
|
||||||
|
function lerpPosition(pos1, pos2, t) {
|
||||||
|
if (!pos1 || !pos2) return pos2 || pos1 || null;
|
||||||
|
return {
|
||||||
|
x: lerp(pos1.x, pos2.x, t),
|
||||||
|
y: lerp(pos1.y, pos2.y, t),
|
||||||
|
z: lerp(pos1.z, pos2.z, t)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function lerpAngle(a, b, t) {
|
||||||
|
while (a > 360) a -= 360;
|
||||||
|
while (a < 0) a += 360;
|
||||||
|
while (b > 360) b -= 360;
|
||||||
|
while (b < 0) b += 360;
|
||||||
|
|
||||||
|
let diff = b - a;
|
||||||
|
if (diff > 180) diff -= 360;
|
||||||
|
if (diff < -180) diff += 360;
|
||||||
|
|
||||||
|
return a + diff * t;
|
||||||
|
}
|
||||||
|
|
||||||
const pingTracker = {
|
const pingTracker = {
|
||||||
history: [],
|
history: [],
|
||||||
@ -103,6 +142,74 @@ const pingTracker = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function updateEntityInterpolation(entityId, newData) {
|
||||||
|
const now = performance.now();
|
||||||
|
|
||||||
|
if (!entityInterpolationData[entityId]) {
|
||||||
|
entityInterpolationData[entityId] = {
|
||||||
|
current: JSON.parse(JSON.stringify(newData)),
|
||||||
|
target: JSON.parse(JSON.stringify(newData)),
|
||||||
|
lastUpdateTime: now
|
||||||
|
};
|
||||||
|
return entityInterpolationData[entityId].current;
|
||||||
|
}
|
||||||
|
|
||||||
|
entityInterpolationData[entityId].current = JSON.parse(JSON.stringify(entityInterpolationData[entityId].target));
|
||||||
|
entityInterpolationData[entityId].target = JSON.parse(JSON.stringify(newData));
|
||||||
|
entityInterpolationData[entityId].lastUpdateTime = now;
|
||||||
|
|
||||||
|
return entityInterpolationData[entityId].current;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInterpolatedEntityData(entityId) {
|
||||||
|
if (!NETWORK_SETTINGS.useInterpolation || !entityInterpolationData[entityId]) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = entityInterpolationData[entityId];
|
||||||
|
const now = performance.now();
|
||||||
|
const elapsed = now - data.lastUpdateTime;
|
||||||
|
|
||||||
|
const pingTime = pingTracker.getAveragePing();
|
||||||
|
const targetDuration = Math.min(200, Math.max(50, pingTime * 0.8));
|
||||||
|
const t = Math.min(1, elapsed / targetDuration);
|
||||||
|
const easedT = t * (2 - t);
|
||||||
|
|
||||||
|
const result = JSON.parse(JSON.stringify(data.current));
|
||||||
|
|
||||||
|
if (result.Player) {
|
||||||
|
if (data.current.Player && data.target.Player) {
|
||||||
|
if (data.current.Player.pos && data.target.Player.pos) {
|
||||||
|
result.Player.pos = lerpPosition(
|
||||||
|
data.current.Player.pos,
|
||||||
|
data.target.Player.pos,
|
||||||
|
easedT * NETWORK_SETTINGS.interpolationAmount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.current.Player.yaw !== undefined && data.target.Player.yaw !== undefined) {
|
||||||
|
result.Player.yaw = lerpAngle(
|
||||||
|
data.current.Player.yaw,
|
||||||
|
data.target.Player.yaw,
|
||||||
|
easedT * NETWORK_SETTINGS.interpolationAmount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (result.Bomb) {
|
||||||
|
if (data.current.Bomb && data.target.Bomb) {
|
||||||
|
if (data.current.Bomb.pos && data.target.Bomb.pos) {
|
||||||
|
result.Bomb.pos = lerpPosition(
|
||||||
|
data.current.Bomb.pos,
|
||||||
|
data.target.Bomb.pos,
|
||||||
|
easedT * NETWORK_SETTINGS.interpolationAmount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
requestAnimationFrame(render);
|
requestAnimationFrame(render);
|
||||||
|
|
||||||
@ -124,6 +231,37 @@ function render() {
|
|||||||
|
|
||||||
renderFrame();
|
renderFrame();
|
||||||
}
|
}
|
||||||
|
function sendRequest() {
|
||||||
|
isRequestPending = true;
|
||||||
|
pingTracker.startRequest();
|
||||||
|
|
||||||
|
clearTimeout(requestTimeoutTimer);
|
||||||
|
requestTimeoutTimer = setTimeout(() => {
|
||||||
|
if (isRequestPending) {
|
||||||
|
console.warn("[radarflow] Request timeout, retrying...");
|
||||||
|
isRequestPending = false;
|
||||||
|
|
||||||
|
if (retryCount < NETWORK_SETTINGS.maxRetries) {
|
||||||
|
retryCount++;
|
||||||
|
sendRequest();
|
||||||
|
} else {
|
||||||
|
retryCount = 0;
|
||||||
|
console.error("[radarflow] Maximum retries reached, reconnecting...");
|
||||||
|
reconnecting = true;
|
||||||
|
if (websocket) {
|
||||||
|
try {
|
||||||
|
websocket.close();
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
websocket = null;
|
||||||
|
}
|
||||||
|
setTimeout(connect, NETWORK_SETTINGS.reconnectDelay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, NETWORK_SETTINGS.requestTimeout);
|
||||||
|
|
||||||
|
websocket.send("requestInfo");
|
||||||
|
}
|
||||||
|
|
||||||
function renderFrame() {
|
function renderFrame() {
|
||||||
fillCanvas();
|
fillCanvas();
|
||||||
@ -170,9 +308,14 @@ function processPlayerPositions() {
|
|||||||
let oldPlayerList = { ...playerList };
|
let oldPlayerList = { ...playerList };
|
||||||
playerList = {};
|
playerList = {};
|
||||||
|
|
||||||
entityData.forEach(data => {
|
entityData.forEach((data, index) => {
|
||||||
|
const entityId = `entity_${index}`;
|
||||||
|
|
||||||
if (data.Player) {
|
if (data.Player) {
|
||||||
const player = data.Player;
|
const player = data.Player;
|
||||||
|
if (NETWORK_SETTINGS.useInterpolation) {
|
||||||
|
updateEntityInterpolation(entityId, data);
|
||||||
|
}
|
||||||
|
|
||||||
if (player.playerType === "Local") {
|
if (player.playerType === "Local") {
|
||||||
localYaw = player.yaw;
|
localYaw = player.yaw;
|
||||||
@ -211,8 +354,6 @@ function processPlayerPositions() {
|
|||||||
rotationDisabledUntilRespawn = true;
|
rotationDisabledUntilRespawn = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[radarflow] Focused player: ${focusedPlayerName}, Position: ${focusedPlayerPos ? 'Found' : 'Not found'}, Rotation disabled: ${temporarilyDisableRotation || rotationDisabledUntilRespawn}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawImage() {
|
function drawImage() {
|
||||||
@ -314,11 +455,50 @@ function drawPlayerHealth(pos, playerType, health, hasBomb) {
|
|||||||
function drawEntities() {
|
function drawEntities() {
|
||||||
if (!entityData) return;
|
if (!entityData) return;
|
||||||
|
|
||||||
entityData.forEach(entity => {
|
const clipRect = {
|
||||||
if (entity.Bomb) {
|
x: -50,
|
||||||
drawBomb(entity.Bomb.pos, entity.Bomb.isPlanted);
|
y: -50,
|
||||||
} else if (entity.Player) {
|
width: canvas.width + 100,
|
||||||
const player = entity.Player;
|
height: canvas.height + 100
|
||||||
|
};
|
||||||
|
|
||||||
|
entityData.forEach((entity, index) => {
|
||||||
|
const entityId = `entity_${index}`;
|
||||||
|
let interpolatedEntity = null;
|
||||||
|
|
||||||
|
if (NETWORK_SETTINGS.useInterpolation) {
|
||||||
|
interpolatedEntity = getInterpolatedEntityData(entityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderEntity = interpolatedEntity || entity;
|
||||||
|
|
||||||
|
if (!renderEntity) return;
|
||||||
|
|
||||||
|
let pos;
|
||||||
|
if (renderEntity.Bomb) {
|
||||||
|
pos = renderEntity.Bomb.pos;
|
||||||
|
} else if (renderEntity.Player) {
|
||||||
|
pos = renderEntity.Player.pos;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pos) return;
|
||||||
|
|
||||||
|
const transformed = mapAndTransformCoordinates(pos);
|
||||||
|
const mapPos = transformed.pos;
|
||||||
|
|
||||||
|
const isVisible = mapPos.x >= clipRect.x &&
|
||||||
|
mapPos.x <= clipRect.x + clipRect.width &&
|
||||||
|
mapPos.y >= clipRect.y &&
|
||||||
|
mapPos.y <= clipRect.y + clipRect.height;
|
||||||
|
|
||||||
|
if (!isVisible) return;
|
||||||
|
|
||||||
|
if (renderEntity.Bomb) {
|
||||||
|
drawBomb(renderEntity.Bomb.pos, renderEntity.Bomb.isPlanted);
|
||||||
|
} else if (renderEntity.Player) {
|
||||||
|
const player = renderEntity.Player;
|
||||||
let fillStyle = localColor;
|
let fillStyle = localColor;
|
||||||
|
|
||||||
switch (player.playerType) {
|
switch (player.playerType) {
|
||||||
@ -715,7 +895,7 @@ function drawEntity(pos, fillStyle, dormant, hasBomb, yaw, hasAwp, playerType, i
|
|||||||
|
|
||||||
const transformed = mapAndTransformCoordinates(pos);
|
const transformed = mapAndTransformCoordinates(pos);
|
||||||
const mapPos = transformed.pos;
|
const mapPos = transformed.pos;
|
||||||
const circleRadius = transformed.textSize * 0.6;
|
let circleRadius = transformed.textSize * 0.6;
|
||||||
const distance = circleRadius + 2;
|
const distance = circleRadius + 2;
|
||||||
const radius = distance + 5;
|
const radius = distance + 5;
|
||||||
const arrowWidth = 35;
|
const arrowWidth = 35;
|
||||||
@ -864,6 +1044,12 @@ function unloadMap() {
|
|||||||
function processData(data) {
|
function processData(data) {
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
|
|
||||||
|
const now = performance.now();
|
||||||
|
lastUpdateTime = now;
|
||||||
|
lastResponseTime = now;
|
||||||
|
connectionHealthy = true;
|
||||||
|
isRequestPending = false;
|
||||||
|
|
||||||
radarData = data;
|
radarData = data;
|
||||||
freq = data.freq;
|
freq = data.freq;
|
||||||
entityData = data.entityData;
|
entityData = data.entityData;
|
||||||
@ -889,6 +1075,17 @@ function decompressData(data) {
|
|||||||
try {
|
try {
|
||||||
pingTracker.endRequest();
|
pingTracker.endRequest();
|
||||||
|
|
||||||
|
clearTimeout(requestTimeoutTimer);
|
||||||
|
lastResponseTime = performance.now();
|
||||||
|
connectionHealthy = true;
|
||||||
|
retryCount = 0;
|
||||||
|
|
||||||
|
const rtt = pingTracker.getAveragePing();
|
||||||
|
networkLatencyHistory.push(rtt);
|
||||||
|
if (networkLatencyHistory.length > 10) {
|
||||||
|
networkLatencyHistory.shift();
|
||||||
|
}
|
||||||
|
|
||||||
if (data[0] === 0x01) {
|
if (data[0] === 0x01) {
|
||||||
try {
|
try {
|
||||||
if (typeof pako === 'undefined') {
|
if (typeof pako === 'undefined') {
|
||||||
@ -917,37 +1114,53 @@ function decompressData(data) {
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("[radarflow] Data processing error:", e);
|
console.error("[radarflow] Data processing error:", e);
|
||||||
|
isRequestPending = false;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function connect() {
|
function connect() {
|
||||||
|
reconnecting = true;
|
||||||
|
|
||||||
if (websocket == null) {
|
if (websocket == null) {
|
||||||
console.log(`[radarflow] Connecting to ${websocketAddr}`);
|
console.log(`[radarflow] Connecting to ${websocketAddr}`);
|
||||||
|
|
||||||
let socket = new WebSocket(websocketAddr);
|
let socket = new WebSocket(websocketAddr);
|
||||||
|
socket.binaryType = "arraybuffer";
|
||||||
|
|
||||||
socket.onopen = () => {
|
socket.onopen = () => {
|
||||||
console.log("[radarflow] Connection established");
|
console.log("[radarflow] Connection established");
|
||||||
requestAnimationFrame(render);
|
lastResponseTime = performance.now();
|
||||||
|
connectionHealthy = true;
|
||||||
|
reconnecting = false;
|
||||||
|
isRequestPending = false;
|
||||||
|
retryCount = 0;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
socket.send(`ping:0`);
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
if (!fpsStartTime) {
|
||||||
|
requestAnimationFrame(render);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
socket.onmessage = (event) => {
|
socket.onmessage = (event) => {
|
||||||
isRequestPending = false;
|
if (event.data === "pong") {
|
||||||
|
lastResponseTime = performance.now();
|
||||||
if (event.data === "error") {
|
|
||||||
console.error("[radarflow] Server error");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.data instanceof Blob) {
|
if (event.data === "error") {
|
||||||
event.data.arrayBuffer().then(buffer => {
|
console.error("[radarflow] Server error");
|
||||||
const data = new Uint8Array(buffer);
|
isRequestPending = false;
|
||||||
const jsonData = decompressData(data);
|
return;
|
||||||
if (jsonData) processData(jsonData);
|
}
|
||||||
}).catch(err => {
|
|
||||||
console.error("[radarflow] Buffer processing error:", err);
|
if (event.data instanceof ArrayBuffer) {
|
||||||
});
|
const data = new Uint8Array(event.data);
|
||||||
|
const jsonData = decompressData(data);
|
||||||
|
if (jsonData) processData(jsonData);
|
||||||
} else if (typeof event.data === 'string') {
|
} else if (typeof event.data === 'string') {
|
||||||
try {
|
try {
|
||||||
const jsonData = JSON.parse(event.data);
|
const jsonData = JSON.parse(event.data);
|
||||||
@ -956,6 +1169,8 @@ function connect() {
|
|||||||
} else {
|
} else {
|
||||||
processData(jsonData);
|
processData(jsonData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lastResponseTime = performance.now();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("[radarflow] JSON parse error:", e);
|
console.error("[radarflow] JSON parse error:", e);
|
||||||
}
|
}
|
||||||
@ -965,8 +1180,12 @@ function connect() {
|
|||||||
socket.onclose = (event) => {
|
socket.onclose = (event) => {
|
||||||
console.log("[radarflow] Connection closed");
|
console.log("[radarflow] Connection closed");
|
||||||
websocket = null;
|
websocket = null;
|
||||||
unloadMap();
|
|
||||||
setTimeout(connect, 1000);
|
if (!reconnecting) {
|
||||||
|
unloadMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(connect, NETWORK_SETTINGS.reconnectDelay);
|
||||||
};
|
};
|
||||||
|
|
||||||
socket.onerror = (error) => {
|
socket.onerror = (error) => {
|
||||||
@ -974,6 +1193,8 @@ function connect() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
websocket = socket;
|
websocket = socket;
|
||||||
|
} else {
|
||||||
|
reconnecting = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1023,18 +1244,22 @@ function togglePerformanceMode() {
|
|||||||
drawMoney = false;
|
drawMoney = false;
|
||||||
drawHealth = false;
|
drawHealth = false;
|
||||||
|
|
||||||
|
NETWORK_SETTINGS.interpolationAmount = 0.85;
|
||||||
|
|
||||||
document.getElementById("namesCheck").checked = false;
|
document.getElementById("namesCheck").checked = false;
|
||||||
document.getElementById("gunsCheck").checked = false;
|
document.getElementById("gunsCheck").checked = false;
|
||||||
document.getElementById("moneyDisplay").checked = false;
|
document.getElementById("moneyDisplay").checked = false;
|
||||||
document.getElementById("healthCheck").checked = false;
|
document.getElementById("healthCheck").checked = false;
|
||||||
|
|
||||||
console.log("[radarflow] Performance mode enabled");
|
console.log("[radarflow] Performance mode enabled with enhanced smoothing");
|
||||||
} else {
|
} else {
|
||||||
drawNames = document.getElementById("namesCheck").checked = true;
|
drawNames = document.getElementById("namesCheck").checked = true;
|
||||||
drawGuns = document.getElementById("gunsCheck").checked = true;
|
drawGuns = document.getElementById("gunsCheck").checked = true;
|
||||||
drawMoney = document.getElementById("moneyDisplay").checked = true;
|
drawMoney = document.getElementById("moneyDisplay").checked = true;
|
||||||
drawHealth = document.getElementById("healthCheck").checked = true;
|
drawHealth = document.getElementById("healthCheck").checked = true;
|
||||||
|
|
||||||
|
NETWORK_SETTINGS.interpolationAmount = 0.7;
|
||||||
|
|
||||||
console.log("[radarflow] Performance mode disabled");
|
console.log("[radarflow] Performance mode disabled");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user