KoreanFoodie's Study
[C++ 게임 서버] 5-5. 패킷 직렬화 #2 본문
[C++ 게임 서버] 5-5. 패킷 직렬화 #2
핵심 :
1. 가변 데이터를 처리할 수 있는 PacketList 를 만들면, 버퍼를 읽을 때 임시 객체를 생성하지 않고 캐스팅을 통해 데이터를 효과적으로 읽어들일 수 있다.
자, 일단 저번 시간에는 가변 데이터 형식인 buffs 에 대해 패킷 직렬화를 수행했었다. 그런데 코드를 잘 보면, 조금 마음에 들지 않는 부분이 있다.
vector<PKT_S_TEST::BuffsListItem> buffs;
buffs.resize(pkt.buffsCount);
for (int32 i = 0; i < pkt.buffsCount; i++)
br >> buffs[i];
바로 buffs 데이터를 읽을 때 임시 객체가 생성되는 것인데... 생각해보면 굳이 그럴 필요가 있나 싶다. 🤔
이 부분을 최적화하기 위해, 이번 글에서는 일단 ClientPacketHandler 쪽에서 buffs 를 읽는 부분을 수정할 것이다.
먼저, ClientPacketHandler 의 헤더 쪽에 리스트를 다룰 수 있는 템플릿 클래스를 하나 만들어 준다.
template<typename T>
class PacketList
{
public:
PacketList() : _data(nullptr), _count(0) { }
PacketList(T* data, uint16 count) : _data(data), _count(count) { }
T& operator[](uint16 index)
{
ASSERT_CRASH(index < _count);
return _data[index];
}
uint16 Count() { return _count; }
// ranged-base for 지원
PacketIterator<T, PacketList<T>> begin() { return PacketIterator<T, PacketList<T>>(*this, 0); }
PacketIterator<T, PacketList<T>> end() { return PacketIterator<T, PacketList<T>>(*this, _count); }
private:
T* _data;
uint16 _count;
};
내용을 보면, 템플릿을 사용하고 있긴 하지만 실상 data 와 count 만 주의 깊게 보면 된다. 즉 _data 에는 읽으려는 버퍼의 시작점이 들어가고, _count 에는 버퍼에 들어간 가변 길이 데이터에서의 요소 갯수가 들어갈 것이다.
참, ranged-base for 를 지원하기 위해 PacketList 를 위한 PacketIterator 도 아래와 같이 만들어 주자 :
template<typename T, typename C>
class PacketIterator
{
public:
PacketIterator(C& container, uint16 index) : _container(container), _index(index) { }
bool operator!=(const PacketIterator& other) const { return _index != other._index; }
const T& operator*() const { return _container[_index]; }
T& operator*() { return _container[_index]; }
T* operator->() { return &_container[_index]; }
PacketIterator& operator++() { _index++; return *this; }
PacketIterator operator++(int32) { PacketIterator ret = *this; ++_index; return ret; }
private:
C& _container;
uint16 _index;
};
그럼 PKT_S_TEST 에서, buffs 를 처리했던 부분을 다음과 같이 조금 수정해 준다 :
#pragma pack(1)
// [ PKT_S_TEST ][BuffsListItem BuffsListItem BuffsListItem]
struct PKT_S_TEST
{
struct BuffsListItem
{
uint64 buffId;
float remainTime;
};
/** ... */
using BuffsList = PacketList<PKT_S_TEST::BuffsListItem>;
BuffsList GetBuffsList()
{
BYTE* data = reinterpret_cast<BYTE*>(this);
data += buffsOffset;
return BuffsList(reinterpret_cast<PKT_S_TEST::BuffsListItem*>(data), buffsCount);
}
//vector<BuffData> buffs;
//wstring name;
};
#pragma pack()
이제 BuffList 타입은, PacketList 의 PKT_S_TEST::BuffsListItem 타입에 대한 특수화 버전으로 나가게 된다.
그럼 이제 Handle_S_TEST 는 다음과 같이 변하게 되는데 :
// [ PKT_S_TEST ][BuffsListItem BuffsListItem BuffsListItem]
void ClientPacketHandler::Handle_S_TEST(BYTE* buffer, int32 len)
{
BufferReader br(buffer, len);
PKT_S_TEST* pkt = reinterpret_cast<PKT_S_TEST*>(buffer);
if (pkt->Validate() == false)
return;
//cout << "ID: " << id << " HP : " << hp << " ATT : " << attack << endl;
// 예전 방식. 임시 벡터가 필요했었다.
vector<PKT_S_TEST::BuffsListItem> buffs;
// 지금 방식. 이제 pkt 의 주소를 바로 읽어 캐스팅해서 사용하므로, 임시 객체가 필요 없다.
PKT_S_TEST::BuffsList buffs = pkt->GetBuffsList();
cout << "BufCount : " << buffs.Count() << endl;
for (int32 i = 0; i < buffs.Count(); i++)
cout << "BufInfo : " << buffs[i].buffId << " " << buffs[i].remainTime << endl;
for (auto it = buffs.begin(); it != buffs.end(); ++it)
cout << "BufInfo : " << it->buffId << " " << it->remainTime << endl;
for (auto& buff : buffs)
cout << "BufInfo : " << buff.buffId << " " << buff.remainTime << endl;
}
무엇이 달라진 것일까? 바로 buffs 를 읽고 저장하는 방식이 바뀌었다! 이제는 buffs 를 위한 벡터를 따로 임시로 만들어 줄 필요 없이, 패킷의 버퍼를 그대로 읽어서 캐스팅만 적절히 취해줌으로써 Count 를 이용한 for loop 과 range-based for loop 까지 사용할 수 있게 된 것을 확인할 수 있다 😄
'Game Dev > Game Server' 카테고리의 다른 글
[C++ 게임 서버] 5-7. Protobuf (0) | 2023.12.16 |
---|---|
[C++ 게임 서버] 5-6. 패킷 직렬화 #3 (0) | 2023.12.16 |
[C++ 게임 서버] 5-4. 패킷 직렬화 #1 (0) | 2023.12.15 |
[C++ 게임 서버] 5-3. Unicode (0) | 2023.12.15 |
[C++ 게임 서버] 5-2. PacketHandler (0) | 2023.12.15 |