원시자료형들은 memcpy를 사용해 보내도 잘 수신이 되지만,
클래스를 보내게 되면 해당 클래스 내부의 함수들도 함수 포인터를 통해
보내지는데 수신자의 가상함수테이블이 송신자의 가상함수테이블과 같다는 보장이 없다.
따라서 메모리접근예외가 발생하거나 엉뚱한 위치의 코드를 실행하게될수도 있어 안전하지 않다.
여러 바이트로 이뤄진 숫자를 저장할 때, 플랫폼마다 그 방식이 다르다.
해당 플랫폼이 바이트를 어떤 순서로 저장하는지 그 방식을 플랫폼의 엔디언이라고 하고,
빅 엔디언, 리틀 엔디언으로 구별된다.
드문드문 채워져 있거나, 완전히 안 채워진 자료구조를 최적화 하며 압축을 하는 방식이다.
이 책의 예제같은 경우에는 mName을 128바이트 배열로 설정해두었는데 string값으로 설정후 strlen을 이용하는 식으로 메모리를 줄일 수 있다.
포인터 포함한 객체를 직렬화하는 경우는 두 가지로,
임베딩(인라이닝) 또는 링킹이 그것이다.
엔트로피 인코딩이란 출현 데이터의 예측 가능성 정도가 얼마나 높고 낮나에 따라 압축률이 달라지는 이론이다.
예를 들어 고양이가 대다수의 시간을 땅에서 보내는데
고양이의 높이값으로 계속 4바이트 값을 전송하는 건 메모리 낭비다.
따라서 cpu의 연산을 좀 사용하더라도 고양이 높이를 비교연산을 통해 0이 아닐때만 높이를 전송하는 식으로 구현하면 절약이 가능하다.
소수점의 자리를 요구한다고 전부 float값을 할당해서 전송해버리면 낭비되는 비트가 생길것이다. 따라서 게임의 수치의 가능 범위 및 요구 정밀도를 파악한 후, 해당 정밀도에 맞게 고정소수점 방식으로 변환하면 절약할 수 있다.
이 책의 예처럼 월드 크기가 4천x4천이고, 월드 중심이 0,0,0이며, 클라이언트 좌표이동은 0.1단위의 정밀도면 충분하다고 했을 때, x축이 가질수있는 값의 최대는 (2000-(-2000))/0.1 +1 으로
40001개의 값만 있으면 표현할 수 있다.
WriteBits()함수는 리틀 엔디언에서만 작동을 하는데,
이유는 바이트를 0번부터 기록하기 때문이다.
빅 엔디언환경에서는 최상위 바이트부터 기록하므로 반대가 되어야한다.
빅엔디언 환경에서도 작동하려면 해당 플랫폼의 엔디언 환경을 체크한 후 빅엔디언이라면
바이트 스왑함수를 이용해 스왑해준 후 writeBits함수를 사용하는식으로 해야한다.
void MemoryOutputStream::Write(const unordered_map<int,int>& inMap)
{
//inMap사이즈만큼 할당해준 후,
size_t elemCnt = inMap.size();
//사이즈 Write
Write(elemCnt);
//Map안의 pair에 대해 write연산 진행
for (const auto& iter : inMap) {
Write(iter.first);
Write(iter.second);
}
}
void MemoryOutputStream::Read(unordered_map<int, int>& outMap) {
size_t elemCnt;
//사이즈 할당 후,
Read(elemCnt);
//사이즈만큼 크기 조정해주고
outMap.reserve(elemCnt);
//각 first값과 second값을 Read연산을 통해 저장
for (const auto& iter : outMap) {
Read(iter.first);
Read(iter.second);
}
}
template<typename tKey, typename tValue >
void MemoryOutputStream::Read(unordered_map<tKey, tValue>& outMap) {
size_t elemCnt;
Read(elemCnt);
outMap.reserve(elemCnt);
for (const auto& iter : outMap) {
Read(iter.first);
Read(iter.second);
}
}