- Creating auxiliary libraries for ECS
- Log Project
- Memory Project
- Language
- C++
- Functionality for printing engine-related information
- Ability to separately store log information
- Need to categorize log information:
- Info: General information
- Warn: Information requiring attention but not causing runtime errors
- Error: Information causing runtime errors or requiring exception handling
- To efficiently handle memory initialization and allocation tasks
- To centralize the management of various types of instances
- Implemented in a manner similar to smart pointers
- Addressing memory-related issues when returning pointers to objects in ECS
- Only destructible from the Memory Manager
- Configuration of CMakeLists for creating Log and Memory Projects as libraries
- Creating a GitHub repository and establishing dependencies for usage
- IMemoryPool
- Log
- MemoryPool (Parent: IMemoryPool)
- MemoryPtr
- MemoryManager
- IMemoryPool
-
Interface for objects using MemoryPool
class IMemoryPool { public: IMemoryPool() {} virtual ~IMemoryPool() {} public: virtual void Init() = 0; virtual 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
- Main roles:
- Actual allocation and deallocation of memory
- Manages instances by allocating all memory at once and using initialization and destructors
- Memory management:
-
Manages memory using the initial address and object size index information obtained from the MemoryPool
- For example:
- Allocated address: 01
- Memory size: 8
- Object size: 2
- Possible object indices (addresses): 0(01), 1(03), 2(05), 3(07)
- For example:
-
Allocates memory when the MemoryPool is initially created
-
void Init()
- Allocates memory using malloc
- Populates the m_CanConstruct queue with indices of constructable instances
- m_CanConstruct:
- A queue containing index information for instances that can be initialized
- m_CanDestruct:
- A queue containing index information for instances that can be destructed
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; }
-
-
The allocated memory persists until the MemoryPool is destroyed
-
- Instance management:
- Creation and destruction are managed using instance pointers
-
m_CanConstruct:
- A queue containing index information for instances that can be initialized
-
m_CanDestruct:
- A queue containing index information for instances that can be destructed
-
Creation:
- template< typename … Args > T Construct(Args&&… args)
- Checks if the MemoryPool is full
- Returns true if m_CanConstruct is empty
- Uses an available index to call placement new for object construction
- Checks if the MemoryPool is full
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)
-
Destruction:
-
void Destruct(T& Ptr)
- Uses the Ptr to find the index at which the instance can be destructed from the MemoryPool
- If an index is found:
- Calls the destructor of the instance and sets it to nullptr
- Updates m_CanConstruct and 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); } }
-
-
- Creation and destruction are managed using instance pointers
- Main roles:
-
MemoryPtr
-
Main role:
- Protection against external pointer deallocation and casting
-
Pointer-related:
-
Only the Memory Manager can deallocate the pointer
- template< typename U > friend class MemoryPtr;
- Allows access to each other's private member variables from other template MemoryPtr instances
private: friend class MemoryManager; template< typename U > friend class MemoryPtr;
- template< typename U > friend class MemoryPtr;
-
Allows casting using 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 related:
- Holds the MemoryPool's start pointer to simplify management by the Memory Manager
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
-
Main Responsibilities
- Create and delete MemoryPools.
- Initialize and destroy instances.
-
MemoryPool Related
-
Access Approach
-
Access MemoryPool through Start Pointers.
-
Start Pointers are stored in lists based on data types.
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;
-
-
Creation
-
template< typename T > void CreateMemoryPool( size_t Size )
- m_IMemoryPoolMap
- Data to access IMemoryPool using Start Pointers.
- Key: Start Pointer
- Value: IMemoryPool Pointer
- m_TypePoolPtrListMap
- Data to access Start Pointers using Type information.
- 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
-
-
-
Instance Management
-
Creation
-
Option to create one instance per MemoryPool or multiple instances.
-
template< typename T, typename… Args > MemoryPtr Create( Args&&… args )
- Create multiple instances.
-
template< typename T, typename… Args > MemoryPtr CreateOne( Args&&… args )
- Create a single instance.
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) ... ); }
-
-
Find an available MemoryPool in the IMemoryPool list and create an instance.
-
template< typename T, typename… Args > MemoryPtr MainCreate( size_t Size, Args&&… args )
- HasList()
- Check if there is an IMemoryPool List for the given T type.
- CreateList()
- Create an IMemoryPool List for the given T type.
- GetList()
- Get a reference to the List with IMemoryPool pointers for the given T type.
- GetMemoryPool( char Start )
- Get the IMemoryPool Pointer that has the given Start Pointer of type T.
- Iterate through the IMemoryPool List from the end and check if an instance can be created.
- All are full
- When all IMemoryPools are full, create a new MemoryPool.
- Recursively call the MainCreate function.
- An available MemoryPool
- Call the MemoryPool and create an object.
- All are full
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()
-
-
-
Deletion
-
Delete using the Start Ptr member variable of the MemoryPool inside MemoryPtr.
- template< typename T >
void Delete( MemoryPtr& mPtr )
- GetPoolPtr()
- Get the Start Ptr of the MemoryPool where this MemoryPtr belongs.
- HasMemoryPool( char Start )
- Check if there is a MemoryPool for type T with the given Start Pointer.
- 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 )
-
-
-
-
Configuration Management
-
BUILD_STATIC_LIBRARY
- true: Static library
- false: Dynamic library
-
INSTALL_DEMO_FILE
- true: Install demo file for MemoryManager
- false: Do not install demo file for MemoryManager
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 "Archive 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" )
-
-
Dependency Management
-
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 ----------------------------------- #
-
-
Execution Code
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(); }
-
Console Output
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**
-
Execution Code
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(); }
-
Console Output
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**
-
Execution Code
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(); }
-
Console Output
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
- If the MemoryPtr is not created through MemoryManager, it cannot be destructed using MemoryManager.
- During my work on the Log Project and Memory Project, I came to realize the significance of creating and using libraries.
- Separating and managing them provided a great deal of convenience. By using only actual, functional versions, I found that updating them in other projects was straightforward, typically involving minimal changes.
- While it's true that drastic changes in the library's interface might require adjustments in dependent projects, maintaining a stable interface and expanding functionality generally kept such issues to a minimum.
- Additionally, building and utilizing static and dynamic libraries proved to be more convenient than adding entire source files to projects.
- Incorporating GitHub repositories into the build process allowed for versatile usage of open-source libraries, not limited to my own projects.
- Creating MemoryManager and IMemoryPool taught me the importance of interfaces. In cases where templates couldn't utilize virtual functions, I implemented specific functionalities in concrete classes while placing broader functionalities in interfaces.
- I gained a deeper understanding of RTTI (Run-Time Type Information) by using dynamic_cast within MemoryPtr. For example, if a Parent instance is allocated and then cast to a Child, which inherits from Parent, the cast fails. This occurs because the instance lacks the v-table for Child, making the dynamic_cast return nullptr as it recognizes the cast as invalid.
- In MemoryManager, where MemoryPtr is returned as a result of Create, I learned about the concept of temporary objects. After grasping this concept, I incorporated move constructors and move assignment operators into MemoryPtr to prevent unnecessary object creation and optimize efficiency.