진행 기간: 2023/03/01 → 2023/08/13 스킬: C++, CMake
- ECS를 위한 부수적인 라이브러리 생성
- Log Project
- Memory Project
- 언어
- C++
- 엔진 상의 정보를 출력하기 위한 기능
- Log 정보들을 따로 저장시킬 수도 있어야 함
- 카테고리를 나눠 줄 필요가 있음
- Info : 일반적인 정보
- Warn : 런타임 에러까지는 아니지만, 주의해야 될 정보
- Error: 런타임 에러를 유발하거나, 예외처리가 되어진 정보
- 메모리 초기화 및 할당 작업을 효율적으로 처리하기 위함
- 여러 종류의 인스턴스를 중앙 관리하기 위함
- 스마트 포인터와 유사한 형태로 구현
- ECS 상에서 객체들의 포인터를 반환할 경우, 메모리 관련 많은 문제가 생길 수 있음
- Memory Manager에서만 소멸이 가능하도록 설정
- Log, Memory Project들을 라이브러리로 생성할 수 있는 CMakelists 설정
- Github에 해당 Repository를 생성하고, 연결하여 의존성 설정한 후 사용
- IMemoryPool
- MemoryPool( Parent : IMemoryPool )
- MemoryPtr
- MemoryManager
- Log
- IMemoryPool
-
MemoryPool를 사용하는 객체들을 위한 인터페이스
class IMemoryPool { public: IMemoryPool() {} virtual ~IMemoryPool() {} public: virtual void Init() = 0; virtual void Destroy() = 0; virtual bool CheckFull() = 0; virtual bool CheckEmpty() = 0; public : virtual const size_t& GetTotalSize() = 0; virtual const size_t& GetObjectSize() = 0; virtual char*& GetStartPtr() = 0; };
-
-
MemoryPool
- 주된 역할
- 실제로 메모리를 할당 및 해제하는 주된 객체
- 전체 메모리를 한꺼번에 할당 받으며, 초기화와 소멸자로 인스턴스를 관리
- 메모리 관련
-
MemoryPool에서 처음에 할당 받은 주소와 메모리 크기에 대한 오브젝트 크기의 인덱스 정보로 관리
- 예를 들어
- 할당 받은 주소 : 01
- 메모리 크기 : 8
- 오브젝트 사이즈 : 2
- 가능한 오브젝트 인덱스( 주소 ) : 0( 01 ), 1( 03 ), 2( 05 ), 3(07 )
- 예를 들어
-
처음 MemoryPool이 생성될 때, 주어진 할당 사이즈를 확인 후 할당 받음
-
void Init()
- malloc을 통하여 메모리 할당만 진행
- m_CanConstruct에 사용가능한 Index 기입
- m_CanConstruct
- 초기화시킬 수 있는 인스턴스들의 포인터에 대한 인덱스 정보를 지닌 큐
- m_CanDestruct
- 소멸시킬 수 있는 인스턴스들의 포인터에 대한 인덱스 정보를 지닌 큐
void Init() { m_pStart = static_cast<char*>( std::malloc( m_TotalSize ) ); for ( size_t I = 0; I < m_TotalSize / m_ObjectSize; I++ ) { m_CanConstruct.push( static_cast<int>( I ) ); } m_CanDestruct.clear(); m_CanDestruct.reserve( static_cast<int>( m_TotalSize / m_ObjectSize ) ); return; }
-
-
주어진 메모리를 MemoryPool이 소멸될 때까지 유지됨
-
- 인스턴스 관리 관련
- 생성과 소멸을 인스턴스의 포인터를 사용하여서 관리
-
m_CanConstruct
- 초기화시킬 수 있는 인스턴스들의 포인터에 대한 인덱스 정보를 지닌 큐
-
m_CanDestruct
- 소멸시킬 수 있는 인스턴스들의 포인터에 대한 인덱스 정보를 지닌 큐
-
생성 관련
- template< typename … Args >
T Construct( Args&&… args )*
- CheckFull()
- 해당 MemoryPool이 가득 찼는지 확인
- m_CanConstruct가 비었을 시, true
- 초기화 가능한 인덱스를 이용하여 위치지정 new를 호출
- CheckFull()
template< typename ... Args > T* Construct( Args&&... args ) { if ( CheckFull() ) { throw Except( " MemoryPool | %s | %s | This MemoryPool is full ", __FUNCTION__, typeid( T ).name() ); } int Index = m_CanConstruct.front(); m_CanDestruct.push_back( Index ); m_CanConstruct.pop(); T* Ptr = new ( GetStartPtr() + Index * GetObjectSize() ) T( std::forward<Args>( args ) ... ); Log::Info( " Instance | %s | %p | Create new ", typeid( T ).name(), Ptr ); return Ptr; }
- template< typename … Args >
T Construct( Args&&… args )*
-
소멸 관련
-
void Destruct( T& Ptr )*
- Ptr을 이용하여서, MemoryPool에서 소멸시킬 수 있는 Index 확인
- Index가 있을 경우
- 해당 인스턴스의 소멸자를 호출하고 nullptr 기입
- m_CanConstruct와 m_CanDestruct 최신화
void Destruct( T*& Ptr ) { int Index = static_cast<int>( ( reinterpret_cast<char*>( Ptr ) - GetStartPtr() ) / GetObjectSize() ); auto ITR = std::remove( m_CanDestruct.begin(), m_CanDestruct.end(), Index ); if ( ITR != m_CanDestruct.end() ) { Log::Info( " Instance | %s | %p | Delete ", typeid( T ).name(), Ptr ); m_CanDestruct.erase( ITR, m_CanDestruct.end() ); Ptr->~T(); Ptr = nullptr; m_CanConstruct.push( Index ); } else { throw Except(" Instance | %s | %p | This memorypool has not this instance ", typeid( T ).name(), Ptr ); } }
-
-
- 생성과 소멸을 인스턴스의 포인터를 사용하여서 관리
- 주된 역할
-
MemoryPtr
-
주된 역할
- 외부에서의 포인터 해제에 대한 보호역할 및 캐스팅을 위한 용도
-
포인터 관련
-
포인터를 해제할 수 있는 역할은 오직 MemoryManager
- template< typename U >
friend class MemoryPtr;
- 다른 템플릿 MemoryPtr에서도 서로 private 멤버 변수에 접근할 수 있도록
private : friend class MemoryManager; template< typename U > friend class MemoryPtr;
- template< typename U >
friend class MemoryPtr;
-
RTTI를 통한 캐스팅이 가능
template< typename U > bool CheckValidityofCopy( U*& otherPtr ) { if ( otherPtr == nullptr ) { Log::Warn( " MPTR | %s | %s | The pointer is empty ", __FUNCTION__, typeid( T ).name() ); return false; } if ( m_Ptr != nullptr ) { Log::Warn( " MPTR | %s | %s | Already pointer existed %p", __FUNCTION__, typeid( T ).name(), m_Ptr ); return false; } T* mainPtr = dynamic_cast< T* >( otherPtr ); if ( mainPtr == nullptr ) { Log::Warn( " MPTR | %s | %s | No inheritance relationship with %s ", __FUNCTION__, typeid( T ).name(), typeid( U ).name() ); return false; } m_Ptr = mainPtr; return true; }
-
-
MemoryPool 관련
- MemoryPool의 Start Pointer를 지니고 있어서 MemoryManager에서 관리가 편리하기 위함
public : char* GetPoolPtr() { return m_PoolPtr; } void SetPoolPtr( char* Start ) { bool Check = ( Start != m_PoolPtr ) || ( Start != nullptr ); if ( Check ) m_PoolPtr = Start; } private : T* m_Ptr = nullptr; char* m_PoolPtr;
-
- MemoryManager
-
주된 역할
- MemoryPool을 생성 및 삭제하는 역할
- 인스턴스를 초기화 및 소멸하는 역할
-
MemoryPool 관련
-
접근 방식
-
Start Pointer로 MemoryPool에 접근
-
Start Pointer의 리스트는 Type별로 저장
using IMemoryPoolPtrList = std::list< char* >; using IMemoryPoolMap = std::unordered_map< char*, IMemoryPool* >; using TypePoolPtrListMap = std::unordered_map< const std::type_info*, IMemoryPoolPtrList >; IMemoryPoolMap m_IMemoryPoolMap; TypePoolPtrListMap m_TypePoolPtrListMap;
-
-
생성 관련
-
template< typename T > void CreateMemoryPool( size_t Size )
- m_IMemoryPoolMap
- Start Pointer을 사용하여 IMemoryPool에 접근하기 위한 자료
- Key : Start Pointer
- Value : IMemoryPool Pointer
- m_TypePoolPtrListMap
- Type 데이터를 이용하여 Start Pointer들 데이터들에 접근하기 위한 자료
- Key : const std::type_info*
- Value : Start Pointer Set
template< typename T > void CreateMemoryPool( size_t Size ) { IMemoryPool* iMemoryPool = new MemoryPool<T>( Size ); iMemoryPool->Init(); m_IMemoryPoolMap[ iMemoryPool->GetStartPtr() ] = iMemoryPool; m_TypePoolPtrListMap[ &typeid( T ) ].push_back( iMemoryPool->GetStartPtr() ); Log::Info( " MemoryManager | %s | Pool Ptr %p - Start Ptr %p | Create new memory pool ", typeid( T ).name(), iMemoryPool, iMemoryPool->GetStartPtr() ); }
- m_IMemoryPoolMap
-
-
-
인스턴스 관련
-
생성 관련
-
하나의 MemoryPool에 하나의 인스턴스만 생성할지, 여러개를 생성할지 설정 가능
-
template< typename T, typename… Args > MemoryPtr Create( Args&&… args )
- 여러 개의 인스턴스를 생성
-
template< typename T, typename… Args > MemoryPtr CreateOne( Args&&… args )
- 하나의 인스턴스만을 생성
template< typename T, typename... Args > MemoryPtr<T> Create( Args&&... args ) { size_t Size = m_DefaultSize; return MainCreate<T>( Size, std::forward<Args>( args ) ... ); } template< typename T, typename... Args> MemoryPtr<T> CreateOne( Args&&... args ) { size_t Size = sizeof( T ); return MainCreate<T>( Size, std::forward<Args>( args) ... ); }
-
-
IMemoryPool list에서 초기화 가능한 MemoryPool을 찾아서 인스턴스 생성 진행
-
template< typename T, typename… Args > MemoryPtr MainCreate( size_t Size, Args&&… args )
- HasList()
- 해당 T 타입에 대한 IMemoryPool List가 있는지 확인
- CreateList()
- 해당 T 타입에 대한 IMemoryPool List 생성
- GetList()
- 해당 T 타입에 대한 IMemoryPool 포인터를 지닌 List의 레퍼런스 호출
- GetMemoryPool( char Start )*
- 해당 T 타입의 Start Pointer를 지닌 IMemoryPool Pointer 반환
- IMemoryPool List를 끝에서부터 호출하며, 생성 가능 여부 확인
- 전부 불가능
- 전체의 IMemoryPool이 가득 찼을 시, 새로운 MemoryPool 생성
- MainCreate 함수를 재귀적으로 호출
- 가능
- MemoryPool을 호출 및 객체 생성
- 전부 불가능
template< typename T, typename... Args > MemoryPtr<T> MainCreate( size_t Size, Args&&... args ) { if ( !HasList<T>() ) CreateList<T>( Size ); IMemoryPoolPtrList& PoolPtrList = GetList<T>(); for ( auto ITR = PoolPtrList.rbegin(); ITR != PoolPtrList.rend(); ITR++ ) { MemoryPool<T>* memoryPool = GetMemoryPool<T>( *ITR ); if ( memoryPool->CheckFull() ) continue; else { MemoryPtr<T> mPtr = memoryPool->Construct( std::forward<Args>( args ) ... ); mPtr.SetPoolPtr( memoryPool->GetStartPtr() ); return mPtr; } } CreateMemoryPool<T>( Size ); return MainCreate<T>( Size, std::forward<Args>( args ) ... ); }
- HasList()
-
-
-
삭제 관련
-
MemoryPtr안에 있는 MemoryPool의 Start Ptr 멤버 변수를 통하여서 삭제
- template< typename T >
void Delete( MemoryPtr& mPtr )
- GetPoolPtr()
- 해당 MemoryPtr의 Ptr가 있는 MemoryPool의 Start Ptr 반환
- HasMemoryPool( char Start )*
- 해당 T 타입에서 Start의 Start Pointer를 갖는 MemoryPool 확인
- GetPoolPtr()
template< typename T > void Delete( MemoryPtr<T>& mPtr ) { if ( !HasList<T>() ) return; char* PoolPtr = mPtr.GetPoolPtr(); bool Check = HasMemoryPool<T>( PoolPtr ); if ( Check ) { MemoryPool<T>* memoryPool = GetMemoryPool<T>( PoolPtr ); memoryPool->Destruct( mPtr.GetPtr() ); if ( memoryPool->CheckEmpty() ) DeletePool<T>( PoolPtr ); } }
- template< typename T >
void Delete( MemoryPtr& mPtr )
-
-
-
-
설정 관리
-
BUILD_STATIC_LIBRARY
- true : 정적 라이브러리
- false : 동적 라이브러리
-
INSTALL_DEMO_FILE
- true : Demofile 설치
- false : Demofile 미설치
OPTION( BUILD_STATIC_LIBRARY "Set option for the library which is static or dynamic" OFF ) OPTION( INSTALL_DEMO_FILE "Install demofile for memorymanager" ON ) ## Set Option for install lib, header files' path SET( PREFIX_DIR ${MAIN_DIR}/build/Prefix CACHE PATH "Prefix Path" ) SET( INCLUDE_DIR ${MAIN_DIR}/build/include CACHE PATH "Header files Path" ) SET( ARC_DIR ${MAIN_DIR}/build/arc CACHE PATH "Archieve files Path" ) SET( LIB_DIR ${MAIN_DIR}/build/lib CACHE PATH "Library files Path" ) SET( BIN_DIR ${MAIN_DIR}/build/bin CACHE PATH "Execute files Path" ) SET( CMAKE_BUILD_TYPE Debug CACHE STRING "Build Type" )
-
-
연결 관리
-
Log Project
#### Log ----------------------------------- # MESSAGE( STATUS "Log Project - Linking ... ") EXTERNALPROJECT_ADD( LogProject GIT_REPOSITORY GIT_REPOSITORY https://github.com/Winteradio/Log.git GIT_TAG "v2.2.0" PREFIX ${PREFIX_DIR}/LogProject UPDATE_COMMAND "" PATCH_COMMAND "" TEST_COMMAND "" INSTALL_COMMAND "" CMAKE_ARGS -DINCLUDE_DIR=${INCLUDE_DIR} -DLIB_DIR=${LIB_DIR} -DBIN_DIR=${BIN_DIR} -DARC_DIR=${ARC_DIR} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DBUILD_STATIC_LIBRARY=${BUILD_STATIC_LIBRARY} -DINSTALL_DEMO_FILE=OFF ) LIST( APPEND DEP_INCLUDE ${INCLUDE_DIR} ) LIST( APPEND DEP_LIST LogProject ) LIST( APPEND DEP_LIBS ${ARC_DIR}/LogProject.${STATIC_LIB} ) MESSAGE( STATUS "Log Project - Done") #### Log ----------------------------------- #
-
Memory Project
#### Memory Pool ----------------------------------- # MESSAGE(STATUS "Memory Project - Linking ...") EXTERNALPROJECT_ADD( MemoryProject GIT_REPOSITORY GIT_REPOSITORY https://github.com/Winteradio/MemoryPool.git GIT_TAG "v4.2.0" PREFIX ${PREFIX_DIR}/MemoryProject UPDATE_COMMAND "" PATCH_COMMAND "" TEST_COMMAND "" INSTALL_COMMAND "" CMAKE_ARGS -DPREFIX_DIR=${PREFIX_DIR} -DINCLUDE_DIR=${INCLUDE_DIR} -DLIB_DIR=${LIB_DIR} -DBIN_DIR=${BIN_DIR} -DARC_DIR=${ARC_DIR} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DBUILD_STATIC_LIBRARY=${BUILD_STATIC_LIBRARY} -DINSTALL_DEMO_FILE=${INSTALL_DEMO_FILE} -DINSTALL_MEMORY_MANAGER=ON ) LIST( APPEND DEP_INCLUDE ${INCLUDE_DIR} ) LIST( APPEND DEP_LIST MemoryProject ) LIST( APPEND DEP_LIBS ${ARC_DIR}/MemoryProject.${STATIC_LIB} ) LIST( APPEND DEP_LIBS ${ARC_DIR}/LogProject.${STATIC_LIB} ) MESSAGE(STATUS "Memory Project - Done") #### MemoryPool ----------------------------------- #
-
-
실행 코드
void Example() { MemoryManager::GetHandle().Init(); MemoryManager::GetHandle().SetDefaultSize( 32 ); MemoryPtr<Object> Value = MemoryManager::GetHandle().Create<Object>( 10 ); MemoryManager::GetHandle().Delete<Object>( Value ); MemoryManager::GetHandle().Destroy(); }
-
결과 콘솔
17:59:40:528 | INFO OS | Windows 17:59:40:528 | WARN MemoryManager | struct Object | There isn't existed this type of MemoryPool 17:59:40:528 | INFO MemoryManager | struct Object | Create new pool ptr list 17:59:40:528 | INFO MemoryManager | struct Object | Pool Ptr 000001D709C9CD00 - Start Ptr 000001D709CA7400 | Create new memory pool **17:59:40:528 | INFO Instance | struct Object | 000001D709CA7400 | Create new 17:59:40:528 | INFO Instance | struct Object | 000001D709CA7400 | Delete**
-
실행 코드
struct IObject { public : IObject( int Value ) : m_Value( Value ) {}; virtual ~IObject() {}; virtual void Action() { Log::Info("IObject"); } public : int m_Value; }; struct Object : public IObject { public : Object( int Value ) : IObject( Value ) {}; Object() : IObject( 0 ) {}; virtual ~Object() {}; virtual void Action() { Log::Info("Object"); } }; void Example() { MemoryManager::GetHandle().Init(); MemoryManager::GetHandle().SetDefaultSize( 32 ); MemoryPtr<IObject> IOValue = MemoryManager::GetHandle().Create<IObject>( 10 ); MemoryPtr<Object> OValue = IOValue; MemoryManager::GetHandle().Destroy(); }
-
결과 콘솔
18:01:32:826 | INFO OS | Windows 18:01:32:826 | WARN MemoryManager | struct IObject | There isn't existed this type of MemoryPool 18:01:32:826 | INFO MemoryManager | struct IObject | Create new pool ptr list 18:01:32:826 | INFO MemoryManager | struct IObject | Pool Ptr 000001F8DB371AE0 - Start Ptr 000001F8DB377140 | Create new memory pool 18:01:32:826 | INFO Instance | struct IObject | 000001F8DB377140 | Create new **18:01:32:826 | WARN MPTR | MemoryPtr<struct Object>::CheckValidityofCopy | struct Object | No inheritance relationship with struct IObject 18:01:32:826 | ERROR MPTR | MemoryPtr<struct Object>::operator = | struct Object | Copying this MPtr is invalid**
-
실행 코드
void Example() { MemoryManager::GetHandle().Init(); MemoryManager::GetHandle().SetDefaultSize( 32 ); MemoryManager::GetHandle().Create<Object>( 11 ); MemoryPtr<Object> OValue = new Object( 10 ); MemoryManager::GetHandle().Delete<Object>( OValue ); MemoryManager::GetHandle().Destroy(); }
-
결과 콘솔
18:04:08:086 | INFO OS | Windows 18:04:08:086 | WARN MemoryManager | struct Object | There isn't existed this type of MemoryPool 18:04:08:086 | INFO MemoryManager | struct Object | Create new pool ptr list 18:04:08:086 | INFO MemoryManager | struct Object | Pool Ptr 000001D44E6B0EA0 - Start Ptr 000001D44E6B5410 | Create new memory pool 18:04:08:086 | INFO Instance | struct Object | 000001D44E6B5410 | Create new **18:04:08:086 | WARN MemoryManager | struct Object | There is no memory pool CCCCCCCCCCCCCCCC** 18:04:08:086 | INFO MemoryManager | Pool Ptr 000001D44E6B0EA0 - Start Ptr 000001D44E6B5410 | Destroy memory pool 18:04:08:086 | INFO Instance | struct Object | 000001D44E6B5410 | Delete
- MemoryManager를 통해서 생성된 MemoryPtr이 아닌 경우, Destruct 불가
- Log Project와 Memory Project를 진행하면서 라이브러리를 만들어서 사용할 수 있도록 하겠다.
- 나누어서 관리하다보디 더욱 편리하였다. 실제로 사용가능한 버젼만을 사용하게 된다면 기존의 프로젝트에서 단순 버젼을 올려주거나 그대로 사용만 하면 다른 프로젝트들에도 크게 무리가 없었다.
- 물론 인터페이스가 180도 바뀌는 경우에는 해당 프로젝트에 의존하는 다른 프로젝트들의 수정도 필요하겠지만, 인터페이스를 유지하며 확장할 경우에는 그럴 필요가 거의 없었다.
- 또한, 정적, 동적 라이브러리를 빌드하며 만들고 사용함으로써, 기존의 소스파일들을 전체 추가하는 방식보다 편리하였다.
- Github의 Repository를 불러와서 빌드하는 과정들을 넣음으로써, 단순히 내 프로젝트만을 사용하는 것이 다른 오픈 소스들을 사용함에 있어서도 응용할 수 있는 유용한 경험이었다.
- MemoryManager와 IMemoryPool을 만들면서 인터페이스의 중요성을 알게 되었다. template의 경우에는 virtual 함수로 설정할 수가 없기에, 해당 부분은 구체화 클래스에서 구현을 하고, 다른 기능들은 포괄적인 기능들은 인터페이스에 넣어서 구현을 진행하였다.
- MemoryPtr 내에서의 dynamic_cast를 통해서 RTTI에 대해서 조금 더 깊게 알게 되었다. 예를 들어, Parent 인스턴스가 할당되고 그것을 Parent를 상속받는 Child로 캐스팅할 경우, 실패하게 된다. 왜냐하면 해당 인스턴스는 Child에 대한 v-table을 지니고 있지 않기 때문에, 유효하지 않은 캐스팅이라고 판단하여 dynamic_cast의 결과값으로 nullptr이 나오기 때문이다.
- MemoryManager에선 Create 결과 값으로 MemoryPtr을 반환하게 되는데, 이때 임시객체의 개념을 배우고 난 뒤에는 그러한 불필요한 객체의 생성과 연산량을 막기 위해서 이동생성자와 이동 복사 대입자를 MemoryPtr에 넣어서 보다 효율적인 생성이 될 수 있도록 하였다.