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 님의 블로그

23일 일지 본문

프로젝트 Final

23일 일지

rabbit97 2024. 11. 23. 20:27

# 리액션 핸들러와 관련해서 에러가 해결되지 않아 정리하는 기록

 

 - 지금 현상 : 공격 카드를 사용하면 무한로딩에 걸린다. 클라이언트에선 널값을 받았다고는 나오는데 어디서 무슨 데이터를 받았는지는 확인 불가

 





# 해본 방법

 - 정확한 오류 원인을 알 수 없음 : 리액션과 관련 된 로직 모두 로그 찍어보고 분석해보기

 

유즈 카드 노티피케이션, 리액션 리퀘스트 핸들러, 유저 업데이트 노티피케이션

 

이 3곳을 중점으로 보았는데

 

유즈카드에 케이스 중 공격 카드에 체력을 깎는 로직을 넣으면 정상적으로 잘 깎이는 거 확인

하지만 원래는 리액션 리퀘스트가 발동 되고 상대방이 방어 카드가 있으면 방어카드를 사용할지 말지에 대한 선택이 주어져야하는데 체력을 먼저 깎고 선택지가 주어짐 ( 둘 다 동시에 진행 된 거일 수도 있음 - 일단 검증 작업 없이 방어카드를 사용 안했다는 결과가 미리 클라이언트로 전달 )

 

 

 

공격로직에 체력을 깎는 로직을 넣기 전 카드 사용 로직 테스트를 위해 회복에 체력을 깎는 로직을 임시로 넣었는데

 

이 카드는 체력이 정상적으로 잘 깎이면서 무한로딩이 걸리지 않음 - 유저 업데이트 로직은 정상적으로 잘 작동 중인걸 알 수 있음

 

 

 

둘의 로그를 비교하기 위해 둘 다 로그를 찍어봄(로그가 너무 길어 필요한 부분만 기록)


공격 카드를 사용했을때의 로그

[nodemon] restarting due to changes...
[nodemon] starting `node src/server.js`
Protobuf 파일 로드 완료
127.0.0.1:5555에서 실행
{ address: '127.0.0.1', family: 'IPv4', port: 5555 }
...
useCard 실행
useCardNotification - getAllUserDatas: [
  {
    "id": 3,
    "nickname": "2222",
    "character": {
      ...
    }
  },
  {
    "id": 2,
    "nickname": "1111",
    "character": {
      ...
    }
  }
]
...
handleReactionRequest - Received payload: C2SReactionRequest {}
handleReactionRequest - reactionType: 0
handleReactionRequest - gameSession found
handleReactionRequest - User character info: {
  ...
}
handleReactionRequest - NONE_REACTION 처리
handleReactionRequest - Sending response: <Buffer 00 24 05 31 2e 30 2e 30 00 00 00 04 00 00 00 00>

공격 카드를 사용한 경우

공격 카드 사용 시 로그:

  1. 서버가 시작되고 연결된 클라이언트들의 요청을 처리함.
  2. useCard 함수가 실행됨.
  3. useCardNotification을 통해 각 사용자 데이터가 서버 로그에 출력됨.
  4. 각 사용자 객체에 대한 정보를 로그에 상세히 기록함.
  5. 클라이언트가 요청을 보낸 후 서버에서 응답을 보내는 과정에서 handleReactionRequest 함수가 호출됨.
  6. handleReactionRequest 함수에서 논리 처리를 시작하고, NONE_REACTION 처리를 수행함.
  7. 이후 handleReactionRequest - Sending response 로그가 출력된 뒤에 추가적인 단계가 진행되지 않음.
  8. 결과적으로 무한 로딩에 걸림.

 

 

 

 

 

회복 카드를 사용했을때의 로그

[nodemon] starting `node src/server.js`
Protobuf 파일 로드 완료
127.0.0.1:5555에서 실행
...
useCard 실행
useCardNotification - getAllUserDatas: [
  {
    "id": 2,
    "nickname": "1111",
    "character": {
      ...
    }
  },
  {
    "id": 3,
    "nickname": "2222",
    "character": {
      ...
    }
  }
]
User object: {
  "socket": {
    ...
  },
  "id": 2,
  "nickname": "1111",
  ...
}
Notifying user: 2
User object: {
  "socket": {
    ...
  },
  "id": 3,
  "nickname": "2222",
  ...
}
Notifying user: 3

회복 카드를 사용한 경우

