rabbit97 님의 블로그
5일 일지 본문
# 오늘의 개발 진행 상황
추측 항법을 적용 했으나 보내는 주기가 0.1초 이상으로 넘어가면 여전히 움직임이 끊기기는 함
클라이언트에서 보내는 패킷번호 4번 빈 내용의 로그인 리스폰스로 서버에 일정 주기만큼 보내서 그 받는 시간을 저장해 평균값으로 레이턴시를 구하는 방법
import { getGameSessionBySocket } from '../../sessions/game.session.js';
import { createResponse } from '../../utils/packet/response/createResponse.js';
import config from '../../config/config.js';
import { getUserBySocket } from '../../sessions/user.session.js';
import handleError from '../../utils/errors/errorHandler.js';
const packetType = config.packet.packetType;
// 상수 정의
const UPDATE_INTERVAL = 100;
const MIN_SPEED = 2;
const MAX_SPEED = 5;
const DEFAULT_LATENCY = 50;
// 각 유저의 마지막 위치와 시간을 저장하는 Map
const lastPositions = new Map();
const emptyPacketLatencies = new Map();
let lastUpdateTime = Date.now();
function getLatencyForUser(userId) {
const latency = emptyPacketLatencies.get(userId);
return latency !== undefined ? latency / 2 : DEFAULT_LATENCY;
}
function predictPosition(userId, currentTime) {
const lastPos = lastPositions.get(userId);
if (!lastPos) return null;
const timeSinceLastUpdate = currentTime - lastUpdateTime;
if (timeSinceLastUpdate < UPDATE_INTERVAL) {
return {
id: userId,
x: lastPos.x,
y: lastPos.y,
timestamp: lastPos.timestamp,
};
}
const latency = getLatencyForUser(userId);
const deltaTime = (currentTime - lastPos.timestamp + latency) / 1000;
if (lastPos.isMoving) {
const speed = Math.min(
Math.max(Math.sqrt(lastPos.velocityX ** 2 + lastPos.velocityY ** 2), MIN_SPEED),
MAX_SPEED,
);
const distance = Math.sqrt(lastPos.velocityX ** 2 + lastPos.velocityY ** 2);
const normalizedVX = distance > 0 ? lastPos.velocityX / distance : 0;
const normalizedVY = distance > 0 ? lastPos.velocityY / distance : 0;
const predictedX = lastPos.x + normalizedVX * speed * deltaTime;
const predictedY = lastPos.y + normalizedVY * speed * deltaTime;
return {
id: userId,
x: predictedX,
y: predictedY,
timestamp: currentTime,
latency: latency,
};
}
return {
id: userId,
x: lastPos.x,
y: lastPos.y,
timestamp: currentTime,
latency: latency,
};
}
// 핸들러는 빈 패킷을 처리하도록 설정
function handleEmptyPacket(socket) {
const receiveTime = Date.now();
const responsePacket = createEmptyResponse();
socket.write(responsePacket, () => {
const sendTime = Date.now();
const rtt = sendTime - receiveTime;
emptyPacketLatencies.set(socket.userId, rtt);
console.log(`RTT for userId ${socket.userId}: ${rtt} ms`);
});
}
function createEmptyResponse() {
return Buffer.from([0x00]); // 실제 전송할 데이터 없이 만드는 응답
}
// 빈 패킷 여부를 확인하는 함수
function isEmptyPacket(data) {
return data.length === 1 && data[0] === 0x00;
}
const handlePositionUpdate = async ({ socket, payload }) => {
try {
if (!payload || typeof payload !== 'object') {
throw new Error('Payload가 올바르지 않습니다.');
}
const { x, y } = payload;
if (typeof x === 'undefined' || typeof y === 'undefined') {
throw new Error('페이로드에 x 또는 y 값이 없습니다.');
}
const gameSession = getGameSessionBySocket(socket);
if (!gameSession) {
throw new Error('해당 유저의 게임 세션이 존재하지 않습니다.');
}
const currentUser = getUserBySocket(socket);
if (!currentUser) {
throw new Error('유저가 존재하지 않습니다.');
}
const currentTime = Date.now();
const userId = currentUser.id;
const lastPos = lastPositions.get(userId);
let isMoving = false;
let velocityX = 0;
let velocityY = 0;
if (lastPos) {
const dx = x - lastPos.x;
const dy = y - lastPos.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0.1) {
isMoving = true;
const deltaTime = (currentTime - lastPos.timestamp) / 1000;
if (deltaTime > 0) {
velocityX = dx / deltaTime;
velocityY = dy / deltaTime;
}
}
}
lastPositions.set(userId, {
x,
y,
velocityX,
velocityY,
isMoving,
timestamp: currentTime,
});
currentUser.setPos(x, y);
if (currentTime - lastUpdateTime < UPDATE_INTERVAL) {
return;
}
lastUpdateTime = currentTime;
const characterPositions = [];
const allUsers = gameSession.getAllUsers();
allUsers.forEach((user) => {
const posData =
user.id === userId
? { id: userId, x, y, timestamp: currentTime }
: predictPosition(user.id, currentTime);
if (posData) {
characterPositions.push(posData);
}
});
const notiData = { characterPositions };
const notificationResponse = createResponse(
packetType.POSITION_UPDATE_NOTIFICATION,
socket.sequence,
notiData,
);
allUsers.forEach((user) => {
user.socket.write(notificationResponse);
});
} catch (error) {
handleError(socket, error);
}
};
export { handleEmptyPacket, isEmptyPacket };
export default handlePositionUpdate;
import { getUserBySocket } from '../../sessions/user.session.js';
// 로그인 응답 시간을 저장하는 Map
const loginResponseTimes = new Map();
export const handleLoginResponse = ({ socket }) => {
const currentTime = Date.now();
const currentUser = getUserBySocket(socket);
if (!currentUser) {
console.error('유저가 존재하지 않습니다.');
return;
}
const userId = currentUser.id;
loginResponseTimes.set(userId, currentTime);
};
export default handleLoginResponse;
export { loginResponseTimes };
클라이언트 코드가 수정이 많이 까다로울 걸 예상하기에 이렇게 적용을 해봤지만
이걸 적용하려는 목적이 네트워크 트래픽을 위치 이동이나 유저 정보 업데이트 등을 좀 줄여보자는 의도로 도입을 한건데
실제 배포 서버에서는 주기를 2초는 해야 카드 사용이 좀 그나마 매끄럽게 진행이 되었기에
적용을 해도 빛을 못보는 로직... (있으나 마나 하다)
결국 데브로 올리자는 말을 못꺼냈고 작업하던 브렌치에 조용히 담아두었다
# 내일은 중간 발표!!!!
팀원들과 다같이 발표 자료를 만들었다
사실 제일 부담되는건 발표였는데 다행히도 사다리타기에서 발표자가 내가 아니게 되었다
내일 지금 배포 서버의 문제점을 피드백 받았으면 좋겠다.
(문제를 알면 수정하려는 시도를 하겠는데 도대체 뭐가 문제인지..)