지난시간까지 job에 대한 내용들을 다뤄봤다.

 

이번시간에는 어느새 부터인가 GameSession의 소멸자가 뜨지 않는데, 어디선가 메모리 릭이 있다는 얘기니까 이것부터 해결해보자.

class GameSession : public PacketSession
{
public:
	~GameSession()
	{
		cout << "~GameSession" << endl;
	}

	virtual void OnConnected() override;
	virtual void OnDisconnected() override;
	virtual void OnRecvPacket(BYTE* buffer, int32 len) override;
	virtual void OnSend(int32 len) override;

public:
	Vector<PlayerRef> _players;
};

class Player
{
public:
	uint64					playerId = 0;
	string					name;
	Protocol::PlayerType	type = Protocol::PLAYER_TYPE_NONE;
	GameSessionRef			ownerSession; // TODO cycle problem
};

게임세션의 벡터에서 플레이어를 들고있는데, 플레이어에서 또 게임세션을 들고있으면서 발생하는 문제. (사이클)

또 Room에도 PlayerRef를 들고있다.

 

player와 GameSession, Room에서 서로의 레퍼런스를 갖고있기 때문에 생기는 일.

사용이 종료되면 모두 놓아주도록 만들자.

bool Handle_C_ENTER_GAME(PacketSessionRef& session, Protocol::C_ENTER_GAME& pkt)
{
	GameSessionRef gameSession = static_pointer_cast<GameSession>(session);

	uint64 index = pkt.playerindex();
	//TODO 유효성 검사
	gameSession->_currentPlayer = gameSession->_players[index];
	gameSession->_room = GRoom;

	//GRoom.PushJob(MakeShared<EnterJob>(GRoom, player)); // 일감만 예약해줌
	GRoom->DoAsync(&Room::Enter, gameSession->_currentPlayer);

	// 이 아랫부분을 실행해주는게 좀 이상하지만 일단은 놔둠
	Protocol::S_ENTER_GAME enterGamePkt;
	enterGamePkt.set_success(true);
	auto sendBuffer = ClientPacketHandler::MakeSendBuffer(enterGamePkt);
	gameSession->_currentPlayer->ownerSession->Send(sendBuffer);

	return true;
}

 

class GameSession : public PacketSession
{
public:
	~GameSession()
	{
		cout << "~GameSession" << endl;
	}

	virtual void OnConnected() override;
	virtual void OnDisconnected() override;
	virtual void OnRecvPacket(BYTE* buffer, int32 len) override;
	virtual void OnSend(int32 len) override;

public:
	Vector<PlayerRef> _players;

	PlayerRef _currentPlayer;
	weak_ptr<class Room> _room;
};

void GameSession::OnDisconnected()
{
	GSessionManager.Remove(static_pointer_cast<GameSession>(shared_from_this()));
	if (_currentPlayer)
	{
		if (auto room = _room.lock())
			room->DoAsync(&Room::Leave, _currentPlayer);
	}
	
	_currentPlayer = nullptr;
	_players.clear();
}
bool Handle_C_ENTER_GAME(PacketSessionRef& session, Protocol::C_ENTER_GAME& pkt)
{
	GameSessionRef gameSession = static_pointer_cast<GameSession>(session);

	uint64 index = pkt.playerindex();
	//TODO 유효성 검사
	gameSession->_currentPlayer = gameSession->_players[index];
	gameSession->_room = GRoom;

	//GRoom.PushJob(MakeShared<EnterJob>(GRoom, player)); // 일감만 예약해줌
	GRoom->DoAsync(&Room::Enter, gameSession->_currentPlayer);

	// 이 아랫부분을 실행해주는게 좀 이상하지만 일단은 놔둠
	Protocol::S_ENTER_GAME enterGamePkt;
	enterGamePkt.set_success(true);
	auto sendBuffer = ClientPacketHandler::MakeSendBuffer(enterGamePkt);
	gameSession->_currentPlayer->ownerSession->Send(sendBuffer);

	return true;
}

JobQueue에다가 DoAsync를 만들어 처리하고있는데 아쉬운 점이 있다.