회복 카드를 사용 시 로그:

  1. 서버가 시작되고 연결된 클라이언트들의 요청을 처리함.
  2. useCard 함수가 실행됨.
  3. useCardNotification을 통해 각 사용자 데이터가 서버 로그에 출력됨.
  4. 각 사용자 객체에 대한 정보를 로그에 상세히 기록함.
  5. 클라이언트가 요청을 보낸 후, 서버가 각 사용자에게 응답을 데게로 전달함.

 

 

그럼 이제 서버에선 클라이언트에 무슨 데이터를 줘야할까???????

 

message C2SReactionRequest {

ReactionType reactionType = 1;

// NOT_USE_CARD = 1

}

 

message S2CReactionResponse {

bool success = 1;

GlobalFailCode failCode = 2;

}

 

message CharacterStateInfoData {

CharacterStateType state = 1;

CharacterStateType nextState = 2;

int64 nextStateAt = 3; // state가 nextState로 풀리는 밀리초 타임스탬프.

state가 NONE이면 0 int64 stateTargetUserId = 4; // state에 target이 있을 경우

}


사실 어지간한 데이터는 모두 서버에서 로그로 확인한 데이터들인데 무슨 데이터를 어떻게 받아야하는지 감이 잘 안잡히는 상황

 

1. useCard 함수 실행 후 BbangShooterStateInfo 및 BbangTargetStateInfo가 제대로 작동하여 stateInfo가 올바르게 변경됨:

useCard 실행
bbang shooter state info : 2 3
{ state: 1, nextState: 0, nextStateAt: 1732361551953, stateTargetUserId: 3 }
bbang target state info : 3
{ state: 2, nextState: 0, nextStateAt: 1732361551954, stateTargetUserId: 3 }

 

 

2. useCardNotification을 통해 각 사용자의 stateInfo가 올바르게 전달됨:

useCardNotification - getAllUserDatas: [
  {
    "id": 3,
    "nickname": "2222",
    "character": {
      ...
      "stateInfo": {
        "state": 2,
        "nextState": 0,
        "nextStateAt": 1732361551954,
        "stateTargetUserId": 3
      },
      ...
    }
  },
  {
    "id": 2,
    "nickname": "1111",
    "character": {
      ...
      "stateInfo": {
        "state": 1,
        "nextState": 0,
        "nextStateAt": 1732361551953,
        "stateTargetUserId": 3
      },
      ...
    }
  }
]

 

 

3. handleReactionRequest에서 유저의 상태 정보가 올바르게 확인됨:

handleReactionRequest - Received payload: C2SReactionRequest {}
handleReactionRequest - reactionType: 0
handleReactionRequest - gameSession found
handleReactionRequest - User character info: {
  ...
  "stateInfo": {
    "state": 2,
    "nextState": 0,
    "nextStateAt": 1732361551954,
    "stateTargetUserId": 3
  },
  ...
}
Current user.stateInfo: {
  "state": 2,
  "nextState": 0,
  "nextStateAt": 1732361551954,
  "stateTargetUserId": 3
}
handleReactionRequest - NONE_REACTION 처리

 

 

 

 

테스트 중 발견한 상황

 

잠깐 지연시간이 걸렸었는데

 

정확히 한쪽 클라이언트에서 공격을 받았다는 팝업이 뜨면 무한 로딩에 걸린다

 

스테이트 인포 데이터가 클라이언트로 전달 되지 않았다면?

 

이쪽으로 접근을 해야할 듯 하다

 

현재 스테이트 인포 데이터를 처리하는 곳은 

 

