포트폴리오/서버연동 온라인 arpg - Knight Online

[포트폴리오(Knight Online)] - 비동기 버그 관련 수정

빗방울소리 2023. 5. 16. 20:06

 

 

 지난번 퀘스트 시스템을 구현하면서 비동기 문제 때문에 참 골치가 아팠는데, 결국 원인을 찾긴 하였다. 디버깅을 해도 이게 발생을 할 때도 있는데, 안할 때가 많아서 원인을 찾기가 쉽지 않았다. 이번 글에서는 원인을 찾은 과정과 해결책에 대해 정리해 보려고 한다.

 

 

 

 

 

 현재 NPC 출력 대사를 퀘스트의 진행 상태에 따라 다르게 출력되도록 해놨는데, 서버로부터 퀘스트 진행 상태를 정상적으로 받아왔음에도 NPC의 출력대사가 퀘스트 진행상태와 맞지 않았다. 예를 들어 퀘스트를 이미 클리어 했는데 퀘스트 진행 중일 때의 대사가 출력된다던가 말이다.

 

 

 

 

 

 

 

문제의 원인을 찾는 과정

 

 

 별거 없었다. 버그가 발생할 것 같은 지점에 break point를 걸고 버그가 발생할 때까지 디버깅하는 방법 말곤 없었다.(노가다) 결국 원인을 발견하긴 했다.

 

 

 

 

 현재 게임 접속 시 비동기로 백엔드 스프레드 시트에서 퀘스트 및 대화 스크립트 대화를 받아오고 동시에 소켓 서버로부터 플레이어의 퀘스트 진행 상황을 받아오게 해놔서 퀘스트 및 NPC 초기화를 위해 총 3가지 비동기 작업이 완료될 필요가 있다. 이중 하나라도 완료가 안 되었는데 게임 내의 NPC 오브젝트 초기화를 시도하면 버그가 발생한다.

 

 

 

 이 부분은 분명히 작업할 때 command pattern을 이용해서 그런 일이 없도록 막아놨는데도 가끔 버그가 났다.

 

 

 

public static void S_QuestListHandler(PacketSession session, IMessage packet)
{
	S_QuestList questList = packet as S_QuestList;

	Debug.Log($"{questList.ToString()}");

	Managers.Quest.Clear();


	foreach (QuestInfo questInfo in questList.Quests)
	{
		// 나머지 퀘스트 세부정보는 퀘스트 최종 초기화에서 처리
		Quest quest = new Quest()
		{
			ID = questInfo.TemplateId,
			IsCleared = questInfo.IsCleared,
			IsRewarded = questInfo.IsRewarded
		};

		Managers.Quest.AddQuestRegister(quest);

	}

	Managers.Quest.IsRecievedPlayerQuestsByServer = true;
	Managers.Quest.FlushQuestDialogueTaskQueue();
}

 

 

 현재 서버로부터 퀘스트 진행상황을 전달 받는 핸들러 부분 코드이다. Managers.Quest.AddQuestRegister(quest); 이 부분에서 서버로부터 받은 정보를 퀘스트 객체로 만들어 플레이어의 퀘스트 리스트에 추가한다. 그러고 나서 퀘스트와 연동된 스크립트들을 처리하도록 아랫줄에 코드를 작성하였다.

 

 

 

 

 근데 이 부분에서 플레이어의 퀘스트 리스트에 서버로부터 받은 퀘스트를 추가 하였는데도 NPC의 스크립트가 그에 따라 변하질 않았다. 분명 서버로부터 플레이어의 퀘스트 리스트도 제대로 받았는데 말이다. 디버깅을 하면서 보니 NPC의 스크립트를 초기화하는 과정에서 플레이어의 퀘스트 리스트에 서버로 부터 받은 퀘스트 정보가 들어가 있지 않은 것을 확인하였다. 알고 보니 AddQuestRegister() 함수에서 작업 처리를 등록만 하는 함수라서 이것이 언제 처리 될 지 모르는데 동시에 NPC 스크립트 초기화를 진행해 버린것이다. 

 

 

 

 

 

 그러니까 운이 좋아서 플레이어 퀘스트 리스트 초기화가 빨리 진행되면 NPC 스크립트 초기화도 정상작동 하고 그게 아니라면 지금처럼 버그가 나는 것이었다. 난 분명 이런 부분을 전부 처리했다고 생각했는데도 불구하고 비동기 처리가 제대로 되질 않았던 것이다. 

 

 

 

 

public void FlushQuestQueue()
{
    // 퀘스트 초기화가 필요한 퀘스트가 쌓인 경우 처리
    foreach (Quest quest in quests)
    {
        // 퀘스트가 아직 초기화되지 않았다면 퀘스트 정보를 주입
        if(quest.Name == null)
            quest.SetQuestData(Managers.Quest.GetQuestById(quest.ID));


        // 실제 퀘스트 추가
        PlayerQuestsAdd(quest);


        // UI 갱신
        UI_LobbyScene lobbySceneUI = Managers.UI.SceneUI as UI_LobbyScene;
        UI_Quest questUI = lobbySceneUI.QuestUI;
        questUI.RefreshUI();

    }

    if (IsAddedPlayerQuests == false)
        IsAddedPlayerQuests = true;


    FlushQuestDialogueTaskQueue();

}

public void QuestDialogueTaskRegister(DialougeTask dialougeTask)
{
    lock (_lock)
    {
        _questDialogueTasks.Enqueue(dialougeTask);
        
        if (IsRecievedPlayerQuestsByServer && IsAddedPlayerQuests) // 퀘스트 정보를 서버로 부터 받아 초기화 했다면
        {
            FlushQuestDialogueTaskQueue();
        }
    }
}

 

 

 그래서 IsAddedPlayerQuests 라는 bool값을 하나 더 추가해서 플레이어 퀘스트 정보가 완전히 초기화 된 후에 NPC 스크립트를 초기화 할 수 있도록 코드를 수정해주었다.

 

 

 

 

 

 문제의 해결 자체는 어려운게 아니지만 원인을 찾는 게 쉽지가 않았다. 디버깅 할 때마다 발생을 해주면 고맙겠지만, 몇 번을 리트라이해야 한번 버그가 나주니까 잡아내는게 어려웠던거 같다.