스킬이건 AI건 무한대로 틱을 돌면서 실행하는 경우는 많이 없다.

클라이언트는 랜더링때문에 그렇게 하겠지만 서버 입장에서는 1초에 몇백번씩 한 오브젝트를 갱신할 이유가 없다.

보통은 1~2초단위로 주기적으로 체크만 하면서 객체를 업데이트 하면서로직을 실행시키는게 일반적.

길찾기, 이동, 데미지판별 등등 결국에는 빠르게 연산할 필요는 없다는 것이다.

 

그런데 지금은 그렇게 처리하고 있지 않다.

 

일반적으로 쿨타임같은건 어떻게 처리할까?

현재 시간을 체크 -> 끝나는 시간 체크

while (true)에서 현재시간이 끝나는시간보다 큰지 확인하고 지났으면 1초가 경과됐다. 할것이다.

그런데 서버에서는 객체가 수십 수백만개기 때문에 무의미하게 while true를 도는건 말도 안된다.

그래서 n초후에 실행이 되는 그런게 필요하다. (대부분의 게임엔진에 있는 개념)

마찬가지로 예약할 수 있는 시스템이 있어야한다.

 

지금까지는 일감을 즉흥적으로 만들어서 꼿아주는 식으로만 작업하고있었다.

그런데 이제 만약 일감을 예약해야한다면 지금 당장은 실행할 필요가 없고 1초 후에 jobqueue에 넣어줘야한다면 로직이 달라져야 할것이다. 그래서 이 예약시스템도 만드는 방법이 다양하다.

 

오늘 해볼 방법은 중앙통제를 이용하는 방법인다.

만약 기존 방법처럼 jobqueue에 예약을 한다고 하자. 그러면 언제 job이 작동해야하는지 모르기때문에 계속 확인해야할것이다.

그래서 그냥 중앙 시슽템에서 모든 예약된 일감들을 가지고있다가 빠르게 체크해서 뿌려주는(?) 방식으로 만들어 본다.

 

JobTimer 추가

 

jobqueue의 push가 기존에는 1빠로 들어왔으면 실행하는것까지 담당해왔다.

지금은 배분하는 역할만 하기위해서 push에 bool인자를 추가해 execute를 하지 않을 수 있는 스위치를 만들어두자.

 

void JobQueue::Push(JobRef job, bool pushOnly)
{
	const int32 prevCount = _jobCount.fetch_add(1);
	_jobs.Push(job);

	if (prevCount == 0)
	{
		if (LCurrentJobQueue == nullptr && pushOnly == false)
		{
			Execute();
		}
		else
		{
			//여유 있는 다른 쓰레드가 실행하도록 GlobalQueue에 넘긴다.
			GGlobalQueue->Push(shared_from_this());
		}
	}
}

 

코어글로벌에 jobtimer 추가.

 

jobqueue 클래스에 ToTimer 추가해서 타이머들은 JobTimer에 등록하도록 만들기

 

그리고 이걸 처리하는 부분은 쓰레드매니저에 아래 퍼블릭 함수 추가.

void ThreadManager::DistributeReservedJobs()
{
	const uint64 now = ::GetTickCount64();

	GJobTimer->Distribute(now);
}

 

 

void DoWorkerJob(ServerServiceRef& service)
{
	while (true)
	{
		LEndTickCount = ::GetTickCount64() + WORKER_TICK;
		
		// 네트워크 입출력 처리 -> 인게임 로직까지 (패킷 핸들러에 의해)
		service->GetIocpCore()->Dispatch(10);

		// 예약된 일감 처리
		ThreadManager::DistributeReservedJobs();

		// 글로벌 큐
		ThreadManager::DoGlobalQueueWork();
	}
}

 

 

'강의 수강 > 게임서버(1)' 카테고리의 다른 글

77. DB Bind  (0) 2025.09.05
76. DB Connection  (0) 2025.09.01
74. JobQueue #5  (0) 2025.08.25
73. JobQueue #4  (0) 2025.08.23
강의를 듣고 다시 확인할 것들 모음  (0) 2025.08.23

+ Recent posts