Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

rabbit97 님의 블로그

4일 일지 본문

프로젝트 Final

4일 일지

rabbit97 2024. 12. 4. 21:19

# 오늘의 개발 진행 상황

추측 항법 로직 구현 중 레이턴시 값을 어떻게 줘야하나 고민 중

 

핑을 보내서 퐁을 받고 레이턴시 값을 구하자 결정하고 클라이언트 코드를 확인하니

 

패킷을 받아서 처리하는 부분이 컴파일된 파일이였고 원본 파일이 없어 어떻게 처리하지 고민 중

 

일단 튜터님께 질문을 드리러가 이걸 어떻게 해야하나 조언을 구해보기

 

 

 

추측 항법을 구현하려는 이유가 배포된 서버에서 패킷이 너무 많아 늦게 도착한다고 추측하고 위치 값을 일정 주기로 보내고 추측 항법을 사용해서 트래픽을 줄여보자는 의견이였는데

 

튜터님의 답변은 네트워크 트래픽때문에 추측 항법을 구현하는건 맞지 않다는 답변.

 

 

 

근본적인 문제를 찾기 위해서 최대한 트래픽을 줄일 수 있는 방법을 찾고 다시 테스트 시작

 

위치이동 업데이트, 유저 업데이트 정보를 담은 패킷을 좀 늦은 주기로 보내주기

 

 

확실히 효과가 있었다. 과도한 트래픽때문에 다른 카드들의 패킷이 나중에 도착해 각각의 클라이언트에서 반영이 늦었던 것.

 

일단 추측 항법을 구현해야한다고 확신을 받았고 로직 구현 진행

 

 

그런데 마찬가지로 레이턴시 값이 문제였는데 클라이언트를 어떻게 수정해야하나 고민 중

 

이미 구현되어있는 클라이언트의 핑 테스트 흔적 발견

 

주석 처리가 되어있었는데 아마 서버로 빈 내용의 로그인 리스폰스를 보내고 핑 테스트를 진행했다고 판단

 

 

이걸 이용해서 주석처리를 풀고 서버에서 받은 이 빈 로그인 리스폰스를 가지고 받은 시간을 일정 주기마다 체크해서

 

평균을 내려 레이턴시를 구할 수 있다고 판단

 

그렇게 진행 한 로직

 

 

 

포지션 업데이트 핸들러

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 = 33;
const MIN_SPEED = 2;
const MAX_SPEED = 5;

// 각 유저의 마지막 위치와 시간을 저장하는 Map
const lastPositions = new Map();
const emptyPacketLatencies = new Map(); // 빈 패킷 처리로 추가된 레이턴시
let lastUpdateTime = Date.now();

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 = emptyPacketLatencies.get(userId) || 0;
  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;

    return {
      id: userId,
      x: lastPos.x + normalizedVX * speed * deltaTime,
      y: lastPos.y + normalizedVY * speed * deltaTime,
      timestamp: currentTime,
    };
  }

  return {
    id: userId,
    x: lastPos.x,
    y: lastPos.y,
    timestamp: currentTime,
  };
}

// 핸들러는 빈 패킷을 처리하도록 설정
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]); // 실제 전송할 데이터 없이 만드는 응답
}

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 default handlePositionUpdate;

// 빈 패킷에 대한 핸들러 설정 필요
socket.on('data', (data) => {
  if (isEmptyPacket(data)) {
    handleEmptyPacket(socket);
  }
  // 기타 데이터는 추가로 처리
});

function isEmptyPacket(data) {
  return data.length === 0;
}

 

 

추가된 레이턴시 핸들러

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);
  console.log(`User ${userId} login response time recorded at ${currentTime}`);
};

export default handleLoginResponse;

export { loginResponseTimes };

 

 

그리고 핸들러 연결

const handlers = {
  [packetType.REGISTER_REQUEST]: {
    handler: registerHandler,
    protoType: 'C2SRegisterRequest',
  },
  [packetType.REGISTER_RESPONSE]: {
    handler: undefined,
    protoType: 'auth.C2SRegisterResponse',
  },
  [packetType.LOGIN_REQUEST]: {
    handler: loginHandler,
    protoType: 'auth.C2SLoginRequest',
  },
  [packetType.LOGIN_RESPONSE]: {
    handler: handleLoginResponse,
    protoType: 'auth.S2CLoginResponse', ///

 

 

아직 로직이 완성된게 아니라서 테스트를 못해보았다.

내일 점심쯤 될 듯

'프로젝트 Final' 카테고리의 다른 글

9일 일지  (1) 2024.12.09
5일 일지  (1) 2024.12.06
3일 일지  (1) 2024.12.03
29일 일지  (1) 2024.11.30
28일 일지  (0) 2024.11.28