프로젝트 : https://github.com/HoonInPark/ServerHyperion.git
본 포스트에 대한 내용은 feat/framesync
브랜치에 있다.
아프다.
어제 여섯시간 내리 앉아서 머리가 멍할 정도로 집중하고 나니 김기 몸살이 났다.
나 수능 공부할 때 이후로 이 정도로 집중해 본 적이 없었다.
누워서 쉬려고 할 때도 고쳐야 할 점들이 자꾸 머릿속에서 맴돈다.
영화 듄에 나오는 인간 컴퓨터 아저씨가 된 기분이다.
"무례하긴, 순회야" - 객체 풀과 순회 인터페이스 구현하기에서 내부 멤버를 deque
로 변경했다.
근데 deque
는 반복적으로 푸시/팝을 할 경우 동적할당이 일어난다.
누구인가, 누가 런타임에 동적할당을 하였어?
순환큐를 만들고 deque
대신 ObjPool
의 멤버로 바꿔 넣어주자.
순환큐를 직접 만들 순 있지만, 귀찮다.
예전에 알고리즘 공부하며 만든 게 있지만, 못 미덥다 -> (못 미더운 코드)
그래서 Unreal에서 사용하는 TCircularQueue
를 떼오려 한다.
핵심 아이디어는 TCircularQueue
내의 TCircularBuffer
에서 쓰는 TArray
를 vector
로 바꾸는 거다.
template<typename InElementType>
class StlCircularBuffer
{
public:
using ElementType = InElementType;
// ...
private:
uint32 IndexMask;
vector<ElementType> Elements;
};
그리고 초기화되기 전에 빈 값을 가질 수 있도록 StlCircularQueue
와 StlCircularBuffer
의 생성자를 고쳐줬다.
StlCircularBuffer(uint32 Capacity = 0)
{
if (Capacity < 0) return;
if (Capacity > 0xffffffffU) return;
Elements.resize(bit_ceil(Capacity));
fill(Elements.begin(), Elements.end(), ElementType()); // TypeName{} makes it with its default constructor
IndexMask = Elements.size() - 1;
}
template<class ...P>
StlCircularBuffer(uint32 Capacity = 0, P&&... _Params)
{
if (Capacity > 0xffffffffU) return;
Elements.resize(bit_ceil(Capacity));
fill(Elements.begin(), Elements.end(), ElementType(forward<P>(_Params)...));
IndexMask = Elements.size() - 1;
}
원래 TCircularQueue
는 기본생성자가 정의되지 않았었다.
그리고 다른 클래스의 멤버 변수로 쓰일 경우, 생성자에서 초기화할 때 대입연산을 사용할 수 있도록 수정했다.
__forceinline StlCircularBuffer& operator=(const StlCircularBuffer& _InCirBuf)
{
if (this == &_InCirBuf) return *this;
Elements = _InCirBuf.Elements;
IndexMask = _InCirBuf.IndexMask;
return *this;
}
이제,
멤버로 그냥 ObjPool<stOverlappedEx> m_SendDataPool;
와 같이 선언하고
생성자에서 m_SendDataPool = ObjPool<stOverlappedEx>(64);
와 같이 해줄 수 있다.
m_SendDataPool
은 대입되기 전까진 깡통 객체인 것.
아래는 전체 완성 코드.
StlCircularBuffer.h
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#define SERVERHYPERION_EXPORT
#ifdef SERVERHYPERION_EXPORT
#define SERVERHYPERION_API __declspec(dllexport)
#else
#define SERVERHYPERION_API __declspec(dllimport)
#endif
#include <iostream>
#include <vector>
#include <bit>
#include <windows.h>
#include <utility>
using namespace std;
typedef unsigned int uint32;
template<typename InElementType>
class StlCircularBuffer
{
public:
using ElementType = InElementType;
StlCircularBuffer(uint32 Capacity = 0)
{
if (Capacity < 0) return;
if (Capacity > 0xffffffffU) return;
Elements.resize(bit_ceil(Capacity));
fill(Elements.begin(), Elements.end(), ElementType()); // TypeName{} makes it with its default constructor
IndexMask = Elements.size() - 1;
}
template<class ...P>
StlCircularBuffer(uint32 Capacity = 0, P&&... _Params)
{
if (Capacity > 0xffffffffU) return;
Elements.resize(bit_ceil(Capacity));
fill(Elements.begin(), Elements.end(), ElementType(forward<P>(_Params)...));
IndexMask = Elements.size() - 1;
}
__forceinline StlCircularBuffer& operator=(const StlCircularBuffer& _InCirBuf)
{
if (this == &_InCirBuf) return *this;
Elements = _InCirBuf.Elements;
IndexMask = _InCirBuf.IndexMask;
return *this;
}
public:
__forceinline ElementType& operator[](uint32 Index)
{
return Elements[Index & IndexMask];
}
__forceinline const ElementType& operator[](uint32 Index) const
{
return Elements[Index & IndexMask];
}
public:
__forceinline uint32 Capacity() const
{
return Elements.size();
}
__forceinline uint32 GetNextIndex(uint32 CurrentIndex) const
{
return ((CurrentIndex + 1) & IndexMask);
}
__forceinline uint32 GetPreviousIndex(uint32 CurrentIndex) const
{
return ((CurrentIndex - 1) & IndexMask);
}
private:
uint32 IndexMask;
vector<ElementType> Elements;
};
StlCircularQueue.h
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#define SERVERHYPERION_EXPORT
#ifdef SERVERHYPERION_EXPORT
#define SERVERHYPERION_API __declspec(dllexport)
#else
#define SERVERHYPERION_API __declspec(dllimport)
#endif
#include <iostream>
#include <atomic>
#include "StlCircularBuffer.h"
using namespace std;
typedef unsigned int uint32;
typedef signed int int32;
template<typename T>
class StlCircularQueue
{
public:
using FElementType = T;
template <typename T>
__forceinline constexpr remove_reference_t<T>&& MoveTemp(T&& Obj) noexcept
{
using CastType = std::remove_reference_t<T>;
static_assert(is_lvalue_reference_v<T>, "MoveTemp called on an rvalue");
static_assert(!is_same_v<CastType&, const CastType&>, "MoveTemp called on a const object");
return (CastType&&)Obj;
}
template<class ...P>
StlCircularQueue(uint32 CapacityPlusOne = 0, P&&... _Params)
: Buffer(CapacityPlusOne, forward<P>(_Params)...)
, Head(0)
, Tail(0)
{
}
__forceinline StlCircularQueue& operator=(const StlCircularQueue& _InCirQ)
{
if (this == &_InCirQ) return *this;
Buffer = _InCirQ.Buffer;
Head.store(_InCirQ.Head.load());
Tail.store(_InCirQ.Tail.load());
return *this;
}
inline auto begin() const { return Buffer.begin(); }
inline auto end() const { return Buffer.end(); }
public:
uint32 Count() const
{
int32 Count = Tail.load() - Head.load();
if (Count < 0)
{
Count += Buffer.Capacity();
}
return (uint32)Count;
}
bool Dequeue(FElementType& OutElement)
{
const uint32 CurrentHead = Head.load();
if (CurrentHead != Tail.load())
{
OutElement = MoveTemp(Buffer[CurrentHead]);
Head.store(Buffer.GetNextIndex(CurrentHead));
return true;
}
return false;
}
bool Dequeue()
{
const uint32 CurrentHead = Head.load();
if (CurrentHead != Tail.load())
{
Head.Store(Buffer.GetNextIndex(CurrentHead));
return true;
}
return false;
}
void Empty()
{
Head.store(Tail.load());
}
bool Enqueue(const FElementType& Element)
{
const uint32 CurrentTail = Tail.load();
uint32 NewTail = Buffer.GetNextIndex(CurrentTail);
if (NewTail != Head.load())
{
Buffer[CurrentTail] = Element;
Tail.store(NewTail);
return true;
}
return false;
}
bool Enqueue(FElementType&& Element)
{
const uint32 CurrentTail = Tail.load();
uint32 NewTail = Buffer.GetNextIndex(CurrentTail);
if (NewTail != Head.load())
{
Buffer[CurrentTail] = MoveTemp(Element);
Tail.store(NewTail);
return true;
}
return false;
}
__forceinline bool IsEmpty() const
{
return (Head.load() == Tail.load());
}
bool IsFull() const
{
return (Buffer.GetNextIndex(Tail.load()) == Head.load());
}
bool Peek(FElementType& OutItem) const
{
const uint32 CurrentHead = Head.load();
if (CurrentHead != Tail.load())
{
OutItem = Buffer[CurrentHead];
return true;
}
return false;
}
const FElementType* Peek() const
{
const uint32 CurrentHead = Head.load();
if (CurrentHead != Tail.load())
{
return &Buffer[CurrentHead];
}
return nullptr;
}
private:
StlCircularBuffer<FElementType> Buffer;
atomic<uint32> Head;
atomic<uint32> Tail;
};