switch (cardType) {
  //^ 공격
  case CARD_TYPE.BBANG:
    room.plusBbangCount(user.id); // 사용유저의 빵카운트를 +1
    room.BbangShooterStateInfo(user.id, targeId);
    room.BbangTargetStateInfo(targeId);
const useCardHandler = ({ socket, payload }) => {
  try {
    console.log('useCard 실행');
    const { cardType, targetUserId } = payload; // 사용카드, 타켓userId
    const targeId = targetUserId.low;
    const user = getUserBySocket(socket);
    const room = getGameSessionByUser(user);

    const responsePayload = {
      success: true,
      failCode: GLOBAL_FAIL_CODE.NONE_FAILCODE,
    };
BbangShooterStateInfo(userId, targeId) {
  this.getCharacter(userId).stateInfo.state = CHARACTER_STATE_TYPE.BBANG_SHOOTER;
  this.getCharacter(userId).stateInfo.nextState = CHARACTER_STATE_TYPE.NONE_CHARACTER_STATE;
  this.getCharacter(userId).stateInfo.nextStateAt = Date.now() + 1000;
  this.getCharacter(userId).stateInfo.stateTargetUserId = targeId;
}

BbangTargetStateInfo(targeId) {
  this.getCharacter(targeId).stateInfo.state = CHARACTER_STATE_TYPE.BBANG_TARGET;
  this.getCharacter(targeId).stateInfo.nextState = CHARACTER_STATE_TYPE.NONE_CHARACTER_STATE;
  this.getCharacter(targeId).stateInfo.nextStateAt = Date.now() + 1000;
  this.getCharacter(targeId).stateInfo.stateTargetUserId = targeId;
}

 

이 부분

 

카드를 사용하면 해당 데이터가 바뀌고 클라이언트로 유저 업데이트 노티피케이션으로 업데이트 되는데

그 과정에서 문제가 생긴 듯

 

맨 위의 로직을 주석처리하면 무한 로딩이 안걸리긴 한다

 

하나씩 주석 처리를 한다면?

// room.BbangShooterStateInfo(user.id, targeId);

 

이 부분을 주석 처리 하니깐 사용자는 로딩이 나오지 않는다.

 

// room.BbangTargetStateInfo(targeId);

 

반대로 이 부분을 주석처리 하면 사용자만 무한 로딩에 걸리고 팝업이 걸리지 않는다

 

확실한건 

BbangShooterStateInfo(userId, targeId) {
  this.getCharacter(userId).stateInfo.state = CHARACTER_STATE_TYPE.BBANG_SHOOTER;
  this.getCharacter(userId).stateInfo.nextState = CHARACTER_STATE_TYPE.NONE_CHARACTER_STATE;
  this.getCharacter(userId).stateInfo.nextStateAt = Date.now() + 1000;
  this.getCharacter(userId).stateInfo.stateTargetUserId = targeId;
}

BbangTargetStateInfo(targeId) {
  this.getCharacter(targeId).stateInfo.state = CHARACTER_STATE_TYPE.BBANG_TARGET;
  this.getCharacter(targeId).stateInfo.nextState = CHARACTER_STATE_TYPE.NONE_CHARACTER_STATE;
  this.getCharacter(targeId).stateInfo.nextStateAt = Date.now() + 1000;
  this.getCharacter(targeId).stateInfo.stateTargetUserId = targeId;
}

 

이 스테이트 인포 데이터 부분이 뭔가 잘못 됨을 알 수 있는데

 

message CharacterStateInfoData {

CharacterStateType state = 1;

CharacterStateType nextState = 2;

int64 nextStateAt = 3; // state가 nextState로 풀리는 밀리초 타임스탬프. state가 NONE이면 0

int64 stateTargetUserId = 4; // state에 target이 있을 경우

}

 

=====================================================================

 

임시 변경사항

import { getGameSessionBySocket, getGameSessionByUser } from '../../sessions/game.session.js';
import { createResponse } from '../../utils/packet/response/createResponse.js';
import { PACKET_TYPE } from '../../constants/header.js';
import handleError from '../../utils/errors/errorHandler.js';
import userUpdateNotification from '../../utils/notification/userUpdateNotification.js';
import { getUserBySocket } from '../../sessions/user.session.js';

const packetType = PACKET_TYPE;

const REACTION_TYPE = {
  NONE_REACTION: 0,
  NOT_USE_CARD: 1,
};

const handleReactionRequest = async ({ socket, payload }) => {
  try {
    console.log('handleReactionRequest - Received payload:', payload);

    if (!payload || typeof payload !== 'object') {
      throw new Error('Payload가 올바르지 않습니다.');
    }

    const { reactionType } = payload;
    console.log('handleReactionRequest - reactionType:', reactionType);

    if (!Object.values(REACTION_TYPE).includes(reactionType)) {
      throw new Error('유효하지 않은 리액션 타입입니다.');
    }

    const gameSession = await getGameSessionBySocket(socket);
    if (!gameSession) {
      throw new Error('해당 유저의 게임 세션이 존재하지 않습니다.');
    }
    console.log('handleReactionRequest - gameSession found');

    const user = getUserBySocket(socket);
    const room = getGameSessionByUser(user);

    if (!room.users || !room.users[user.id]) {
      throw new Error(`User with id ${user.id} not found in room users.`);
    }

    // 클라이언트에서 낫 유즈 카드 타입을 보내는 조건을 모르겠음
    // 임시 수정 사항 - 쉴드를 사용하면 쉴드 카드 한장을 줄이고 공격 전 상태로 돌림
    // 쉴드가 없거나 피해받기를 누르면 클라이언트에서는 논 리액션 타입으로 보냄
    // 기능은 정상 작동하나 의문점이 많음

    // let defenseUsed = false;
    // let timer = null;
    //
    // // 방어 반응 시 이벤트 핸들러 등록
    // console.log('Registering defenseResponse event listener for socket:', socket.id);
    //
    // socket.once('defenseResponse', (reactionType) => {
    //   console.log('defenseResponse event received:', reactionType);
    //   clearTimeout(timer); // 타이머 멈춤
    //
    //   if (reactionType === REACTION_TYPE.NOT_USE_CARD) {
    //     console.log(`Defense card used by user ${user.id}`);
    //     if (room.users && room.users[user.id]) {
    //       room.resetStateInfoAllUsers();
    //       userUpdateNotification(room);
    //       defenseUsed = true;
    //     } else {
    //       console.error(`User with id ${user.id} not found in room users.`);
    //     }
    //   } else {
    //     console.log(`No defense card used by user ${user.id}`);
    //     if (room.users && room.users[user.id] && room.users[user.id].character.hp > 0) {
    //       room.users[user.id].character.hp -= 1;
    //     } else {
    //       console.error(`User with id ${user.id} not found in room users or already dead.`);
    //     }
    //     room.resetStateInfoAllUsers();
    //     userUpdateNotification(room);
    //   }
    // });

    // `reactionType`가 NONE_REACTION이거나 아무 반응이 없는 경우 즉시 피해 적용
    if (reactionType === REACTION_TYPE.NONE_REACTION) {
      console.log(`Immediate damage applied to user ${user.id}`);
      if (room.users && room.users[user.id] && room.users[user.id].character.hp > 0) {
        room.users[user.id].character.hp -= 1;
      } else {
        console.error(`User with id ${user.id} not found in room users or already dead.`);
      }
      room.resetStateInfoAllUsers();
      userUpdateNotification(room);
    }

    // 리액션 처리 완료 후 응답 전송
    const reactionResponseData = {
      success: true,
      failCode: 0,
    };
    const reactionResponse = createResponse(
      packetType.REACTION_RESPONSE,
      socket.sequence,
      reactionResponseData,
    );
    console.log('handleReactionRequest - Sending response:', reactionResponse);

    if (typeof socket.write === 'function') {
      socket.write(reactionResponse);
    } else {
      throw new Error('socket.write is not a function');
    }
  } catch (error) {
    console.error('리액션 처리 중 에러 발생:', error.message);

    const errorResponse = createResponse(packetType.REACTION_RESPONSE, socket.sequence, {
      success: false,
      failCode: 1,
      message: error.message || 'Reaction failed',
    });

    if (typeof socket.write === 'function') {
      socket.write(errorResponse);
    } else {
      console.error('socket.write is not a function:', socket);
    }

    handleError(socket, error);
  }
};

export default handleReactionRequest;

 

공격이 끝나고 체력이 낮아지면 유저 업데이트 알림 전 

 

resetStateInfoAllUsers() {
  Object.values(this.users).forEach((roomUser) => {
    roomUser.character.stateInfo.state = CHARACTER_STATE_TYPE.NONE_CHARACTER_STATE;
    roomUser.character.stateInfo.nextState = CHARACTER_STATE_TYPE.NONE_CHARACTER_STATE;
    roomUser.character.stateInfo.stateTargetUserId = null;
    roomUser.character.stateInfo.nextStateAt = null;
  });
  console.log("All users' state info have been reset.");
}

 

이 로직을 호출

 

강제로 서버에서 변경하고 알리는 형태

 

case CARD_TYPE.SHIELD:
  console.log('방어 카드 사용');
  room.resetStateInfoAllUsers(); // 모든 유저의 상태 초기화 (공격을 사용하기 전으로 돌리는 방식)

 

방어카드 사용시 클라이언트에서 따로 보내는 패킷이 없어 방어카드를 사용하면 그냥 스테이트 인포 데이터를 리셋하는 쪽으로 진행

 

방어카드 사용 조건이 공격을 맞았을때 사용이 가능한 카드여서 게임 진행에는 문제가 없을 듯 하다

 

일단 1 대 1 로만 테스트를 해서 올 유저로 되어있었는데 이제 유저아이디와 타겟 아이디를 받아서 리셋하는 방향으로 결정

 

리액션 타입 1번 낫 유즈 카드는 자동 방어가 되는 캐릭터가 방어를 했을때 클라이언트로 보내는 타입으로 추측 (로직 추가해야함)

 

클라이언트에서 원하는 방향인지는 모르겠지만 로직은 이제 잘 작동한다

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

27일 일지  (0) 2024.11.27
26일 일지  (0) 2024.11.26
22일 일지  (1) 2024.11.22
21일 일지  (1) 2024.11.21
20일 일지  (0) 2024.11.20