Initial
This commit is contained in:
parent
d4c558a59a
commit
db05d963be
74 changed files with 4473 additions and 3231 deletions
66
.clang-format
Normal file
66
.clang-format
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
# Generated from CLion C/C++ Code Style settings
|
||||||
|
BasedOnStyle: LLVM
|
||||||
|
AccessModifierOffset: -2
|
||||||
|
AlignAfterOpenBracket: Align
|
||||||
|
AlignConsecutiveAssignments: None
|
||||||
|
AlignOperands: Align
|
||||||
|
AllowAllArgumentsOnNextLine: false
|
||||||
|
AllowAllConstructorInitializersOnNextLine: false
|
||||||
|
AllowAllParametersOfDeclarationOnNextLine: false
|
||||||
|
AllowShortBlocksOnASingleLine: Always
|
||||||
|
AllowShortCaseLabelsOnASingleLine: false
|
||||||
|
AllowShortFunctionsOnASingleLine: All
|
||||||
|
AllowShortIfStatementsOnASingleLine: Always
|
||||||
|
AllowShortLambdasOnASingleLine: All
|
||||||
|
AllowShortLoopsOnASingleLine: true
|
||||||
|
AlwaysBreakAfterReturnType: None
|
||||||
|
AlwaysBreakTemplateDeclarations: Yes
|
||||||
|
BreakBeforeBraces: Custom
|
||||||
|
BraceWrapping:
|
||||||
|
AfterCaseLabel: false
|
||||||
|
AfterClass: false
|
||||||
|
AfterControlStatement: Never
|
||||||
|
AfterEnum: false
|
||||||
|
AfterFunction: false
|
||||||
|
AfterNamespace: false
|
||||||
|
AfterUnion: false
|
||||||
|
BeforeCatch: false
|
||||||
|
BeforeElse: false
|
||||||
|
IndentBraces: false
|
||||||
|
SplitEmptyFunction: false
|
||||||
|
SplitEmptyRecord: true
|
||||||
|
BreakBeforeBinaryOperators: None
|
||||||
|
BreakBeforeTernaryOperators: true
|
||||||
|
BreakConstructorInitializers: BeforeColon
|
||||||
|
BreakInheritanceList: BeforeColon
|
||||||
|
ColumnLimit: 0
|
||||||
|
CompactNamespaces: false
|
||||||
|
ContinuationIndentWidth: 4
|
||||||
|
IndentCaseLabels: true
|
||||||
|
IndentPPDirectives: None
|
||||||
|
IndentWidth: 2
|
||||||
|
KeepEmptyLinesAtTheStartOfBlocks: true
|
||||||
|
MaxEmptyLinesToKeep: 2
|
||||||
|
NamespaceIndentation: All
|
||||||
|
ObjCSpaceAfterProperty: false
|
||||||
|
ObjCSpaceBeforeProtocolList: true
|
||||||
|
PointerAlignment: Left
|
||||||
|
ReflowComments: false
|
||||||
|
SpaceAfterCStyleCast: true
|
||||||
|
SpaceAfterLogicalNot: false
|
||||||
|
SpaceAfterTemplateKeyword: false
|
||||||
|
SpaceBeforeAssignmentOperators: true
|
||||||
|
SpaceBeforeCpp11BracedList: false
|
||||||
|
SpaceBeforeCtorInitializerColon: true
|
||||||
|
SpaceBeforeInheritanceColon: true
|
||||||
|
SpaceBeforeParens: ControlStatements
|
||||||
|
SpaceBeforeRangeBasedForLoopColon: false
|
||||||
|
SpaceInEmptyParentheses: false
|
||||||
|
SpacesBeforeTrailingComments: 0
|
||||||
|
SpacesInAngles: false
|
||||||
|
SpacesInCStyleCastParentheses: false
|
||||||
|
SpacesInContainerLiterals: false
|
||||||
|
SpacesInParentheses: false
|
||||||
|
SpacesInSquareBrackets: false
|
||||||
|
TabWidth: 2
|
||||||
|
UseTab: Never
|
||||||
42
.github/workflows/cmake.yml
vendored
Normal file
42
.github/workflows/cmake.yml
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
name: CMake
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "master" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "master" ]
|
||||||
|
|
||||||
|
env:
|
||||||
|
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||||
|
BUILD_TYPE: Release
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
# The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac.
|
||||||
|
# You can convert this to a matrix build if you need cross-platform coverage.
|
||||||
|
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Install LLVM
|
||||||
|
run: sudo apt-get install -y llvm
|
||||||
|
|
||||||
|
- name: Set LLVM Toolchain
|
||||||
|
run: |
|
||||||
|
sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/g++ 10
|
||||||
|
sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++ 20
|
||||||
|
|
||||||
|
- name: Configure CMake
|
||||||
|
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
working-directory: ${{github.workspace}}/build
|
||||||
|
# Execute tests defined by the CMake configuration.
|
||||||
|
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
|
||||||
|
run: ctest -C ${{env.BUILD_TYPE}}
|
||||||
|
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -1,6 +1,6 @@
|
||||||
.idea
|
.idea
|
||||||
tmp
|
*tmp*
|
||||||
bin
|
bin
|
||||||
build
|
build*
|
||||||
lib
|
lib
|
||||||
install
|
install
|
||||||
|
|
|
||||||
20
Allocators/CMakeLists.txt
Normal file
20
Allocators/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.2)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
|
|
||||||
|
project(Allocator)
|
||||||
|
|
||||||
|
### ---------------------- Static Library --------------------- ###
|
||||||
|
file(GLOB SOURCES "./private/*.cpp")
|
||||||
|
add_library(${PROJECT_NAME} STATIC ${SOURCES})
|
||||||
|
target_include_directories(${PROJECT_NAME} PUBLIC ./public/)
|
||||||
|
target_link_libraries(${PROJECT_NAME} PUBLIC Utils)
|
||||||
|
|
||||||
|
### -------------------------- Tests -------------------------- ###
|
||||||
|
enable_testing()
|
||||||
|
add_executable(${PROJECT_NAME}Tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/Tests.cpp)
|
||||||
|
target_link_libraries(${PROJECT_NAME}Tests ${PROJECT_NAME})
|
||||||
|
add_test(${PROJECT_NAME} ${PROJECT_NAME}Tests)
|
||||||
|
|
||||||
|
install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/${PROJECT_NAME}/lib)
|
||||||
|
|
@ -30,7 +30,7 @@ If memory leaks were detected it will be loged in the output console.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Also debug.memleaks file will be generated in the working directory that can be viewved with MemLeaks Viewver.
|
Also debug.memleaks binary will be generated in the working directory that can be viewved with MemLeaks Viewver.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
@ -1,14 +1,10 @@
|
||||||
|
|
||||||
#include "allocators.hpp"
|
#include "Allocators.hpp"
|
||||||
|
|
||||||
#include "filesystem.h"
|
static tp::ModuleManifest* sModuleDependencies[] = { &tp::gModuleBase, NULL };
|
||||||
|
|
||||||
static tp::ModuleManifest* sModuleDependencies[] = { &tp::gModuleFilesystem, NULL };
|
|
||||||
tp::ModuleManifest tp::gModuleAllocator = ModuleManifest("Allocators", NULL, NULL, sModuleDependencies);
|
tp::ModuleManifest tp::gModuleAllocator = ModuleManifest("Allocators", NULL, NULL, sModuleDependencies);
|
||||||
|
|
||||||
|
|
||||||
//void* operator new(size_t aSize, void* aWhere) noexcept { return aWhere; }
|
|
||||||
|
|
||||||
void* operator new(size_t aSize) { return tp::HeapAllocGlobal::allocate(aSize); }
|
void* operator new(size_t aSize) { return tp::HeapAllocGlobal::allocate(aSize); }
|
||||||
void* operator new[](size_t aSize) { return tp::HeapAllocGlobal::allocate(aSize); }
|
void* operator new[](size_t aSize) { return tp::HeapAllocGlobal::allocate(aSize); }
|
||||||
void operator delete(void* aPtr) { tp::HeapAllocGlobal::deallocate(aPtr); }
|
void operator delete(void* aPtr) { tp::HeapAllocGlobal::deallocate(aPtr); }
|
||||||
|
|
@ -49,7 +49,7 @@ ChunkAlloc::ChunkAlloc(ualni aBlockSize, ualni aNBlocks) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void* ChunkAlloc::allocate() {
|
void* ChunkAlloc::allocate() {
|
||||||
RelAssert(mNFreeBlocks && "Out Of Memory");
|
ASSERT(mNFreeBlocks && "Out Of Memory");
|
||||||
|
|
||||||
// 1) PreInitialize blocks
|
// 1) PreInitialize blocks
|
||||||
if (mNInitBlocks < mNBlocks) {
|
if (mNInitBlocks < mNBlocks) {
|
||||||
|
|
@ -103,10 +103,11 @@ void ChunkAlloc::deallocate(void* aPtr) {
|
||||||
mNFreeBlocks++;
|
mNFreeBlocks++;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ChunkAlloc::isFull() { return !mNFreeBlocks; }
|
bool ChunkAlloc::isFull() const { return !mNFreeBlocks; }
|
||||||
bool ChunkAlloc::isEmpty() { return mNFreeBlocks == mNBlocks; }
|
bool ChunkAlloc::isEmpty() const { return mNFreeBlocks == mNBlocks; }
|
||||||
|
|
||||||
ChunkAlloc::~ChunkAlloc() {
|
ChunkAlloc::~ChunkAlloc() {
|
||||||
|
// TODO : check for leaks
|
||||||
if (mOwnBuff) {
|
if (mOwnBuff) {
|
||||||
HeapAllocGlobal::deallocate(mBuff);
|
HeapAllocGlobal::deallocate(mBuff);
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
|
|
||||||
#include "heapallocator.hpp"
|
|
||||||
|
#include "HeapAllocator.hpp"
|
||||||
|
#include "HeapAllocatorGlobal.hpp"
|
||||||
|
|
||||||
#include "PrivateConfig.hpp"
|
#include "PrivateConfig.hpp"
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
@ -8,10 +11,12 @@
|
||||||
using namespace tp;
|
using namespace tp;
|
||||||
|
|
||||||
#if not defined(MEM_DEBUG)
|
#if not defined(MEM_DEBUG)
|
||||||
|
|
||||||
// ----------------------- Release Implementation ---------------------------- //
|
// ----------------------- Release Implementation ---------------------------- //
|
||||||
void* HeapAlloc::allocate(ualni aBlockSize) { return malloc(aBlockSize); }
|
void* HeapAlloc::allocate(ualni aBlockSize) { return malloc(aBlockSize); }
|
||||||
void HeapAlloc::deallocate(void* aPtr) { free(aPtr); }
|
void HeapAlloc::deallocate(void* aPtr) { free(aPtr); }
|
||||||
HeapAlloc::~HeapAlloc() {}
|
HeapAlloc::~HeapAlloc() {}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
namespace tp {
|
namespace tp {
|
||||||
|
|
@ -22,7 +27,7 @@ namespace tp {
|
||||||
};
|
};
|
||||||
|
|
||||||
void* HeapAlloc::allocate(ualni aBlockSize) {
|
void* HeapAlloc::allocate(ualni aBlockSize) {
|
||||||
auto head = (MemHeadLocal*)mAlloc.allocate(aBlockSize + sizeof(MemHeadLocal));
|
auto head = (MemHeadLocal*) HeapAllocGlobal::allocate(aBlockSize + sizeof(MemHeadLocal));
|
||||||
auto out = head + 1;
|
auto out = head + 1;
|
||||||
|
|
||||||
mNumAllocations++;
|
mNumAllocations++;
|
||||||
|
|
@ -33,8 +38,8 @@ void* HeapAlloc::allocate(ualni aBlockSize) {
|
||||||
if (mEntry->mPrev) mEntry->mPrev->mNext = head;
|
if (mEntry->mPrev) mEntry->mPrev->mNext = head;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
head->mNext = NULL;
|
head->mNext = nullptr;
|
||||||
head->mPrev = NULL;
|
head->mPrev = nullptr;
|
||||||
}
|
}
|
||||||
mEntry = head;
|
mEntry = head;
|
||||||
|
|
||||||
|
|
@ -52,18 +57,19 @@ void HeapAlloc::deallocate(void* aPtr) {
|
||||||
mEntry = mEntry->mNext;
|
mEntry = mEntry->mNext;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
mEntry = mEntry->mNext;
|
mEntry = mEntry->mPrev;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mAlloc.deallocate(head);
|
HeapAllocGlobal::deallocate(head);
|
||||||
}
|
}
|
||||||
|
|
||||||
HeapAlloc::~HeapAlloc() {
|
HeapAlloc::~HeapAlloc() {
|
||||||
if (mNumAllocations) {
|
if (mNumAllocations) {
|
||||||
DBG_BREAK("Destruction of not freed Allocator");
|
DEBUG_BREAK("Destruction of not freed Allocator");
|
||||||
|
|
||||||
#ifdef MEM_STACK_TRACE
|
#ifdef MEM_STACK_TRACE
|
||||||
|
// TODO : log leaks and free them up
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
|
|
||||||
#include "heapallocator.hpp"
|
|
||||||
|
#include "HeapAllocatorGlobal.hpp"
|
||||||
#include "PrivateConfig.hpp"
|
#include "PrivateConfig.hpp"
|
||||||
|
|
||||||
#include "StackTrace.hpp"
|
#include "Debugging.hpp"
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
|
@ -12,14 +13,15 @@ using namespace tp;
|
||||||
#if not defined(MEM_DEBUG)
|
#if not defined(MEM_DEBUG)
|
||||||
|
|
||||||
// ----------------------- Release Implementation ---------------------------- //
|
// ----------------------- Release Implementation ---------------------------- //
|
||||||
|
|
||||||
void* HeapAllocGlobal::allocate(ualni aBlockSize) { return malloc(aBlockSize); }
|
void* HeapAllocGlobal::allocate(ualni aBlockSize) { return malloc(aBlockSize); }
|
||||||
void HeapAllocGlobal::deallocate(void* aPtr) { free(aPtr); }
|
void HeapAllocGlobal::deallocate(void* aPtr) { free(aPtr); }
|
||||||
HeapAllocGlobal::~HeapAllocGlobal() {};
|
HeapAllocGlobal::~HeapAllocGlobal() = default;
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
tp::MemHead* tp::HeapAllocGlobal::mEntry = NULL;
|
tp::MemHead* tp::HeapAllocGlobal::mEntry = nullptr;
|
||||||
tp::ualni tp::HeapAllocGlobal::mNumAllocations = NULL;
|
tp::ualni tp::HeapAllocGlobal::mNumAllocations = 0;
|
||||||
|
|
||||||
// ----------------------- Debug Implementation ---------------------------- //
|
// ----------------------- Debug Implementation ---------------------------- //
|
||||||
// |----------------|
|
// |----------------|
|
||||||
|
|
@ -66,7 +68,7 @@ void* HeapAllocGlobal::allocate(ualni aBlockSize) {
|
||||||
auto data = wrap_top + WRAP_SIZE;
|
auto data = wrap_top + WRAP_SIZE;
|
||||||
auto wrap_bottom = data + aBlockSize;
|
auto wrap_bottom = data + aBlockSize;
|
||||||
|
|
||||||
if (!head) { return NULL; }
|
if (!head) { return nullptr; }
|
||||||
head->mBlockSize = aBlockSize;
|
head->mBlockSize = aBlockSize;
|
||||||
|
|
||||||
// 2) Link with existing blocks
|
// 2) Link with existing blocks
|
||||||
|
|
@ -78,8 +80,8 @@ void* HeapAllocGlobal::allocate(ualni aBlockSize) {
|
||||||
if (mEntry->mPrev) mEntry->mPrev->mNext = head;
|
if (mEntry->mPrev) mEntry->mPrev->mNext = head;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
head->mNext = NULL;
|
head->mNext = nullptr;
|
||||||
head->mPrev = NULL;
|
head->mPrev = nullptr;
|
||||||
}
|
}
|
||||||
mEntry = head;
|
mEntry = head;
|
||||||
|
|
||||||
|
|
@ -116,7 +118,7 @@ void HeapAllocGlobal::deallocate(void* aPtr) {
|
||||||
mEntry = mEntry->mNext;
|
mEntry = mEntry->mNext;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
mEntry = mEntry->mNext;
|
mEntry = mEntry->mPrev;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,9 +137,10 @@ void HeapAllocGlobal::deallocate(void* aPtr) {
|
||||||
HeapAllocGlobal::~HeapAllocGlobal() {
|
HeapAllocGlobal::~HeapAllocGlobal() {
|
||||||
// 1) Check for not deallocated memory
|
// 1) Check for not deallocated memory
|
||||||
if (mNumAllocations) {
|
if (mNumAllocations) {
|
||||||
DBG_BREAK("Destruction of not freed Allocator");
|
DEBUG_BREAK("Destruction of not freed Allocator");
|
||||||
|
|
||||||
#ifdef MEM_STACK_TRACE
|
#ifdef MEM_STACK_TRACE
|
||||||
|
// TODO: log leaks
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,19 +3,19 @@
|
||||||
*
|
*
|
||||||
Implementation:
|
Implementation:
|
||||||
* Adding chunk pointer to each chunk to form one-directional list that keeps track of free chunk
|
* Adding chunk pointer to each chunk to form one-directional list that keeps track of free chunk
|
||||||
* Storing ordered pointers to chunks in order to find desired chunk from delete pointer on deallocation in log time
|
* Storing ordered pointers to chunks in order to find desired chunk from delete pointer on de-allocation in log time
|
||||||
*
|
*
|
||||||
* Allocations:
|
* Allocations:
|
||||||
* 1) allocate with chunk stored in list entry
|
* 1) allocate with chunk stored in list entry
|
||||||
* 2) ...
|
* 2) ...
|
||||||
*
|
*
|
||||||
* Deallocations:
|
* De-allocations:
|
||||||
* 1) binary-search with delete pointer to find desired chunk
|
* 1) binary-search with delete pointer to find desired chunk
|
||||||
* 2) ...
|
* 2) ...
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "allocators.hpp"
|
#include "Allocators.hpp"
|
||||||
#include "PrivateConfig.hpp"
|
#include "PrivateConfig.hpp"
|
||||||
|
|
||||||
void tp::PoolAlloc::Chunks::add(Chunk* aChunk) {
|
void tp::PoolAlloc::Chunks::add(Chunk* aChunk) {
|
||||||
|
|
@ -44,7 +44,7 @@ void tp::PoolAlloc::Chunks::remove(Chunk* aChunk) {
|
||||||
mUsedLen--;
|
mUsedLen--;
|
||||||
|
|
||||||
// check for buff low usage
|
// check for buff low usage
|
||||||
if ((halnf)mUsedLen / mLen < 0.25f) {
|
if ((halnf)mUsedLen / (halnf)mLen < 0.25f) {
|
||||||
auto prevBuff = mBuff;
|
auto prevBuff = mBuff;
|
||||||
mBuff = (Chunk**)HeapAllocGlobal::allocate(sizeof(Chunk*) * mLen / 2);
|
mBuff = (Chunk**)HeapAllocGlobal::allocate(sizeof(Chunk*) * mLen / 2);
|
||||||
memcp(mBuff, prevBuff, sizeof(Chunk*) * mUsedLen);
|
memcp(mBuff, prevBuff, sizeof(Chunk*) * mUsedLen);
|
||||||
|
|
@ -63,7 +63,7 @@ tp::PoolAlloc::Chunk* tp::PoolAlloc::Chunks::findNotFull() {
|
||||||
return mBuff[idx];
|
return mBuff[idx];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return NULL;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
tp::PoolAlloc::Chunk** tp::PoolAlloc::Chunks::findUtil(Chunk** aLeft, Chunk** aRight, void* aPtr) {
|
tp::PoolAlloc::Chunk** tp::PoolAlloc::Chunks::findUtil(Chunk** aLeft, Chunk** aRight, void* aPtr) {
|
||||||
|
|
@ -84,7 +84,7 @@ void* tp::PoolAlloc::allocate() {
|
||||||
if (!new_free_chunk) {
|
if (!new_free_chunk) {
|
||||||
|
|
||||||
new_free_chunk = new ((Chunk*)HeapAllocGlobal::allocate(sizeof(Chunk))) Chunk(mBlockSize, mChunkSize);
|
new_free_chunk = new ((Chunk*)HeapAllocGlobal::allocate(sizeof(Chunk))) Chunk(mBlockSize, mChunkSize);
|
||||||
RelAssert(new_free_chunk);
|
ASSERT(new_free_chunk);
|
||||||
mChunks.add(new_free_chunk);
|
mChunks.add(new_free_chunk);
|
||||||
|
|
||||||
if (mFreeChunk) {
|
if (mFreeChunk) {
|
||||||
|
|
@ -94,8 +94,8 @@ void* tp::PoolAlloc::allocate() {
|
||||||
if (mFreeChunk->mPrev) mFreeChunk->mPrev->mNext = new_free_chunk;
|
if (mFreeChunk->mPrev) mFreeChunk->mPrev->mNext = new_free_chunk;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
new_free_chunk->mNext = NULL;
|
new_free_chunk->mNext = nullptr;
|
||||||
new_free_chunk->mPrev = NULL;
|
new_free_chunk->mPrev = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
mFreeChunk = new_free_chunk;
|
mFreeChunk = new_free_chunk;
|
||||||
|
|
@ -111,7 +111,7 @@ void tp::PoolAlloc::deallocate(void* aPtr) {
|
||||||
|
|
||||||
if (mFreeChunk->isEmpty()) {
|
if (mFreeChunk->isEmpty()) {
|
||||||
|
|
||||||
Chunk* new_chunk = NULL;
|
Chunk* new_chunk = nullptr;
|
||||||
for (ualni idx = 0; idx < mChunks.mUsedLen; idx++) {
|
for (ualni idx = 0; idx < mChunks.mUsedLen; idx++) {
|
||||||
if (!mChunks.mBuff[idx]->isFull() && new_chunk != mFreeChunk) {
|
if (!mChunks.mBuff[idx]->isFull() && new_chunk != mFreeChunk) {
|
||||||
new_chunk = mChunks.mBuff[idx];
|
new_chunk = mChunks.mBuff[idx];
|
||||||
|
|
@ -128,5 +128,4 @@ void tp::PoolAlloc::deallocate(void* aPtr) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tp::PoolAlloc::~PoolAlloc() {
|
tp::PoolAlloc::~PoolAlloc() = default;
|
||||||
}
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "PublicConfig.hpp"
|
|
||||||
|
|
||||||
#define MEM_WRAP_SIZE 8 // Wrapping Size in aligned units
|
#define MEM_WRAP_SIZE 8 // Wrapping Size in aligned units
|
||||||
#define MEM_WRAP_FILL_VAL 0xBB // Wrapping Fill Value
|
#define MEM_WRAP_FILL_VAL 0xBB // Wrapping Fill Value
|
||||||
#define MEM_CLEAR_ON_ALLOC // Clear data on allocation
|
#define MEM_CLEAR_ON_ALLOC // Clear data on allocation
|
||||||
29
Allocators/public/Allocators.hpp
Normal file
29
Allocators/public/Allocators.hpp
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "BaseModule.hpp"
|
||||||
|
|
||||||
|
#include "HeapAllocatorGlobal.hpp"
|
||||||
|
#include "HeapAllocator.hpp"
|
||||||
|
#include "ChunkAllocator.hpp"
|
||||||
|
#include "PoolAllocator.hpp"
|
||||||
|
|
||||||
|
namespace tp {
|
||||||
|
extern ModuleManifest gModuleAllocator;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void* operator new(std::size_t aSize, void* aWhere) noexcept { return aWhere; }
|
||||||
|
|
||||||
|
void* operator new(std::size_t aSize);
|
||||||
|
void* operator new[](std::size_t aSize);
|
||||||
|
void operator delete(void* aPtr);
|
||||||
|
void operator delete[](void* aPtr);
|
||||||
|
|
||||||
|
void* operator new(std::size_t aSize, tp::HeapAlloc& aAlloc);
|
||||||
|
void* operator new[](std::size_t aSize, tp::HeapAlloc& aAlloc);
|
||||||
|
void operator delete(void* aPtr, tp::HeapAlloc& aAlloc);
|
||||||
|
void operator delete[](void* aPtr, tp::HeapAlloc& aAlloc);
|
||||||
|
|
||||||
|
void* operator new(std::size_t aSize, tp::HeapAllocGlobal& aAlloc);
|
||||||
|
void* operator new[](std::size_t aSize, tp::HeapAllocGlobal& aAlloc);
|
||||||
|
void operator delete(void* aPtr, tp::HeapAllocGlobal& aAlloc);
|
||||||
|
void operator delete[](void* aPtr, tp::HeapAllocGlobal& aAlloc);
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "common.h"
|
#include "Environment.hpp"
|
||||||
#include "PublicConfig.hpp"
|
|
||||||
|
|
||||||
namespace tp {
|
namespace tp {
|
||||||
|
|
||||||
// Chunk Allocator
|
// Chunk Allocator
|
||||||
// Constant time allocations and deallocations in any order.
|
// Constant time allocations and de-allocations in any order.
|
||||||
// Memory blocks are fixed in size and number of blocks can not exceed given parameter.
|
// Memory blocks are fixed in size and number of blocks can not exceed given parameter.
|
||||||
struct ChunkAlloc {
|
struct ChunkAlloc {
|
||||||
|
|
||||||
|
|
@ -15,8 +14,8 @@ namespace tp {
|
||||||
|
|
||||||
void* allocate();
|
void* allocate();
|
||||||
void deallocate(void* aPtr);
|
void deallocate(void* aPtr);
|
||||||
bool isFull();
|
[[nodiscard]] bool isFull() const;
|
||||||
bool isEmpty();
|
[[nodiscard]] bool isEmpty() const;
|
||||||
|
|
||||||
~ChunkAlloc();
|
~ChunkAlloc();
|
||||||
|
|
||||||
|
|
@ -24,8 +23,8 @@ namespace tp {
|
||||||
ualni mBSize; // Size of data in aligned units
|
ualni mBSize; // Size of data in aligned units
|
||||||
ualni mNBlocks;
|
ualni mNBlocks;
|
||||||
|
|
||||||
ualni* mBuff;
|
ualni* mBuff = nullptr;
|
||||||
ualni* mNextBlock = 0;
|
ualni* mNextBlock = nullptr;
|
||||||
ualni mNFreeBlocks;
|
ualni mNFreeBlocks;
|
||||||
ualni mNInitBlocks = 0;
|
ualni mNInitBlocks = 0;
|
||||||
bool mOwnBuff = false;
|
bool mOwnBuff = false;
|
||||||
|
|
@ -1,16 +1,14 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "HeapAllocatorGlobal.hpp"
|
#include "Environment.hpp"
|
||||||
|
|
||||||
namespace tp {
|
namespace tp {
|
||||||
|
|
||||||
struct HeapAlloc {
|
struct HeapAlloc {
|
||||||
|
|
||||||
#ifdef MEM_DEBUG
|
#ifdef MEM_DEBUG
|
||||||
HeapAllocGlobal mAlloc;
|
|
||||||
|
|
||||||
ualni mNumAllocations = 0;
|
ualni mNumAllocations = 0;
|
||||||
struct MemHeadLocal* mEntry = NULL;
|
struct MemHeadLocal* mEntry = nullptr;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void* allocate(ualni aBlockSize);
|
void* allocate(ualni aBlockSize);
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "common.h"
|
#include "BaseModule.hpp"
|
||||||
#include "PublicConfig.hpp"
|
|
||||||
|
|
||||||
namespace tp {
|
namespace tp {
|
||||||
|
|
||||||
305
Allocators/tests/Tests.cpp
Normal file
305
Allocators/tests/Tests.cpp
Normal file
|
|
@ -0,0 +1,305 @@
|
||||||
|
|
||||||
|
#include "Allocators.hpp"
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
|
||||||
|
tp::ModuleManifest* ModuleDependencies[] = { &tp::gModuleAllocators, NULL };
|
||||||
|
tp::ModuleManifest TestModule("Test", NULL, NULL, ModuleDependencies);
|
||||||
|
if (!TestModule.initialize()) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
tp::HeapAllocGlobal alloc;
|
||||||
|
|
||||||
|
int* val = new(alloc) int();
|
||||||
|
delete(alloc, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct test_struct {
|
||||||
|
tp::alni val = 0;
|
||||||
|
|
||||||
|
test_struct() { val = 1; }
|
||||||
|
~test_struct() { val = -1; }
|
||||||
|
|
||||||
|
bool operator==(const test_struct& in) { return in.val == val; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <tp::alni size>
|
||||||
|
struct allocator_test {
|
||||||
|
test_struct data[size];
|
||||||
|
bool is_allocated[size];
|
||||||
|
tp::alni n_loaded = 0;
|
||||||
|
|
||||||
|
test_struct* allocations[size];
|
||||||
|
|
||||||
|
tp::AbstractAllocator* alloc;
|
||||||
|
tp::AbstractAllocator* parent_alloc;
|
||||||
|
const char* allocator_name = NULL;
|
||||||
|
|
||||||
|
tp::alni rand_idx(bool state) {
|
||||||
|
RAND:
|
||||||
|
|
||||||
|
tp::alni idx = (tp::alni)(tp::randf() * (size + 1));
|
||||||
|
CLAMP(idx, 0, size - 1);
|
||||||
|
|
||||||
|
if (state == is_allocated[idx]) {
|
||||||
|
goto RAND;
|
||||||
|
}
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
allocator_test(tp::AbstractAllocator* palloc, const char* pallocator_name,
|
||||||
|
tp::AbstractAllocator* p_parent_alloc) {
|
||||||
|
allocator_name = pallocator_name;
|
||||||
|
parent_alloc = p_parent_alloc;
|
||||||
|
alloc = palloc;
|
||||||
|
for (tp::alni i = 0; i < size; i++) {
|
||||||
|
RAND:
|
||||||
|
tp::alni val = tp::alni(tp::randf() * (size + 100.f));
|
||||||
|
for (tp::alni check_idx = 0; check_idx < size; check_idx++) {
|
||||||
|
if (data[check_idx].val == val) {
|
||||||
|
goto RAND;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data[i].val = val;
|
||||||
|
is_allocated[i] = false;
|
||||||
|
allocations[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void verify_integrity() {
|
||||||
|
// verify data integrity
|
||||||
|
for (tp::alni i = 0; i < size; i++) {
|
||||||
|
if (is_allocated[i]) {
|
||||||
|
assert(*allocations[i] == data[i] && "data is currupted\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alloc->isWrapSupport()) assert(!alloc->isWrapCorrupted());
|
||||||
|
if (parent_alloc && parent_alloc->isWrapSupport())
|
||||||
|
assert(!parent_alloc->isWrapCorrupted());
|
||||||
|
|
||||||
|
verify_sizes();
|
||||||
|
}
|
||||||
|
|
||||||
|
void verify_sizes() {
|
||||||
|
return;
|
||||||
|
#ifdef MEM_TRACE
|
||||||
|
assert(alloc->sizeInuse() == n_loaded * sizeof(test_struct) &&
|
||||||
|
"invalid inuse size\n");
|
||||||
|
assert(alloc->sizeReserved() >= n_loaded * (tp::alni)sizeof(test_struct) &&
|
||||||
|
"invalid reserved size\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void load_item(tp::alni idx) {
|
||||||
|
if (!is_allocated[idx]) {
|
||||||
|
allocations[idx] = new (alloc) test_struct();
|
||||||
|
|
||||||
|
assert(allocations[idx] && "allocator returned NULL");
|
||||||
|
|
||||||
|
allocations[idx]->val = data[idx].val;
|
||||||
|
is_allocated[idx] = true;
|
||||||
|
n_loaded++;
|
||||||
|
verify_integrity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void unload_item(tp::alni idx) {
|
||||||
|
if (is_allocated[idx]) {
|
||||||
|
verify_integrity();
|
||||||
|
delete allocations[idx];
|
||||||
|
is_allocated[idx] = false;
|
||||||
|
n_loaded--;
|
||||||
|
verify_integrity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void change_states(tp::Range<tp::alni> rg, bool state, bool reversed = false,
|
||||||
|
bool random = false) {
|
||||||
|
for (auto i : rg) {
|
||||||
|
tp::alni idx = i;
|
||||||
|
|
||||||
|
if (random) {
|
||||||
|
idx = rand_idx(state);
|
||||||
|
}
|
||||||
|
else if (reversed) {
|
||||||
|
idx = size - i - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
(state) ? load_item(idx) : unload_item(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// full down-up load then up-down unload
|
||||||
|
void test1() {
|
||||||
|
change_states({ 0, size }, 1);
|
||||||
|
change_states({ 0, size }, 0, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// full down-up load then down-up unload
|
||||||
|
void test2() {
|
||||||
|
change_states({ 0, size }, 1);
|
||||||
|
change_states({ 0, size }, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// full random load then random unload
|
||||||
|
void test3() {
|
||||||
|
change_states({ 0, size }, 1, 0, 1);
|
||||||
|
change_states({ 0, size }, 0, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// multipul tests 1-3
|
||||||
|
void test4() {
|
||||||
|
test1();
|
||||||
|
test1();
|
||||||
|
|
||||||
|
test2();
|
||||||
|
test2();
|
||||||
|
|
||||||
|
test3();
|
||||||
|
test3();
|
||||||
|
}
|
||||||
|
|
||||||
|
static tp::alnf sineupf(tp::alnf asize, tp::alnf x, bool reverse) {
|
||||||
|
tp::alnf end = 4 * PI;
|
||||||
|
tp::alnf a = (2 / 7.f) * asize;
|
||||||
|
tp::alnf b = end / asize;
|
||||||
|
|
||||||
|
tp::alni c = ((-1 * reverse) + (1 * !reverse));
|
||||||
|
tp::alnf c1 = (x - (end * reverse)) / b;
|
||||||
|
tp::alnf c2 = (a * sin(x - (end * reverse)));
|
||||||
|
tp::alnf out = c1 + c2;
|
||||||
|
return c * out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sin load & sin unload with ~1/2 drop factor
|
||||||
|
void test5() {
|
||||||
|
tp::alnf end = 4 * PI;
|
||||||
|
tp::alnf step = end / 4.f;
|
||||||
|
|
||||||
|
for (char i = 0; i < 2; i++) {
|
||||||
|
for (tp::alnf x = 0; x <= end; x += step) {
|
||||||
|
tp::alni target_alloc_count = (tp::alni)ceil(sineupf(size, x, i));
|
||||||
|
CLAMP(target_alloc_count, 0, size);
|
||||||
|
|
||||||
|
while (n_loaded > target_alloc_count) {
|
||||||
|
unload_item(rand_idx(0));
|
||||||
|
}
|
||||||
|
while (n_loaded < target_alloc_count) {
|
||||||
|
load_item(rand_idx(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef MEM_WRAP
|
||||||
|
void check_wrap(tp::alni offset, bool after) {
|
||||||
|
CLAMP(offset, 1, WRAP_LEN);
|
||||||
|
|
||||||
|
test_struct* ts = allocations[rand_idx(0)];
|
||||||
|
tp::alni shift = (sizeof(test_struct) * after) + (offset - 1) * after -
|
||||||
|
offset * (!after);
|
||||||
|
tp::uint1* address = (((tp::uint1*)ts) + shift);
|
||||||
|
|
||||||
|
tp::uint1 val = *address;
|
||||||
|
*address = 5;
|
||||||
|
assert(alloc->isWrapCorrupted());
|
||||||
|
*address = val;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// mem guards test
|
||||||
|
void test6() {
|
||||||
|
change_states({ 0, size }, 1);
|
||||||
|
|
||||||
|
#ifdef MEM_WRAP
|
||||||
|
for (tp::alni after = 0; after < 2; after++) {
|
||||||
|
for (tp::alni offset = 1; offset <= WRAP_LEN; offset++) {
|
||||||
|
check_wrap(offset, after);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
change_states({ 0, size }, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void run_tests() {
|
||||||
|
try {
|
||||||
|
test1();
|
||||||
|
test2();
|
||||||
|
test3();
|
||||||
|
test4();
|
||||||
|
test5();
|
||||||
|
if (alloc->isWrapSupport()) {
|
||||||
|
test6();
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("%s - passed\n", allocator_name);
|
||||||
|
if (!alloc->isWrapSupport()) {
|
||||||
|
printf(" WARNING: %s has no wrap support!! \n", allocator_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (...) {
|
||||||
|
printf("%s - failed\n", allocator_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void heap_alloc_test() {
|
||||||
|
tp::alloc_init(); {
|
||||||
|
try {
|
||||||
|
allocator_test<150> hatest(tp::heapalloc, "heap allocator", NULL);
|
||||||
|
hatest.run_tests();
|
||||||
|
}
|
||||||
|
catch (...) {
|
||||||
|
printf("heap alloc failed\n");
|
||||||
|
}
|
||||||
|
} tp::alloc_uninit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void chunk_alloc_test() {
|
||||||
|
tp::alloc_init(); {
|
||||||
|
try {
|
||||||
|
tp::ChunkAlloc calloc(sizeof(test_struct), 50);
|
||||||
|
allocator_test<50> ca_test(&calloc, "chunk allocator", tp::heapalloc);
|
||||||
|
ca_test.run_tests();
|
||||||
|
}
|
||||||
|
catch (...) {
|
||||||
|
printf("chunk alloc failed\n");
|
||||||
|
}
|
||||||
|
} tp::alloc_uninit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void pool_alloc_test() {
|
||||||
|
tp::alloc_init(); {
|
||||||
|
try {
|
||||||
|
tp::PoolAlloc palloc(sizeof(test_struct), 50);
|
||||||
|
allocator_test<150> pa_test(&palloc, "pool allocator", tp::heapalloc);
|
||||||
|
pa_test.run_tests();
|
||||||
|
}
|
||||||
|
catch (...) {
|
||||||
|
printf("chunk alloc failed\n");
|
||||||
|
}
|
||||||
|
} tp::alloc_uninit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void allocators_test() {
|
||||||
|
|
||||||
|
printf("running tests on alocators:\n");
|
||||||
|
|
||||||
|
heap_alloc_test();
|
||||||
|
chunk_alloc_test();
|
||||||
|
pool_alloc_test();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
CROSSPLATFORM_MAIN;
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
tp::print_env_info();
|
||||||
|
allocators_test();
|
||||||
|
}
|
||||||
20
BaseModule/CMakeLists.txt
Normal file
20
BaseModule/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.2)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
|
|
||||||
|
project(BaseModule)
|
||||||
|
|
||||||
|
### ---------------------- Static Library --------------------- ###
|
||||||
|
file(GLOB SOURCES "./private/*.cpp")
|
||||||
|
add_library(${PROJECT_NAME} STATIC ${SOURCES})
|
||||||
|
target_include_directories(${PROJECT_NAME} PUBLIC ./public/)
|
||||||
|
|
||||||
|
### -------------------------- Tests -------------------------- ###
|
||||||
|
enable_testing()
|
||||||
|
add_executable(${PROJECT_NAME}Tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/Tests.cpp)
|
||||||
|
target_link_libraries(${PROJECT_NAME}Tests ${PROJECT_NAME})
|
||||||
|
add_test(NAME ${PROJECT_NAME}Tests COMMAND ${PROJECT_NAME}Tests)
|
||||||
|
|
||||||
|
|
||||||
|
install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/${PROJECT_NAME}/lib)
|
||||||
26
BaseModule/private/Assert.cpp
Normal file
26
BaseModule/private/Assert.cpp
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
|
||||||
|
#include "Assert.hpp"
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
using namespace tp;
|
||||||
|
|
||||||
|
void tp::_assert_(const char* exp, const char* file, int line) {
|
||||||
|
if (!exp) {
|
||||||
|
exp = "no info";
|
||||||
|
}
|
||||||
|
printf("\nERROR: Assertion Failure - %s -- %s:%i\n", exp, file, line);
|
||||||
|
|
||||||
|
#ifdef ENV_BUILD_DEBUG
|
||||||
|
DEBUG_BREAK(true);
|
||||||
|
#else
|
||||||
|
exit(1);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void tp::terminate(tp::alni code) {
|
||||||
|
exit((int)code);
|
||||||
|
}
|
||||||
81
BaseModule/private/BaseModule.cpp
Normal file
81
BaseModule/private/BaseModule.cpp
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
|
||||||
|
#include "BaseModule.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
using namespace tp;
|
||||||
|
|
||||||
|
static bool init(const ModuleManifest* self) {
|
||||||
|
gEnvironment.log();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ModuleManifest* deps[] = { nullptr };
|
||||||
|
ModuleManifest tp::gModuleBase = ModuleManifest("Common", init, nullptr, deps);
|
||||||
|
|
||||||
|
ModuleManifest::ModuleManifest(const char* aModuleName, ModuleInit aInit, ModuleDeinit aDeinit, ModuleManifest** aDependencies) {
|
||||||
|
mInit = aInit;
|
||||||
|
mDeinit = aDeinit;
|
||||||
|
mDependencies = aDependencies;
|
||||||
|
mModuleName = aModuleName;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ModuleManifest::isInitialized() const {
|
||||||
|
return mInitialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ModuleManifest::initialize() {
|
||||||
|
|
||||||
|
mInitCount++;
|
||||||
|
|
||||||
|
if (isInitialized()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
mInitialized = true;
|
||||||
|
|
||||||
|
for (auto module = mDependencies; module && *module; module++) {
|
||||||
|
mInitialized &= (*module)->initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "====== Initializing \"" << mModuleName << "\"\n";
|
||||||
|
|
||||||
|
if (mInit) mInitialized &= mInit(this);
|
||||||
|
|
||||||
|
if (!mInitialized) {
|
||||||
|
std::cout << "Failed to Initialize.\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return mInitialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModuleManifest::deinitialize() {
|
||||||
|
mInitCount--;
|
||||||
|
|
||||||
|
if (mInitCount > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isInitialized()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mDeinit) mDeinit(this);
|
||||||
|
mInitialized = false;
|
||||||
|
|
||||||
|
auto len = 0;
|
||||||
|
for (auto module = mDependencies; module && *module; module++) {
|
||||||
|
len++;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto i = 0; i < len; i++) {
|
||||||
|
auto module = mDependencies + (len - i - 1);
|
||||||
|
if ((*module)->isInitialized()) {
|
||||||
|
(*module)->deinitialize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *ModuleManifest::getName() const {
|
||||||
|
return mModuleName;
|
||||||
|
}
|
||||||
46
BaseModule/private/Common.cpp
Normal file
46
BaseModule/private/Common.cpp
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
|
||||||
|
#include "Common.hpp"
|
||||||
|
|
||||||
|
namespace tp {
|
||||||
|
ualni next2pow(ualni v) {
|
||||||
|
v |= v >> 1;
|
||||||
|
v |= v >> 2;
|
||||||
|
v |= v >> 4;
|
||||||
|
v |= v >> 8;
|
||||||
|
v |= v >> 16;
|
||||||
|
v |= v >> 32;
|
||||||
|
return v + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uhalni next2pow(uhalni v) {
|
||||||
|
v |= v >> 1;
|
||||||
|
v |= v >> 2;
|
||||||
|
v |= v >> 4;
|
||||||
|
v |= v >> 8;
|
||||||
|
v |= v >> 16;
|
||||||
|
return v + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ufalni next2pow(ufalni v) {
|
||||||
|
v |= v >> 1;
|
||||||
|
v |= v >> 2;
|
||||||
|
v |= v >> 4;
|
||||||
|
v |= v >> 8;
|
||||||
|
return v + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ualni hash(const char* bytes) {
|
||||||
|
unsigned long hash = 5381;
|
||||||
|
int c;
|
||||||
|
while ((c = *bytes++)) {
|
||||||
|
hash = ((hash << 5) + hash) + c;
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
ualni hash(alni bytes) { return abs(bytes); }
|
||||||
|
ualni hash(alnf bytes) { return (alni)(abs(bytes)); }
|
||||||
|
ualni hash(halni bytes) { return hash(alni(bytes)); }
|
||||||
|
ualni hash(uhalni bytes) { return hash(alni(bytes)); }
|
||||||
|
ualni hash(ualni bytes) { return hash(alni(bytes)); }
|
||||||
|
}
|
||||||
21
BaseModule/private/Environment.cpp
Normal file
21
BaseModule/private/Environment.cpp
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
|
||||||
|
#include "Environment.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
const char* ArchString[] = { "UNDEF", "INTEL", "ARM" };
|
||||||
|
const char* BuildTypeString[] = { "UNDEF", "DEBUG", "RELEASE" };
|
||||||
|
const char* ToolchainString[] = { "UNDEF", "GNU", "LLVM", "MSVC" };
|
||||||
|
const char* OSString[] = { "UNDEF", "LINUX", "WINDOWS", "ANDROID", "IOS" };
|
||||||
|
const char* ArchWidthString[] = { "UNDEF", "X64", "X32" };
|
||||||
|
|
||||||
|
|
||||||
|
const tp::Environment tp::gEnvironment;
|
||||||
|
|
||||||
|
void tp::Environment::log() const {
|
||||||
|
std::cout << "ARCH : " << ArchString[(int)mArch] << "\n";
|
||||||
|
std::cout << "WIDTH : " << ArchWidthString[(int)mWidth] << "\n";
|
||||||
|
std::cout << "CURRENT OS : " << OSString[(int)mOS] << "\n";
|
||||||
|
std::cout << "TOOLCHAIN : " << ToolchainString[(int)mToolchain] << "\n";
|
||||||
|
std::cout << "BUILD TYPE : " << BuildTypeString[(int)mBuildType] << "\n";
|
||||||
|
}
|
||||||
34
BaseModule/public/Assert.hpp
Normal file
34
BaseModule/public/Assert.hpp
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Environment.hpp"
|
||||||
|
|
||||||
|
namespace tp {
|
||||||
|
void _assert_(const char* exp, const char* file, int line);
|
||||||
|
void terminate(tp::alni code = 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
#define FAIL(exp) tp::_assert_(#exp, __FILE__, __LINE__);
|
||||||
|
#define ASSERT(exp) if (!(exp)) { FAIL(exp) }
|
||||||
|
|
||||||
|
#undef assert
|
||||||
|
#ifdef ENV_BUILD_DEBUG
|
||||||
|
#define DEBUG_ASSERT(exp) ASSERT(exp)
|
||||||
|
#else
|
||||||
|
#define DEBUG_ASSERT(exp) {}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#if defined(ENV_OS_WINDOWS)
|
||||||
|
#define DEBUG_BREAK(expr) if (expr) { __debugbreak(); }
|
||||||
|
#elif defined(ENV_OS_ANDROID)
|
||||||
|
#define DEBUG_BREAK(expr) if (expr) { __builtin_debugtrap(); }
|
||||||
|
#elif defined(ENV_OS_LINUX)
|
||||||
|
#define DEBUG_BREAK(expr) if (expr) { __builtin_trap(); }
|
||||||
|
#else
|
||||||
|
#define DEBUG_BREAK(expr) ()
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#define SWITCH_NO_DEF default : { FAIL("No Default Case Possible"); }
|
||||||
31
BaseModule/public/BaseModule.hpp
Normal file
31
BaseModule/public/BaseModule.hpp
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Common.hpp"
|
||||||
|
#include "Assert.hpp"
|
||||||
|
|
||||||
|
#define MODULE_SANITY_CHECK(name) ASSERT(name.isInitialized() && "Module Is Not Initialized" && #name)
|
||||||
|
|
||||||
|
namespace tp {
|
||||||
|
|
||||||
|
class ModuleManifest {
|
||||||
|
public:
|
||||||
|
typedef bool (*ModuleInit)(const ModuleManifest*);
|
||||||
|
typedef void (*ModuleDeinit)(const ModuleManifest*);
|
||||||
|
|
||||||
|
ModuleManifest(const char* aModuleName, ModuleInit aInit, ModuleDeinit aDeinit, ModuleManifest** aDependencies);
|
||||||
|
[[nodiscard]] bool isInitialized() const;
|
||||||
|
bool initialize();
|
||||||
|
void deinitialize();
|
||||||
|
[[nodiscard]] const char* getName() const;
|
||||||
|
private:
|
||||||
|
const char* mModuleName = nullptr;
|
||||||
|
ModuleManifest** mDependencies; // NULL terminated
|
||||||
|
bool mInitialized = false;
|
||||||
|
ModuleInit mInit = nullptr;
|
||||||
|
ModuleDeinit mDeinit = nullptr;
|
||||||
|
uhalni mInitCount = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern ModuleManifest gModuleBase;
|
||||||
|
};
|
||||||
59
BaseModule/public/Common.hpp
Normal file
59
BaseModule/public/Common.hpp
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Environment.hpp"
|
||||||
|
#include "TypeInfo.hpp"
|
||||||
|
#include <initializer_list>
|
||||||
|
|
||||||
|
namespace tp {
|
||||||
|
template<typename Type>
|
||||||
|
using init_list = std::initializer_list<Type>;
|
||||||
|
|
||||||
|
// Selects whether to pass by constant reference or by value
|
||||||
|
template <typename tType>
|
||||||
|
using SelCopyArg = typename TypeSelect<(sizeof(tType) > sizeof(tp::alni)), const tType&, tType>::Result;
|
||||||
|
|
||||||
|
ualni next2pow(ualni v);
|
||||||
|
uhalni next2pow(uhalni v);
|
||||||
|
ufalni next2pow(ufalni v);
|
||||||
|
|
||||||
|
ualni hash(const char* bytes);
|
||||||
|
ualni hash(alni bytes);
|
||||||
|
ualni hash(halni bytes);
|
||||||
|
ualni hash(uhalni bytes);
|
||||||
|
ualni hash(ualni bytes);
|
||||||
|
ualni hash(alnf bytes);
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
[[nodiscard]] T clamp(T v, T l, T u) { if (v < l) { v = l; } else if (v > u) { v = u; } return v; }
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
[[nodiscard]] T max(T a, T b) { return (a > b) ? a : b; }
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
[[nodiscard]] T min(T a, T b) { return (a < b) ? a : b; }
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
[[nodiscard]] T abs(T v) { if (v < 0) { return -v; } return v; }
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline void swap(T& t1, T& t2) { const T tmp = t1; t1 = t2; t2 = tmp; }
|
||||||
|
|
||||||
|
// only for x > 0 and y > 0
|
||||||
|
template <typename T>
|
||||||
|
[[nodiscard]] T ceil_positive(T x, T y) { return T( 1 + ((x - 1) / (tp::alnf)y) ); }
|
||||||
|
|
||||||
|
// power
|
||||||
|
template <typename T>
|
||||||
|
[[nodiscard]] T pow(T x, uhalni n) {
|
||||||
|
T out = x; for (uhalni i = 0; i < n - 1; i++) { out = out * x; }
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Type>
|
||||||
|
halni nDig10(Type val) {
|
||||||
|
val = abs(val); halni out = 0;
|
||||||
|
while (val != 0) { val = halni(val / 10); out++; }
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
||||||
198
BaseModule/public/Environment.hpp
Normal file
198
BaseModule/public/Environment.hpp
Normal file
|
|
@ -0,0 +1,198 @@
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <climits>
|
||||||
|
#include <cfloat>
|
||||||
|
|
||||||
|
namespace tp {
|
||||||
|
|
||||||
|
class Environment {
|
||||||
|
public:
|
||||||
|
|
||||||
|
enum class Arch { UNDEF, INTEL, ARM } mArch = Arch::UNDEF;
|
||||||
|
|
||||||
|
// Build Type
|
||||||
|
#if defined(__DEBUG__) || defined(_DEBUG) || defined(DEBUG) || !defined(NDEBUG)
|
||||||
|
#define ENV_BUILD_DEBUG
|
||||||
|
enum class BuildType { UNDEF, DEBUG, RELEASE } mBuildType = BuildType::DEBUG;
|
||||||
|
#else
|
||||||
|
#define ENV_BUILD_RELEASE
|
||||||
|
enum class BuildType { UNDEF, DEBUG, RELEASE } mBuildType = BuildType::RELEASE;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// MCVS
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#define ENV_COMPILER_MSVC
|
||||||
|
enum class Toolchain { UNDEF, GNU, LLVM, MSVC } mToolchain = Toolchain::MSVC;
|
||||||
|
|
||||||
|
// VERSION
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
// TARGET OS
|
||||||
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
|
#define ENV_OS_WINDOWS
|
||||||
|
enum class OS { UNDEF, LINUX, WINDOWS, ANDROID, IOS } mOS = OS::WINDOWS;
|
||||||
|
#else
|
||||||
|
enum class OS { UNDEF, LINUX, WINDOWS, ANDROID, IOS } mOS = OS::UNDEF;
|
||||||
|
#error "unexplored compilation to os target"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// TARGET ALIGNED SIZE
|
||||||
|
#ifdef _WIN64
|
||||||
|
enum class ArchWidth { UNDEF, X64, X32 } mWidth = ArchWidth::X64;
|
||||||
|
#define ENV_BITS_64
|
||||||
|
#else
|
||||||
|
enum class ArchWidth { UNDEF, X64, X32 } mWidth = ArchWidth::X32;
|
||||||
|
#define ENV_BITS_32
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// GCC
|
||||||
|
#if defined(__GNUC__) && !defined(__llvm__) && !defined(__INTEL_COMPILER)
|
||||||
|
#define ENV_COMPILER_GCC
|
||||||
|
enum class Toolchain { UNDEF, GNU, LLVM, MSVC } mToolchain = Toolchain::GNU;
|
||||||
|
|
||||||
|
// VERSION
|
||||||
|
#if (__GNUC___ > 5 || (__GNUC__ == 5 && __GNUC_MINOR__ >= 1))
|
||||||
|
// TODO
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// TARGET OS
|
||||||
|
#if defined(__linux__) && !defined(__ANDROID__)
|
||||||
|
#define ENV_OS_LINUX
|
||||||
|
enum class OS { UNDEF, LINUX, WINDOWS, ANDROID, IOS } mOS = OS::LINUX;
|
||||||
|
#else
|
||||||
|
#error "unexplored compilation to os target"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// TARGET ALIGNED SIZE
|
||||||
|
#if defined(__aarch64__) || defined(__x86_64__) || defined(__ARM_64BIT_STATE)
|
||||||
|
#define ENV_BITS_64
|
||||||
|
enum class ArchWidth { UNDEF, X64, X32 } mWidth = ArchWidth::X64;
|
||||||
|
#elif defined(__x86_64__) || defined(i386)
|
||||||
|
#define ENV_BITS_32
|
||||||
|
enum class ArchWidth { UNDEF, X64, X32 } mWidth = ArchWidth::X32;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// CLANG
|
||||||
|
#if defined(__clang__) && !defined(ENV_COMPILER_GCC)
|
||||||
|
#define ENV_COMPILER_CLANG
|
||||||
|
enum class Toolchain { UNDEF, GNU, LLVM, MSVC } mToolchain = Toolchain::LLVM;
|
||||||
|
|
||||||
|
// VERSION
|
||||||
|
#if (__clang_major__ > 5 || (__GNUC__ == 5 && __GNUC_MINOR__ >= 1))
|
||||||
|
// TODO
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// TARGET OS
|
||||||
|
#if defined(__linux__) && !defined(__ANDROID__)
|
||||||
|
#define ENV_OS_LINUX
|
||||||
|
enum class OS { UNDEF, LINUX, WINDOWS, ANDROID, IOS } mOS = OS::LINUX;
|
||||||
|
#elif defined(__ANDROID__)
|
||||||
|
enum class OS { UNDEF, LINUX, WINDOWS, ANDROID, IOS } mOS = OS::ANDOID;
|
||||||
|
#define ENV_OS_ANDROID
|
||||||
|
#else
|
||||||
|
#error "unexplored compilation to target os"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// TARGET ALIGNED SIZE
|
||||||
|
#if defined(__aarch64__) || defined(__x86_64__) || defined(__ARM_64BIT_STATE)
|
||||||
|
#define ENV_BITS_64
|
||||||
|
enum class ArchWidth { UNDEF, X64, X32 } mWidth = ArchWidth::X64;
|
||||||
|
#elif defined(__x86_64__) || defined(i386)
|
||||||
|
#define ENV_BITS_32
|
||||||
|
enum class ArchWidth { UNDEF, X64, X32 } mWidth = ArchWidth::X32;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__EMSCRIPTEN__) || defined(__MINGW32__) || defined(__MINGW32__) || defined(__MINGW64__)
|
||||||
|
#error "compiler is not supported"
|
||||||
|
#else
|
||||||
|
#if !(defined(ENV_COMPILER_MSVC) || defined(ENV_COMPILER_CLANG) || defined(ENV_COMPILER_GCC))
|
||||||
|
// Linux and Linux - derived __linux__
|
||||||
|
// Darwin(Mac OS X and iOS) __APPLE__
|
||||||
|
// Akaros __ros__
|
||||||
|
// NaCL __native_client__
|
||||||
|
// AsmJS __asmjs__
|
||||||
|
// Fuschia __Fuchsia__
|
||||||
|
#error "unknown compiler"
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void log() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifndef ENV_BITS_64
|
||||||
|
#error "ERROR - not 64 bit archytectures are out of support"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const extern Environment gEnvironment;
|
||||||
|
|
||||||
|
typedef char int1;
|
||||||
|
typedef unsigned char uint1;
|
||||||
|
typedef short int2;
|
||||||
|
typedef unsigned short uint2;
|
||||||
|
typedef unsigned int uint4;
|
||||||
|
typedef uint2 ufalni;
|
||||||
|
typedef uint4 uhalni;
|
||||||
|
typedef int int4;
|
||||||
|
typedef int4 halni;
|
||||||
|
typedef unsigned long long uint8;
|
||||||
|
typedef uint8 ualni;
|
||||||
|
typedef long long int8;
|
||||||
|
typedef int8 alni;
|
||||||
|
typedef double flt8;
|
||||||
|
typedef flt8 alnf;
|
||||||
|
typedef float flt4;
|
||||||
|
typedef flt4 halnf;
|
||||||
|
|
||||||
|
#define ENV_INT1_MAX tp::int1( 0x7f)
|
||||||
|
#define ENV_INT1_MIN (-tp::int1(0x80))
|
||||||
|
#define ENV_UINT1_MAX tp::uint1(0xff)
|
||||||
|
#define ENV_UINT1_MIN tp::uint1(0x00)
|
||||||
|
#define ENV_INT2_MAX tp::int2( 0x7fff)
|
||||||
|
#define ENV_INT2_MIN (-tp::int2(0x8000))
|
||||||
|
#define ENV_UINT2_MAX tp::uint2(0xffff)
|
||||||
|
#define ENV_UINT2_MIN tp::uint2(0x0000)
|
||||||
|
#define ENV_UHALNI_MAX tp::uhalni(0xffffffff)
|
||||||
|
#define ENV_UHALNI_MIN tp::uhalni(0x00000000)
|
||||||
|
#define ENV_HALNI_MAX tp::halni( 0x7fffffff)
|
||||||
|
#define ENV_HALNI_MIN (-tp::halni(0x80000000))
|
||||||
|
#define ENV_UALNI_MAX tp::ualni(0xffffffffffffffff)
|
||||||
|
#define ENV_UALNI_MIN tp::ualni(0x0000000000000000)
|
||||||
|
#define ENV_ALNI_MAX tp::alni( 0x7fffffffffffffff)
|
||||||
|
#define ENV_ALNI_MIN (-tp::alni(0x8000000000000000))
|
||||||
|
#define ENV_ALNI_SIZE_B (8)
|
||||||
|
#define ENV_ALNF_DECIMAL_DIG DBL_DECIMAL_DIG
|
||||||
|
#define ENV_ALNF_DIG DBL_DIG
|
||||||
|
#define ENV_ALNF_EPSILON DBL_EPSILON
|
||||||
|
#define ENV_ALNF_HAS_SUBNORM DBL_HAS_SUBNORM
|
||||||
|
#define ENV_ALNF_MANT_DIG DBL_MANT_DIG
|
||||||
|
#define ENV_ALNF_MAX DBL_MAX
|
||||||
|
#define ENV_ALNF_MAX_10_EXP DBL_MAX_10_EXP
|
||||||
|
#define ENV_ALNF_MAX_EXP DBL_MAX_EXP
|
||||||
|
#define ENV_ALNF_MIN DBL_MIN
|
||||||
|
#define ENV_ALNF_MIN_10_EXP DBL_MIN_10_EXP
|
||||||
|
#define ENV_ALNF_MIN_EXP DBL_MIN_EXP
|
||||||
|
#define ENV_ALNF_RADIX _DBL_RADIX
|
||||||
|
#define ENV_ALNF_TRUE_MIN DBL_TRUE_MIN
|
||||||
|
#define ENV_HALNF_DECIMAL_DIG FLT_DECIMAL_DIG
|
||||||
|
#define ENV_HALNF_DIG FLT_DIG
|
||||||
|
#define ENV_HALNF_EPSILON FLT_EPSILON
|
||||||
|
#define ENV_HALNF_HAS_SUBNORM FLT_HAS_SUBNORM
|
||||||
|
#define ENV_HALNF_GUARD FLT_GUARD
|
||||||
|
#define ENV_HALNF_MANT_DIG FLT_MANT_DIG
|
||||||
|
#define ENV_HALNF_MAX FLT_MAX
|
||||||
|
#define ENV_HALNF_MAX_10_EXP FLT_MAX_10_EXP
|
||||||
|
#define ENV_HALNF_MAX_EXP FLT_MAX_EXP
|
||||||
|
#define ENV_HALNF_MIN FLT_MIN
|
||||||
|
#define ENV_HALNF_MIN_10_EXP FLT_MIN_10_EXP
|
||||||
|
#define ENV_HALNF_MIN_EXP FLT_MIN_EXP
|
||||||
|
#define ENV_HALNF_NORMALIZE FLT_NORMALIZE
|
||||||
|
#define ENV_HALNF_RADIX FLT_RADIX
|
||||||
|
#define ENV_HALNF_TRUE_MIN FLT_TRUE_MIN
|
||||||
|
};
|
||||||
927
BaseModule/public/TypeInfo.hpp
Normal file
927
BaseModule/public/TypeInfo.hpp
Normal file
|
|
@ -0,0 +1,927 @@
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// The Loki Library
|
||||||
|
// Copyright (c) 2001 by Andrei Alexandrescu
|
||||||
|
// This code accompanies the book:
|
||||||
|
// Alexandrescu, Andrei. "Modern C++ Design: Generic Programming and Design
|
||||||
|
// Patterns Applied". Copyright (c) 2001. Addison-Wesley.
|
||||||
|
// Code covered by the MIT License
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
namespace tp {
|
||||||
|
template <bool flag, typename T, typename U>
|
||||||
|
struct TypeSelect {
|
||||||
|
typedef T Result;
|
||||||
|
};
|
||||||
|
template <typename T, typename U>
|
||||||
|
struct TypeSelect<false, T, U> {
|
||||||
|
typedef U Result;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace tp {
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// class template IsSameType
|
||||||
|
// Return true iff two given types are the same
|
||||||
|
// Invocation: SameType<T, U>::value
|
||||||
|
// where:
|
||||||
|
// T and U are types
|
||||||
|
// Result evaluates to true iff U == T (types equal)
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
template <typename T, typename U>
|
||||||
|
struct IsSameType {
|
||||||
|
enum { value = false };
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct IsSameType<T, T> {
|
||||||
|
enum { value = true };
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Helper types Small and Big - guarantee that sizeof(Small) < sizeof(Big)
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
namespace Private {
|
||||||
|
template <class T, class U>
|
||||||
|
struct ConversionHelper {
|
||||||
|
typedef char Small;
|
||||||
|
struct Big { char dummy[2]; };
|
||||||
|
static Big Test(...);
|
||||||
|
static Small Test(U);
|
||||||
|
static T MakeT();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// class template Conversion
|
||||||
|
// Figures out the conversion relationships between two types
|
||||||
|
// Invocations (T and U are types):
|
||||||
|
// a) Conversion<T, U>::exists
|
||||||
|
// returns (at compile time) true if there is an implicit conversion from T
|
||||||
|
// to U (example: Derived to Base)
|
||||||
|
// b) Conversion<T, U>::exists2Way
|
||||||
|
// returns (at compile time) true if there are both conversions from T
|
||||||
|
// to U and from U to T (example: int to char and back)
|
||||||
|
// c) Conversion<T, U>::sameType
|
||||||
|
// returns (at compile time) true if T and U represent the same type
|
||||||
|
//
|
||||||
|
// Caveat: might not work if T and U are in a private inheritance hierarchy.
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
template <class T, class U>
|
||||||
|
struct Conversion {
|
||||||
|
typedef Private::ConversionHelper<T, U> H;
|
||||||
|
#ifndef __MWERKS__
|
||||||
|
enum { exists = sizeof(typename H::Small) == sizeof((H::Test(H::MakeT()))) };
|
||||||
|
#else
|
||||||
|
enum { exists = false };
|
||||||
|
#endif
|
||||||
|
enum { exists2Way = exists && Conversion<U, T>::exists };
|
||||||
|
enum { sameType = false };
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct Conversion<T, T> {
|
||||||
|
enum { exists = 1, exists2Way = 1, sameType = 1 };
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct Conversion<void, T> {
|
||||||
|
enum { exists = 0, exists2Way = 0, sameType = 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct Conversion<T, void> {
|
||||||
|
enum { exists = 0, exists2Way = 0, sameType = 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct Conversion<void, void> {
|
||||||
|
public:
|
||||||
|
enum { exists = 1, exists2Way = 1, sameType = 1 };
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// class template SuperSubclass
|
||||||
|
// Invocation: SuperSubclass<B, D>::value where B and D are types.
|
||||||
|
// Returns true if B is a public base of D, or if B and D are aliases of the
|
||||||
|
// same type.
|
||||||
|
//
|
||||||
|
// Caveat: might not work if T and U are in a private inheritance hierarchy.
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
template <class T, class U>
|
||||||
|
struct SuperSubclass {
|
||||||
|
enum {
|
||||||
|
value = (::tp::Conversion<const volatile U*, const volatile T*>::exists &&
|
||||||
|
!::tp::Conversion<const volatile T*, const volatile void*>::sameType)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Dummy enum to make sure that both classes are fully defined.
|
||||||
|
enum { dontUseWithIncompleteTypes = (sizeof(T) == sizeof(U)) };
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct SuperSubclass<void, void> {
|
||||||
|
enum { value = false };
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class U>
|
||||||
|
struct SuperSubclass<void, U> {
|
||||||
|
enum {
|
||||||
|
value = (::tp::Conversion<const volatile U*, const volatile void*>::exists &&
|
||||||
|
!::tp::Conversion<const volatile void*, const volatile void*>::sameType)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Dummy enum to make sure that both classes are fully defined.
|
||||||
|
enum { dontUseWithIncompleteTypes = (0 == sizeof(U)) };
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct SuperSubclass<T, void> {
|
||||||
|
enum {
|
||||||
|
value = (::tp::Conversion<const volatile void*, const volatile T*>::exists &&
|
||||||
|
!::tp::Conversion<const volatile T*, const volatile void*>::sameType)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Dummy enum to make sure that both classes are fully defined.
|
||||||
|
enum { dontUseWithIncompleteTypes = (sizeof(T) == 0) };
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// class template SuperSubclassStrict
|
||||||
|
// Invocation: SuperSubclassStrict<B, D>::value where B and D are types.
|
||||||
|
// Returns true if B is a public base of D.
|
||||||
|
//
|
||||||
|
// Caveat: might not work if T and U are in a private inheritance hierarchy.
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
template<class T, class U>
|
||||||
|
struct SuperSubclassStrict {
|
||||||
|
enum {
|
||||||
|
value = (::tp::Conversion<const volatile U*, const volatile T*>::exists &&
|
||||||
|
!::tp::Conversion<const volatile T*, const volatile void*>::sameType &&
|
||||||
|
!::tp::Conversion<const volatile T*, const volatile U*>::sameType)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Dummy enum to make sure that both classes are fully defined.
|
||||||
|
enum { dontUseWithIncompleteTypes = (sizeof(T) == sizeof(U)) };
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct SuperSubclassStrict<void, void> {
|
||||||
|
enum { value = false };
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class U>
|
||||||
|
struct SuperSubclassStrict<void, U> {
|
||||||
|
enum {
|
||||||
|
value = (::tp::Conversion<const volatile U*, const volatile void*>::exists &&
|
||||||
|
!::tp::Conversion<const volatile void*, const volatile void*>::sameType &&
|
||||||
|
!::tp::Conversion<const volatile void*, const volatile U*>::sameType)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Dummy enum to make sure that both classes are fully defined.
|
||||||
|
enum { dontUseWithIncompleteTypes = (0 == sizeof(U)) };
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct SuperSubclassStrict<T, void> {
|
||||||
|
enum {
|
||||||
|
value = (::tp::Conversion<const volatile void*, const volatile T*>::exists &&
|
||||||
|
!::tp::Conversion<const volatile T*, const volatile void*>::sameType &&
|
||||||
|
!::tp::Conversion<const volatile T*, const volatile void*>::sameType)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Dummy enum to make sure that both classes are fully defined.
|
||||||
|
enum { dontUseWithIncompleteTypes = (sizeof(T) == 0) };
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace tp
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// macro SUPERSUBCLASS
|
||||||
|
// Invocation: SUPERSUBCLASS(B, D) where B and D are types.
|
||||||
|
// Returns true if B is a public base of D, or if B and D are aliases of the
|
||||||
|
// same type.
|
||||||
|
//
|
||||||
|
// Caveat: might not work if T and U are in a private inheritance hierarchy.
|
||||||
|
// Deprecated: Use SuperSubclass class template instead.
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#define SUPERSUBCLASS(T, U) ::tp::SuperSubclass<T,U>::value
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// macro SUPERSUBCLASS_STRICT
|
||||||
|
// Invocation: SUPERSUBCLASS(B, D) where B and D are types.
|
||||||
|
// Returns true if B is a public base of D.
|
||||||
|
//
|
||||||
|
// Caveat: might not work if T and U are in a private inheritance hierarchy.
|
||||||
|
// Deprecated: Use SuperSubclassStrict class template instead.
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#define SUPERSUBCLASS_STRICT(T, U) ::tp::SuperSubclassStrict<T,U>::value
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
namespace tp {
|
||||||
|
|
||||||
|
struct NullType {};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// class template Typelist
|
||||||
|
// The building block of typelists of any length
|
||||||
|
// Use it through the LOKI_TYPELIST_NN macros
|
||||||
|
// Defines nested types:
|
||||||
|
// Head (first element, a non-typelist type by convention)
|
||||||
|
// Tail (second element, can be another typelist)
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
template <class T, class U>
|
||||||
|
struct Typelist {
|
||||||
|
typedef T Head;
|
||||||
|
typedef U Tail;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Typelist utility algorithms
|
||||||
|
|
||||||
|
namespace TL {
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// class template MakeTypelist
|
||||||
|
// Takes a number of arguments equal to its numeric suffix
|
||||||
|
// The arguments are type names.
|
||||||
|
// MakeTypelist<T1, T2, ...>::Result
|
||||||
|
// returns a typelist that is of T1, T2, ...
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
template
|
||||||
|
<
|
||||||
|
typename T1 = NullType, typename T2 = NullType, typename T3 = NullType,
|
||||||
|
typename T4 = NullType, typename T5 = NullType, typename T6 = NullType,
|
||||||
|
typename T7 = NullType, typename T8 = NullType, typename T9 = NullType,
|
||||||
|
typename T10 = NullType, typename T11 = NullType, typename T12 = NullType,
|
||||||
|
typename T13 = NullType, typename T14 = NullType, typename T15 = NullType,
|
||||||
|
typename T16 = NullType, typename T17 = NullType, typename T18 = NullType
|
||||||
|
>
|
||||||
|
struct MakeTypelist {
|
||||||
|
private:
|
||||||
|
typedef typename MakeTypelist
|
||||||
|
<
|
||||||
|
T2, T3, T4,
|
||||||
|
T5, T6, T7,
|
||||||
|
T8, T9, T10,
|
||||||
|
T11, T12, T13,
|
||||||
|
T14, T15, T16,
|
||||||
|
T17, T18
|
||||||
|
>
|
||||||
|
::Result TailResult;
|
||||||
|
|
||||||
|
public:
|
||||||
|
typedef Typelist<T1, TailResult> Result;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct MakeTypelist<> {
|
||||||
|
typedef NullType Result;
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// class template Length
|
||||||
|
// Computes the length of a typelist
|
||||||
|
// Invocation (TList is a typelist):
|
||||||
|
// Length<TList>::value
|
||||||
|
// returns a compile-time constant containing the length of TList, not counting
|
||||||
|
// the end terminator (which by convention is NullType)
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
template <class TList> struct Length;
|
||||||
|
template <> struct Length<NullType> {
|
||||||
|
enum { value = 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T, class U>
|
||||||
|
struct Length< Typelist<T, U> > {
|
||||||
|
enum { value = 1 + Length<U>::value };
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// class template TypeAt
|
||||||
|
// Finds the type at a given index in a typelist
|
||||||
|
// Invocation (TList is a typelist and index is a compile-time integral
|
||||||
|
// constant):
|
||||||
|
// TypeAt<TList, index>::Result
|
||||||
|
// returns the type in position 'index' in TList
|
||||||
|
// If you pass an out-of-bounds index, the result is a compile-time error
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
template <class TList, unsigned int index> struct TypeAt;
|
||||||
|
|
||||||
|
template <class Head, class Tail>
|
||||||
|
struct TypeAt<Typelist<Head, Tail>, 0> {
|
||||||
|
typedef Head Result;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class Head, class Tail, unsigned int i>
|
||||||
|
struct TypeAt<Typelist<Head, Tail>, i> {
|
||||||
|
typedef typename TypeAt<Tail, i - 1>::Result Result;
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// class template TypeAtNonStrict
|
||||||
|
// Finds the type at a given index in a typelist
|
||||||
|
// Invocations (TList is a typelist and index is a compile-time integral
|
||||||
|
// constant):
|
||||||
|
// a) TypeAt<TList, index>::Result
|
||||||
|
// returns the type in position 'index' in TList, or NullType if index is
|
||||||
|
// out-of-bounds
|
||||||
|
// b) TypeAt<TList, index, D>::Result
|
||||||
|
// returns the type in position 'index' in TList, or D if index is out-of-bounds
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
template <class TList, unsigned int index,
|
||||||
|
typename DefaultType = NullType>
|
||||||
|
struct TypeAtNonStrict {
|
||||||
|
typedef DefaultType Result;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class Head, class Tail, typename DefaultType>
|
||||||
|
struct TypeAtNonStrict<Typelist<Head, Tail>, 0, DefaultType> {
|
||||||
|
typedef Head Result;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class Head, class Tail, unsigned int i, typename DefaultType>
|
||||||
|
struct TypeAtNonStrict<Typelist<Head, Tail>, i, DefaultType> {
|
||||||
|
typedef typename
|
||||||
|
TypeAtNonStrict<Tail, i - 1, DefaultType>::Result Result;
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// class template IndexOf
|
||||||
|
// Finds the index of a type in a typelist
|
||||||
|
// Invocation (TList is a typelist and T is a type):
|
||||||
|
// IndexOf<TList, T>::value
|
||||||
|
// returns the position of T in TList, or NullType if T is not found in TList
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
template <class TList, class T> struct IndexOf;
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct IndexOf<NullType, T> {
|
||||||
|
enum { value = -1 };
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T, class Tail>
|
||||||
|
struct IndexOf<Typelist<T, Tail>, T> {
|
||||||
|
enum { value = 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class Head, class Tail, class T>
|
||||||
|
struct IndexOf<Typelist<Head, Tail>, T> {
|
||||||
|
private:
|
||||||
|
enum { temp = IndexOf<Tail, T>::value };
|
||||||
|
public:
|
||||||
|
enum { value = (temp == -1 ? -1 : 1 + temp) };
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// class template Append
|
||||||
|
// Appends a type or a typelist to another
|
||||||
|
// Invocation (TList is a typelist and T is either a type or a typelist):
|
||||||
|
// Append<TList, T>::Result
|
||||||
|
// returns a typelist that is TList followed by T and NullType-terminated
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
template <class TList, class T> struct Append;
|
||||||
|
|
||||||
|
template <> struct Append<NullType, NullType> {
|
||||||
|
typedef NullType Result;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T> struct Append<NullType, T> {
|
||||||
|
typedef Typelist<T, NullType> Result;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class Head, class Tail>
|
||||||
|
struct Append<NullType, Typelist<Head, Tail> > {
|
||||||
|
typedef Typelist<Head, Tail> Result;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class Head, class Tail, class T>
|
||||||
|
struct Append<Typelist<Head, Tail>, T> {
|
||||||
|
typedef Typelist<Head,
|
||||||
|
typename Append<Tail, T>::Result>
|
||||||
|
Result;
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// class template Erase
|
||||||
|
// Erases the first occurence, if any, of a type in a typelist
|
||||||
|
// Invocation (TList is a typelist and T is a type):
|
||||||
|
// Erase<TList, T>::Result
|
||||||
|
// returns a typelist that is TList without the first occurence of T
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
template <class TList, class T> struct Erase;
|
||||||
|
|
||||||
|
template <class T> // Specialization 1
|
||||||
|
struct Erase<NullType, T> {
|
||||||
|
typedef NullType Result;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T, class Tail> // Specialization 2
|
||||||
|
struct Erase<Typelist<T, Tail>, T> {
|
||||||
|
typedef Tail Result;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class Head, class Tail, class T> // Specialization 3
|
||||||
|
struct Erase<Typelist<Head, Tail>, T> {
|
||||||
|
typedef Typelist<Head,
|
||||||
|
typename Erase<Tail, T>::Result>
|
||||||
|
Result;
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// class template EraseAll
|
||||||
|
// Erases all first occurences, if any, of a type in a typelist
|
||||||
|
// Invocation (TList is a typelist and T is a type):
|
||||||
|
// EraseAll<TList, T>::Result
|
||||||
|
// returns a typelist that is TList without any occurence of T
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
template <class TList, class T> struct EraseAll;
|
||||||
|
template <class T>
|
||||||
|
struct EraseAll<NullType, T> {
|
||||||
|
typedef NullType Result;
|
||||||
|
};
|
||||||
|
template <class T, class Tail>
|
||||||
|
struct EraseAll<Typelist<T, Tail>, T> {
|
||||||
|
// Go all the way down the list removing the type
|
||||||
|
typedef typename EraseAll<Tail, T>::Result Result;
|
||||||
|
};
|
||||||
|
template <class Head, class Tail, class T>
|
||||||
|
struct EraseAll<Typelist<Head, Tail>, T> {
|
||||||
|
// Go all the way down the list removing the type
|
||||||
|
typedef Typelist<Head,
|
||||||
|
typename EraseAll<Tail, T>::Result>
|
||||||
|
Result;
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// class template NoDuplicates
|
||||||
|
// Removes all duplicate types in a typelist
|
||||||
|
// Invocation (TList is a typelist):
|
||||||
|
// NoDuplicates<TList, T>::Result
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
template <class TList> struct NoDuplicates;
|
||||||
|
|
||||||
|
template <> struct NoDuplicates<NullType> {
|
||||||
|
typedef NullType Result;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class Head, class Tail>
|
||||||
|
struct NoDuplicates< Typelist<Head, Tail> > {
|
||||||
|
private:
|
||||||
|
typedef typename NoDuplicates<Tail>::Result L1;
|
||||||
|
typedef typename Erase<L1, Head>::Result L2;
|
||||||
|
public:
|
||||||
|
typedef Typelist<Head, L2> Result;
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// class template Replace
|
||||||
|
// Replaces the first occurence of a type in a typelist, with another type
|
||||||
|
// Invocation (TList is a typelist, T, U are types):
|
||||||
|
// Replace<TList, T, U>::Result
|
||||||
|
// returns a typelist in which the first occurence of T is replaced with U
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
template <class TList, class T, class U> struct Replace;
|
||||||
|
|
||||||
|
template <class T, class U>
|
||||||
|
struct Replace<NullType, T, U> {
|
||||||
|
typedef NullType Result;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T, class Tail, class U>
|
||||||
|
struct Replace<Typelist<T, Tail>, T, U> {
|
||||||
|
typedef Typelist<U, Tail> Result;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class Head, class Tail, class T, class U>
|
||||||
|
struct Replace<Typelist<Head, Tail>, T, U> {
|
||||||
|
typedef Typelist<Head,
|
||||||
|
typename Replace<Tail, T, U>::Result>
|
||||||
|
Result;
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// class template ReplaceAll
|
||||||
|
// Replaces all occurences of a type in a typelist, with another type
|
||||||
|
// Invocation (TList is a typelist, T, U are types):
|
||||||
|
// Replace<TList, T, U>::Result
|
||||||
|
// returns a typelist in which all occurences of T is replaced with U
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
template <class TList, class T, class U> struct ReplaceAll;
|
||||||
|
|
||||||
|
template <class T, class U>
|
||||||
|
struct ReplaceAll<NullType, T, U> {
|
||||||
|
typedef NullType Result;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T, class Tail, class U>
|
||||||
|
struct ReplaceAll<Typelist<T, Tail>, T, U> {
|
||||||
|
typedef Typelist<U, typename ReplaceAll<Tail, T, U>::Result> Result;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class Head, class Tail, class T, class U>
|
||||||
|
struct ReplaceAll<Typelist<Head, Tail>, T, U> {
|
||||||
|
typedef Typelist<Head,
|
||||||
|
typename ReplaceAll<Tail, T, U>::Result>
|
||||||
|
Result;
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// class template Reverse
|
||||||
|
// Reverses a typelist
|
||||||
|
// Invocation (TList is a typelist):
|
||||||
|
// Reverse<TList>::Result
|
||||||
|
// returns a typelist that is TList reversed
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
template <class TList> struct Reverse;
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct Reverse<NullType> {
|
||||||
|
typedef NullType Result;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class Head, class Tail>
|
||||||
|
struct Reverse< Typelist<Head, Tail> > {
|
||||||
|
typedef typename Append<
|
||||||
|
typename Reverse<Tail>::Result, Head>::Result Result;
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// class template MostDerived
|
||||||
|
// Finds the type in a typelist that is the most derived from a given type
|
||||||
|
// Invocation (TList is a typelist, T is a type):
|
||||||
|
// MostDerived<TList, T>::Result
|
||||||
|
// returns the type in TList that's the most derived from T
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
template <class TList, class T> struct MostDerived;
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct MostDerived<NullType, T> {
|
||||||
|
typedef T Result;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class Head, class Tail, class T>
|
||||||
|
struct MostDerived<Typelist<Head, Tail>, T> {
|
||||||
|
private:
|
||||||
|
typedef typename MostDerived<Tail, T>::Result Candidate;
|
||||||
|
public:
|
||||||
|
typedef typename TypeSelect<
|
||||||
|
SuperSubclass<Candidate, Head>::value,
|
||||||
|
Head, Candidate>::Result Result;
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// class template DerivedToFront
|
||||||
|
// Arranges the types in a typelist so that the most derived types appear first
|
||||||
|
// Invocation (TList is a typelist):
|
||||||
|
// DerivedToFront<TList>::Result
|
||||||
|
// returns the reordered TList
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
template <class TList> struct DerivedToFront;
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct DerivedToFront<NullType> {
|
||||||
|
typedef NullType Result;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class Head, class Tail>
|
||||||
|
struct DerivedToFront< Typelist<Head, Tail> > {
|
||||||
|
private:
|
||||||
|
typedef typename MostDerived<Tail, Head>::Result
|
||||||
|
TheMostDerived;
|
||||||
|
typedef typename Replace<Tail,
|
||||||
|
TheMostDerived, Head>::Result Temp;
|
||||||
|
typedef typename DerivedToFront<Temp>::Result L;
|
||||||
|
public:
|
||||||
|
typedef Typelist<TheMostDerived, L> Result;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace TL
|
||||||
|
|
||||||
|
|
||||||
|
template <
|
||||||
|
class T01 = NullType, class T02 = NullType, class T03 = NullType, class T04 = NullType, class T05 = NullType,
|
||||||
|
class T06 = NullType, class T07 = NullType, class T08 = NullType, class T09 = NullType, class T10 = NullType,
|
||||||
|
class T11 = NullType, class T12 = NullType, class T13 = NullType, class T14 = NullType, class T15 = NullType,
|
||||||
|
class T16 = NullType, class T17 = NullType, class T18 = NullType, class T19 = NullType, class T20 = NullType
|
||||||
|
>
|
||||||
|
class Seq {
|
||||||
|
typedef typename Seq< T02, T03, T04, T05, T06, T07, T08, T09, T10,
|
||||||
|
T11, T12, T13, T14, T15, T16, T17, T18, T19, T20>::list TailResult;
|
||||||
|
public:
|
||||||
|
typedef Typelist<T01, TailResult> list;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct Seq<> {
|
||||||
|
typedef NullType list;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace tp
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
#define TYPELIST1(T1) ::compiler::Typelist<T1, ::compiler::NullType>
|
||||||
|
#define TYPELIST2(T1, T2) ::compiler::Typelist<T1, TYPELIST1(T2) >
|
||||||
|
#define TYPELIST3(T1, T2, T3) ::compiler::Typelist<T1, TYPELIST2(T2, T3) >
|
||||||
|
#define TYPELIST4(T1, T2, T3, T4) ::compiler::Typelist<T1, TYPELIST3(T2, T3, T4) >
|
||||||
|
#define TYPELIST5(T1, T2, T3, T4, T5) ::compiler::Typelist<T1, TYPELIST4(T2, T3, T4, T5) >
|
||||||
|
#define TYPELIST6(T1, T2, T3, T4, T5, T6) ::compiler::Typelist<T1, TYPELIST5(T2, T3, T4, T5, T6) >
|
||||||
|
#define TYPELIST7(T1, T2, T3, T4, T5, T6, T7) ::compiler::Typelist<T1, TYPELIST6(T2, T3, T4, T5, T6, T7) >
|
||||||
|
#define TYPELIST8(T1, T2, T3, T4, T5, T6, T7, T8) ::compiler::Typelist<T1, TYPELIST7(T2, T3, T4, T5, T6, T7, T8) >
|
||||||
|
#define TYPELIST9(T1, T2, T3, T4, T5, T6, T7, T8, T9) ::compiler::Typelist<T1, TYPELIST8(T2, T3, T4, T5, T6, T7, T8, T9) >
|
||||||
|
#define TYPELIST10(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) ::compiler::Typelist<T1, TYPELIST9(T2, T3, T4, T5, T6, T7, T8, T9, T10) >
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#pragma warning( push )
|
||||||
|
#pragma warning( disable : 4180 ) //qualifier applied to function type has no meaning; ignored
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace tp {
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// class template IsCustomUnsignedInt
|
||||||
|
// Offers a means to integrate nonstandard built-in unsigned integral types
|
||||||
|
// (such as unsigned __int64 or unsigned long long int) with the TypeTraits
|
||||||
|
// class template defined below.
|
||||||
|
// Invocation: IsCustomUnsignedInt<T> where T is any type
|
||||||
|
// Defines 'value', an enum that is 1 iff T is a custom built-in unsigned
|
||||||
|
// integral type
|
||||||
|
// Specialize this class template for nonstandard unsigned integral types
|
||||||
|
// and define value = 1 in those specializations
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct IsCustomUnsignedInt {
|
||||||
|
enum { value = 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// class template IsCustomSignedInt
|
||||||
|
// Offers a means to integrate nonstandard built-in unsigned integral types
|
||||||
|
// (such as unsigned __int64 or unsigned long long int) with the TypeTraits
|
||||||
|
// class template defined below.
|
||||||
|
// Invocation: IsCustomSignedInt<T> where T is any type
|
||||||
|
// Defines 'value', an enum that is 1 iff T is a custom built-in signed
|
||||||
|
// integral type
|
||||||
|
// Specialize this class template for nonstandard unsigned integral types
|
||||||
|
// and define value = 1 in those specializations
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct IsCustomSignedInt {
|
||||||
|
enum { value = 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// class template IsCustomFloat
|
||||||
|
// Offers a means to integrate nonstandard floating point types with the
|
||||||
|
// TypeTraits class template defined below.
|
||||||
|
// Invocation: IsCustomFloat<T> where T is any type
|
||||||
|
// Defines 'value', an enum that is 1 iff T is a custom built-in
|
||||||
|
// floating point type
|
||||||
|
// Specialize this class template for nonstandard unsigned integral types
|
||||||
|
// and define value = 1 in those specializations
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct IsCustomFloat {
|
||||||
|
enum { value = 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Helper types for class template TypeTraits defined below
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
namespace Private {
|
||||||
|
|
||||||
|
typedef Seq<unsigned char, unsigned short int, unsigned int, unsigned long int>::list StdUnsignedInts;
|
||||||
|
typedef Seq<signed char, short int, int, long int>::list StdSignedInts;
|
||||||
|
typedef Seq<bool, char, wchar_t>::list StdOtherInts;
|
||||||
|
typedef Seq<float, double, long double>::list StdFloats;
|
||||||
|
|
||||||
|
template <typename U> struct AddPointer { typedef U* Result; };
|
||||||
|
template <typename U> struct AddPointer<U&> { typedef U* Result; };
|
||||||
|
|
||||||
|
template <class U> struct AddReference { typedef U& Result; };
|
||||||
|
template <class U> struct AddReference<U&> { typedef U& Result; };
|
||||||
|
template <> struct AddReference<void> { typedef NullType Result; };
|
||||||
|
|
||||||
|
template <class U> struct AddParameterType { typedef const U& Result; };
|
||||||
|
template <class U> struct AddParameterType<U&> { typedef U& Result; };
|
||||||
|
template <> struct AddParameterType<void> { typedef NullType Result; };
|
||||||
|
|
||||||
|
}// namespace Private
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// class template TypeTraits
|
||||||
|
//
|
||||||
|
// Figures out at compile time various properties of any given type
|
||||||
|
// Invocations (T is a type, TypeTraits<T>::Property):
|
||||||
|
//
|
||||||
|
// - isPointer : returns true if T is a pointer type
|
||||||
|
// - PointeeType : returns the type to which T points if T is a pointer
|
||||||
|
// type, NullType otherwise
|
||||||
|
// - isReference : returns true if T is a reference type
|
||||||
|
// - ReferredType : returns the type to which T refers if T is a reference
|
||||||
|
// type, NullType otherwise
|
||||||
|
// - isMemberPointer : returns true if T is a pointer to member type
|
||||||
|
// - isStdUnsignedInt: returns true if T is a standard unsigned integral type
|
||||||
|
// - isStdSignedInt : returns true if T is a standard signed integral type
|
||||||
|
// - isStdIntegral : returns true if T is a standard integral type
|
||||||
|
// - isStdFloat : returns true if T is a standard floating-point type
|
||||||
|
// - isStdArith : returns true if T is a standard arithmetic type
|
||||||
|
// - isStdFundamental: returns true if T is a standard fundamental type
|
||||||
|
// - isUnsignedInt : returns true if T is a unsigned integral type
|
||||||
|
// - isSignedInt : returns true if T is a signed integral type
|
||||||
|
// - isIntegral : returns true if T is a integral type
|
||||||
|
// - isFloat : returns true if T is a floating-point type
|
||||||
|
// - isArith : returns true if T is a arithmetic type
|
||||||
|
// - isFundamental : returns true if T is a fundamental type
|
||||||
|
// - ParameterType : returns the optimal type to be used as a parameter for
|
||||||
|
// functions that take Ts
|
||||||
|
// - isConst : returns true if T is a const-qualified type
|
||||||
|
// - NonConstType : Type with removed 'const' qualifier from T, if any
|
||||||
|
// - isVolatile : returns true if T is a volatile-qualified type
|
||||||
|
// - NonVolatileType : Type with removed 'volatile' qualifier from T, if any
|
||||||
|
// - UnqualifiedType : Type with removed 'const' and 'volatile' qualifiers from
|
||||||
|
// T, if any
|
||||||
|
// - ParameterType : returns the optimal type to be used as a parameter
|
||||||
|
// for functions that take 'const T's
|
||||||
|
//
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class TypeTraits {
|
||||||
|
private:
|
||||||
|
|
||||||
|
template <class U> struct ReferenceTraits {
|
||||||
|
enum { result = false };
|
||||||
|
typedef U ReferredType;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class U> struct ReferenceTraits<U&> {
|
||||||
|
enum { result = true };
|
||||||
|
typedef U ReferredType;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class U> struct PointerTraits {
|
||||||
|
enum { result = false };
|
||||||
|
typedef NullType PointeeType;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class U> struct PointerTraits<U*> {
|
||||||
|
enum { result = true };
|
||||||
|
typedef U PointeeType;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class U> struct PointerTraits<U*&> {
|
||||||
|
enum { result = true };
|
||||||
|
typedef U PointeeType;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class U> struct PToMTraits {
|
||||||
|
enum { result = false };
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class U, class V> struct PToMTraits<U V::*> {
|
||||||
|
enum { result = true };
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class U, class V> struct PToMTraits<U V::*&> {
|
||||||
|
enum { result = true };
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class U> struct UnConst {
|
||||||
|
typedef U Result;
|
||||||
|
enum { isConst = 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class U> struct UnConst<const U> {
|
||||||
|
typedef U Result;
|
||||||
|
enum { isConst = 1 };
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class U> struct UnConst<const U&> {
|
||||||
|
typedef U& Result;
|
||||||
|
enum { isConst = 1 };
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class U> struct UnVolatile {
|
||||||
|
typedef U Result;
|
||||||
|
enum { isVolatile = 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class U> struct UnVolatile<volatile U> {
|
||||||
|
typedef U Result;
|
||||||
|
enum { isVolatile = 1 };
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class U> struct UnVolatile<volatile U&> {
|
||||||
|
typedef U& Result;
|
||||||
|
enum { isVolatile = 1 };
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
typedef typename UnConst<T>::Result
|
||||||
|
NonConstType;
|
||||||
|
typedef typename UnVolatile<T>::Result
|
||||||
|
NonVolatileType;
|
||||||
|
typedef typename UnVolatile<typename UnConst<T>::Result>::Result
|
||||||
|
UnqualifiedType;
|
||||||
|
typedef typename PointerTraits<UnqualifiedType>::PointeeType
|
||||||
|
PointeeType;
|
||||||
|
typedef typename ReferenceTraits<T>::ReferredType
|
||||||
|
ReferredType;
|
||||||
|
|
||||||
|
enum { isConst = UnConst<T>::isConst };
|
||||||
|
enum { isVolatile = UnVolatile<T>::isVolatile };
|
||||||
|
enum { isReference = ReferenceTraits<UnqualifiedType>::result };
|
||||||
|
enum { isMemberPointer = PToMTraits<typename ReferenceTraits<UnqualifiedType>::ReferredType >::result };
|
||||||
|
enum { isPointer = PointerTraits<typename ReferenceTraits<UnqualifiedType>::ReferredType >::result };
|
||||||
|
|
||||||
|
enum {
|
||||||
|
isStdUnsignedInt = TL::IndexOf<Private::StdUnsignedInts, UnqualifiedType>::value >= 0 ||
|
||||||
|
TL::IndexOf<Private::StdUnsignedInts,
|
||||||
|
typename ReferenceTraits<UnqualifiedType>::ReferredType>::value >= 0
|
||||||
|
};
|
||||||
|
enum {
|
||||||
|
isStdSignedInt = TL::IndexOf<Private::StdSignedInts, UnqualifiedType>::value >= 0 ||
|
||||||
|
TL::IndexOf<Private::StdSignedInts,
|
||||||
|
typename ReferenceTraits<UnqualifiedType>::ReferredType>::value >= 0
|
||||||
|
};
|
||||||
|
enum {
|
||||||
|
isStdIntegral = isStdUnsignedInt || isStdSignedInt ||
|
||||||
|
TL::IndexOf<Private::StdOtherInts, UnqualifiedType>::value >= 0 ||
|
||||||
|
TL::IndexOf<Private::StdOtherInts,
|
||||||
|
typename ReferenceTraits<UnqualifiedType>::ReferredType>::value >= 0
|
||||||
|
};
|
||||||
|
enum {
|
||||||
|
isStdFloat = TL::IndexOf<Private::StdFloats, UnqualifiedType>::value >= 0 ||
|
||||||
|
TL::IndexOf<Private::StdFloats,
|
||||||
|
typename ReferenceTraits<UnqualifiedType>::ReferredType>::value >= 0
|
||||||
|
};
|
||||||
|
|
||||||
|
enum { isStdArith = isStdIntegral || isStdFloat };
|
||||||
|
enum { isStdFundamental = isStdArith || isStdFloat || Conversion<T, void>::sameType };
|
||||||
|
|
||||||
|
enum { isUnsignedInt = isStdUnsignedInt || IsCustomUnsignedInt<UnqualifiedType>::value };
|
||||||
|
enum { isSignedInt = isStdSignedInt || IsCustomSignedInt<UnqualifiedType>::value };
|
||||||
|
enum { isIntegral = isStdIntegral || isUnsignedInt || isSignedInt };
|
||||||
|
enum { isFloat = isStdFloat || IsCustomFloat<UnqualifiedType>::value };
|
||||||
|
enum { isArith = isIntegral || isFloat };
|
||||||
|
enum { isFundamental = isStdFundamental || isArith };
|
||||||
|
|
||||||
|
typedef typename TypeSelect<isStdArith || isPointer || isMemberPointer, T, typename Private::AddParameterType<T>::Result>::Result
|
||||||
|
ParameterType;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#pragma warning( pop )
|
||||||
|
#endif // _MSC_VER
|
||||||
15
BaseModule/tests/Tests.cpp
Normal file
15
BaseModule/tests/Tests.cpp
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
|
||||||
|
#include "BaseModule.hpp"
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
tp::ModuleManifest* ModuleDependencies[] = { &tp::gModuleBase, nullptr };
|
||||||
|
tp::ModuleManifest TestModule("Test", nullptr, nullptr, ModuleDependencies);
|
||||||
|
|
||||||
|
if (!TestModule.initialize()) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT(tp::gEnvironment.mWidth == tp::Environment::ArchWidth::X64);
|
||||||
|
|
||||||
|
TestModule.deinitialize();
|
||||||
|
}
|
||||||
16
CMakeLists.txt
Normal file
16
CMakeLists.txt
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
cmake_minimum_required(VERSION 3.2)
|
||||||
|
|
||||||
|
set(CMAKE_INSTALL_PREFIX ${CMAKE_CURRENT_SOURCE_DIR}/install)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -gdwarf-4")
|
||||||
|
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
|
project(Types)
|
||||||
|
|
||||||
|
add_compile_definitions(MEM_DEBUG)
|
||||||
|
|
||||||
|
add_subdirectory(BaseModule)
|
||||||
|
add_subdirectory(Utils)
|
||||||
|
add_subdirectory(Containers)
|
||||||
|
#add_subdirectory(Allocators)
|
||||||
22
Containers/CMakeLists.txt
Normal file
22
Containers/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.2)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
|
|
||||||
|
project(Containers)
|
||||||
|
|
||||||
|
### ---------------------- Static Library --------------------- ###
|
||||||
|
file(GLOB SOURCES "./private/*.cpp")
|
||||||
|
file(GLOB HEADERS "./public/*.hpp")
|
||||||
|
add_library(${PROJECT_NAME} STATIC ${SOURCES} ${HEADERS})
|
||||||
|
target_include_directories(${PROJECT_NAME} PUBLIC ./public/)
|
||||||
|
target_link_libraries(${PROJECT_NAME} PUBLIC BaseModule)
|
||||||
|
|
||||||
|
### -------------------------- Tests -------------------------- ###
|
||||||
|
enable_testing()
|
||||||
|
file(GLOB TEST_SOURCES "./tests/*.cpp")
|
||||||
|
add_executable(${PROJECT_NAME}Tests ${TEST_SOURCES})
|
||||||
|
target_link_libraries(${PROJECT_NAME}Tests ${PROJECT_NAME} Utils)
|
||||||
|
add_test(NAME ${PROJECT_NAME}Tests COMMAND ${PROJECT_NAME}Tests)
|
||||||
|
|
||||||
|
install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/${PROJECT_NAME}/lib)
|
||||||
18
Containers/private/Containers.cpp
Normal file
18
Containers/private/Containers.cpp
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
|
||||||
|
#include "ContainersCommon.hpp"
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
namespace tp {
|
||||||
|
|
||||||
|
static ModuleManifest* sModuleDependencies[] = { &gModuleBase,nullptr };
|
||||||
|
ModuleManifest gModuleContainers = ModuleManifest("Containers", nullptr, nullptr, sModuleDependencies);
|
||||||
|
|
||||||
|
void* DefaultAllocator::allocate(ualni size) {
|
||||||
|
return malloc(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DefaultAllocator::deallocate(void* p) {
|
||||||
|
free(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
360
Containers/public/AvlTree.hpp
Normal file
360
Containers/public/AvlTree.hpp
Normal file
|
|
@ -0,0 +1,360 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ContainersCommon.hpp"
|
||||||
|
|
||||||
|
namespace tp {
|
||||||
|
|
||||||
|
template <typename NumericType>
|
||||||
|
struct AvlNumericKey {
|
||||||
|
|
||||||
|
NumericType val;
|
||||||
|
|
||||||
|
AvlNumericKey() = default;
|
||||||
|
AvlNumericKey(NumericType val) : val(val) {}
|
||||||
|
|
||||||
|
inline bool descentRight(AvlNumericKey in) const { return in.val > val; }
|
||||||
|
inline bool descentLeft(AvlNumericKey in) const { return in.val < val; }
|
||||||
|
inline bool exactNode(AvlNumericKey in) const { return in.val == val; }
|
||||||
|
|
||||||
|
inline AvlNumericKey getFindKey(/**/) const { return val; }
|
||||||
|
inline AvlNumericKey keyInRightSubtree(AvlNumericKey in) const { return in.val; }
|
||||||
|
inline AvlNumericKey keyInLeftSubtree(AvlNumericKey in) const { return in.val; }
|
||||||
|
|
||||||
|
inline void updateTreeCacheCallBack() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Key, typename Data, class Allocator = DefaultAllocator>
|
||||||
|
class AvlTree {
|
||||||
|
typedef SelCopyArg<Key> KeyArg;
|
||||||
|
typedef SelCopyArg<Data> DataArg;
|
||||||
|
|
||||||
|
public:
|
||||||
|
class Node {
|
||||||
|
friend AvlTree;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Node(KeyArg aKey, DataArg aData) : key(aKey), data(aData) {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
Data data;
|
||||||
|
Key key;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Node* mLeft = nullptr;
|
||||||
|
Node* mRight = nullptr;
|
||||||
|
Node* mParent = nullptr;
|
||||||
|
ualni mHeight = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
inline bool descentRight(KeyArg aKey) const { return key.descentRight(aKey); }
|
||||||
|
inline bool descentLeft(KeyArg aKey) const { return key.descentRight(aKey); }
|
||||||
|
inline bool exactNode(KeyArg aKey) const { return key.exactNode(aKey); }
|
||||||
|
|
||||||
|
inline KeyArg getFindKey(const Node* node = nullptr) const { return key.getFindKey(/*node*/); }
|
||||||
|
inline KeyArg keyInRightSubtree(KeyArg aKey) const { return key.keyInRightSubtree(aKey); }
|
||||||
|
inline KeyArg keyInLeftSubtree(KeyArg aKey) const { return key.keyInLeftSubtree(aKey); }
|
||||||
|
|
||||||
|
inline void updateTreeCacheCallBack() { key.updateTreeCacheCallBack(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
Node* mRoot = nullptr;
|
||||||
|
ualni mSize = 0;
|
||||||
|
Allocator mAlloc;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
inline void deleteNode(Node* node) {
|
||||||
|
node->~Node();
|
||||||
|
mAlloc.deallocate(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Node* newNode(KeyArg key, DataArg data) {
|
||||||
|
return new (mAlloc.allocate(sizeof(Node))) Node(key, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ualni getNodeHeight(const Node* node) const {
|
||||||
|
return node ? node->mHeight : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns new head
|
||||||
|
Node* rotateLeft(Node* pivot) {
|
||||||
|
DEBUG_ASSERT(pivot);
|
||||||
|
|
||||||
|
Node* const head = pivot;
|
||||||
|
Node* const right = pivot->mRight;
|
||||||
|
Node* const right_left = right->mLeft;
|
||||||
|
Node* const parent = pivot->mParent;
|
||||||
|
|
||||||
|
// parents
|
||||||
|
if (right_left) right_left->mParent = head;
|
||||||
|
head->mParent = right;
|
||||||
|
right->mParent = parent;
|
||||||
|
|
||||||
|
// children
|
||||||
|
head->mRight = right_left;
|
||||||
|
right->mLeft = head;
|
||||||
|
|
||||||
|
// heights
|
||||||
|
head->mHeight = 1 + max(getNodeHeight(head->mLeft), getNodeHeight(head->mRight));
|
||||||
|
right->mHeight = 1 + max(getNodeHeight(right->mLeft), getNodeHeight(right->mRight));
|
||||||
|
|
||||||
|
// cache
|
||||||
|
head->updateTreeCacheCallBack();
|
||||||
|
right->updateTreeCacheCallBack();
|
||||||
|
|
||||||
|
return right;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node* rotateRight(Node* pivot) {
|
||||||
|
DEBUG_ASSERT(pivot);
|
||||||
|
|
||||||
|
Node* const head = pivot;
|
||||||
|
Node* const left = pivot->mLeft;
|
||||||
|
Node* const left_right = left->mRight;
|
||||||
|
Node* const parent = pivot->mParent;
|
||||||
|
|
||||||
|
// parents
|
||||||
|
if (left_right) left_right->mParent = head;
|
||||||
|
head->mParent = left;
|
||||||
|
left->mParent = parent;
|
||||||
|
|
||||||
|
// children
|
||||||
|
head->mLeft = left_right;
|
||||||
|
left->mRight = head;
|
||||||
|
|
||||||
|
// heights
|
||||||
|
head->mHeight = 1 + max(getNodeHeight(head->mLeft), getNodeHeight(head->mRight));
|
||||||
|
left->mHeight = 1 + max(getNodeHeight(left->mLeft), getNodeHeight(left->mRight));
|
||||||
|
|
||||||
|
// cache
|
||||||
|
head->updateTreeCacheCallBack();
|
||||||
|
left->updateTreeCacheCallBack();
|
||||||
|
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
|
||||||
|
// recursively returns valid left or right child or root
|
||||||
|
Node* insertUtil(Node* head, KeyArg key, DataArg data) {
|
||||||
|
|
||||||
|
Node* insertedNode;
|
||||||
|
|
||||||
|
if (head == nullptr) {
|
||||||
|
mSize++;
|
||||||
|
Node* out = newNode(key, data);
|
||||||
|
out->updateTreeCacheCallBack();
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
else if (head->exactNode(key)) {
|
||||||
|
return head;
|
||||||
|
}
|
||||||
|
else if (head->descentRight(key)) {
|
||||||
|
insertedNode = insertUtil(head->mRight, head->keyInRightSubtree(key), data);
|
||||||
|
head->mRight = insertedNode;
|
||||||
|
insertedNode->mParent = head;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
insertedNode = insertUtil(head->mLeft, head->keyInLeftSubtree(key), data);
|
||||||
|
head->mLeft = insertedNode;
|
||||||
|
insertedNode->mParent = head;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update height
|
||||||
|
head->mHeight = 1 + max(getNodeHeight(head->mRight), getNodeHeight(head->mLeft));
|
||||||
|
|
||||||
|
alni balance = alni(getNodeHeight(head->mRight) - getNodeHeight(head->mLeft));
|
||||||
|
|
||||||
|
if (balance > 1) {
|
||||||
|
if (head->mRight->descentRight(head->keyInRightSubtree(key))) {
|
||||||
|
return rotateLeft(head);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
head->mRight = rotateRight(head->mRight);
|
||||||
|
return rotateLeft(head);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (balance < -1) {
|
||||||
|
if (head->mLeft->descentLeft(head->keyInLeftSubtree(key))) {
|
||||||
|
return rotateRight(head);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
head->mLeft = rotateLeft(head->mLeft);
|
||||||
|
return rotateRight(head);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
head->updateTreeCacheCallBack();
|
||||||
|
|
||||||
|
return head;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node* removeUtil(Node* head, KeyArg key) {
|
||||||
|
if (head == nullptr) return head;
|
||||||
|
|
||||||
|
if (head->exactNode(key)) {
|
||||||
|
if (head->mRight && head->mLeft) {
|
||||||
|
Node* min = minNode(head->mRight);
|
||||||
|
head->data = min->data;
|
||||||
|
head->mRight = removeUtil(head->mRight, min->getFindKey(head->mRight));
|
||||||
|
}
|
||||||
|
else if (head->mRight) {
|
||||||
|
head->data = head->mRight->data;
|
||||||
|
deleteNode(head->mRight);
|
||||||
|
head->mRight = nullptr;
|
||||||
|
mSize--;
|
||||||
|
}
|
||||||
|
else if (head->mLeft) {
|
||||||
|
head->data = head->mLeft->data;
|
||||||
|
deleteNode(head->mLeft);
|
||||||
|
head->mLeft = nullptr;
|
||||||
|
mSize--;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
deleteNode(head);
|
||||||
|
mSize--;
|
||||||
|
head = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (head->descentRight(key)) {
|
||||||
|
head->mRight = removeUtil(head->mRight, head->keyInRightSubtree(key));
|
||||||
|
}
|
||||||
|
else if (head->descentLeft(key)) {
|
||||||
|
head->mLeft = removeUtil(head->mLeft, head->keyInLeftSubtree(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (head == nullptr) return head;
|
||||||
|
|
||||||
|
head->mHeight = 1 + max(getNodeHeight(head->mRight), getNodeHeight(head->mLeft));
|
||||||
|
alni balance = getNodeHeight(head->mRight) - getNodeHeight(head->mLeft);
|
||||||
|
|
||||||
|
if (balance < -1) {
|
||||||
|
if (getNodeHeight(head->mLeft->mLeft) >= getNodeHeight(head->mLeft->mRight)) {
|
||||||
|
return rotateRight(head);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
head->mLeft = rotateLeft(head->mLeft);
|
||||||
|
return rotateRight(head);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (balance > 1) {
|
||||||
|
if (getNodeHeight(head->mRight->mRight) >= getNodeHeight(head->mRight->mLeft)) {
|
||||||
|
return rotateLeft(head);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
head->mRight = rotateRight(head->mRight);
|
||||||
|
return rotateLeft(head);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
head->updateTreeCacheCallBack();
|
||||||
|
|
||||||
|
return head;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
AvlTree() {
|
||||||
|
MODULE_SANITY_CHECK(gModuleContainers)
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] ualni size() const {
|
||||||
|
return mSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node* head() const {
|
||||||
|
return this->mRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
void insert(KeyArg key, DataArg data) {
|
||||||
|
mRoot = insertUtil(mRoot, key, data);
|
||||||
|
mRoot->mParent = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove(KeyArg key) {
|
||||||
|
mRoot = removeUtil(mRoot, key);
|
||||||
|
if (mRoot) mRoot->mParent = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node* maxNode(Node* head) const {
|
||||||
|
if (!head) return nullptr;
|
||||||
|
while (head->mRight != nullptr) {
|
||||||
|
head = head->mRight;
|
||||||
|
}
|
||||||
|
return head;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node* minNode(Node* head) const {
|
||||||
|
if (!head) return nullptr;
|
||||||
|
while (head->mLeft != nullptr) {
|
||||||
|
head = head->mLeft;
|
||||||
|
}
|
||||||
|
return head;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node* find(KeyArg key) const {
|
||||||
|
Node* iter = mRoot;
|
||||||
|
while (true) {
|
||||||
|
if (!iter) return nullptr;
|
||||||
|
if (iter->exactNode(key)) return iter;
|
||||||
|
if (iter->descentLeft(key)) {
|
||||||
|
key = iter->keyInLeftSubtree(key);
|
||||||
|
iter = iter->mLeft;
|
||||||
|
} else {
|
||||||
|
key = iter->keyInRightSubtree(key);
|
||||||
|
iter = iter->mRight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Node* findLessOrEq(KeyArg key) const {
|
||||||
|
Node* iter = mRoot;
|
||||||
|
while (true) {
|
||||||
|
if (!iter) return nullptr;
|
||||||
|
if (iter->exactNode(key)) return iter;
|
||||||
|
if (iter->descentLeft(key)) {
|
||||||
|
if (iter->mLeft) {
|
||||||
|
key = iter->keyInLeftSubtree(key);
|
||||||
|
iter = iter->mLeft;
|
||||||
|
} else {
|
||||||
|
return iter;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (iter->mRight) {
|
||||||
|
key = iter->keyInRightSubtree(key);
|
||||||
|
iter = iter->mRight;
|
||||||
|
} else {
|
||||||
|
return iter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns first invalid node
|
||||||
|
const Node* findInvalidNode(const Node* head) const {
|
||||||
|
if (head == nullptr) return nullptr;
|
||||||
|
|
||||||
|
if (head->mLeft) {
|
||||||
|
// TODO: incomplete test
|
||||||
|
if (!head->descentLeft(head->mLeft->getFindKey(head))) return head;
|
||||||
|
if (head->mLeft->mParent != head) return head;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (head->mRight) {
|
||||||
|
if (!head->descentRight(head->mRight->getFindKey(head))) return head;
|
||||||
|
if (head->mRight->mParent != head) return head;
|
||||||
|
}
|
||||||
|
|
||||||
|
int balance = getNodeHeight(head->mRight) - getNodeHeight(head->mLeft);
|
||||||
|
|
||||||
|
if (balance > 1 || balance < -1) return head;
|
||||||
|
|
||||||
|
const Node* ret = findInvalidNode(head->mRight);
|
||||||
|
|
||||||
|
if (ret) return ret;
|
||||||
|
|
||||||
|
return findInvalidNode(head->mLeft);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isValid() { return findInvalidNode(head()) == nullptr; }
|
||||||
|
};
|
||||||
|
}
|
||||||
26
Containers/public/ContainersCommon.hpp
Normal file
26
Containers/public/ContainersCommon.hpp
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "BaseModule.hpp"
|
||||||
|
|
||||||
|
namespace tp {
|
||||||
|
extern ModuleManifest gModuleContainers;
|
||||||
|
|
||||||
|
class DefaultAllocator {
|
||||||
|
public:
|
||||||
|
DefaultAllocator() = default;
|
||||||
|
static void *allocate(ualni);
|
||||||
|
static void deallocate(void *);
|
||||||
|
};
|
||||||
|
|
||||||
|
class DefaultSaverLoader {
|
||||||
|
public:
|
||||||
|
DefaultSaverLoader() = default;
|
||||||
|
|
||||||
|
template<typename Type>
|
||||||
|
static void write(const Type&) {}
|
||||||
|
|
||||||
|
template<typename Type>
|
||||||
|
static void read(Type&) {}
|
||||||
|
};
|
||||||
|
}
|
||||||
322
Containers/public/List.hpp
Normal file
322
Containers/public/List.hpp
Normal file
|
|
@ -0,0 +1,322 @@
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ContainersCommon.hpp"
|
||||||
|
|
||||||
|
#include "TypeInfo.hpp"
|
||||||
|
|
||||||
|
namespace tp {
|
||||||
|
|
||||||
|
template <typename Type, class Allocator = DefaultAllocator>
|
||||||
|
class List {
|
||||||
|
|
||||||
|
typedef SelCopyArg<Type> TypeArg;
|
||||||
|
typedef ualni Index;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
struct Node {
|
||||||
|
Type data;
|
||||||
|
Node* next = nullptr;
|
||||||
|
Node* prev = nullptr;
|
||||||
|
Node() = default;
|
||||||
|
explicit Node(TypeArg p_data) : data(p_data) {}
|
||||||
|
Type& operator->() { return data; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class IteratorPointer {
|
||||||
|
protected:
|
||||||
|
Node* mIter;
|
||||||
|
public:
|
||||||
|
IteratorPointer() = default;
|
||||||
|
Type& operator->() { return (mIter->data); }
|
||||||
|
const Type& operator->() const { return (mIter->data); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class IteratorReference {
|
||||||
|
protected:
|
||||||
|
Node* mIter;
|
||||||
|
public:
|
||||||
|
IteratorReference() = default;
|
||||||
|
Type* operator->() { return &(mIter->data); }
|
||||||
|
const Type* operator->() const { return &(mIter->data); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class Iterator : public TypeSelect<TypeTraits<Type>::isPointer, IteratorPointer, IteratorReference>::Result {
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit Iterator(Node* iter) { this->mIter = iter; }
|
||||||
|
|
||||||
|
Node* node() { return this->mIter; }
|
||||||
|
Type& data() { return this->mIter->data; }
|
||||||
|
const Node* node() const { return this->mIter; }
|
||||||
|
const Type& data() const { return this->mIter->data; }
|
||||||
|
|
||||||
|
const Iterator& operator*() const { return *this; }
|
||||||
|
|
||||||
|
Iterator& operator++() {
|
||||||
|
this->mIter = this->mIter->next;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const Iterator& left) const { return left.mIter == this->mIter; }
|
||||||
|
bool operator!=(const Iterator& left) const { return left.mIter != this->mIter; }
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
Node* mFirst = nullptr;
|
||||||
|
Node* mLast = nullptr;
|
||||||
|
Index mLength = 0;
|
||||||
|
Allocator mAlloc;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
List() = default;
|
||||||
|
|
||||||
|
List(const init_list<Type>& list) { operator=(list); }
|
||||||
|
|
||||||
|
[[nodiscard]] inline Node* first() const { return mFirst; }
|
||||||
|
[[nodiscard]] inline Node* last() const { return mLast; }
|
||||||
|
[[nodiscard]] inline Index length() const { return mLength; }
|
||||||
|
|
||||||
|
[[nodiscard]] Node* newNode() { return new (mAlloc.allocate(sizeof(Node))) Node(); }
|
||||||
|
[[nodiscard]] Node* newNode(TypeArg arg) { return new (mAlloc.allocate(sizeof(Node))) Node(arg); }
|
||||||
|
[[nodiscard]] Node* newNodeNotConstructed() {
|
||||||
|
auto node = (Node*) mAlloc.allocate(sizeof(Node));
|
||||||
|
node->next = node->prev = nullptr;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const Allocator& getAllocator() const { return mAlloc; }
|
||||||
|
|
||||||
|
void deleteNode(Node* node) {
|
||||||
|
node->~Node();
|
||||||
|
mAlloc.deallocate(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
Node* addNodeBack() {
|
||||||
|
auto const out = newNode();
|
||||||
|
pushBack(out);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node* addNodeFront() {
|
||||||
|
auto const out = newNode();
|
||||||
|
pushFront(out);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
void attach(Node* node, Node* node_to) {
|
||||||
|
if (node_to) {
|
||||||
|
if (node_to->next) {
|
||||||
|
node->next = node_to->next;
|
||||||
|
node->next->prev = node;
|
||||||
|
}
|
||||||
|
node_to->next = node;
|
||||||
|
node->prev = node_to;
|
||||||
|
if (node_to == mLast) {
|
||||||
|
mLast = node;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (mFirst) {
|
||||||
|
mFirst->prev = node;
|
||||||
|
node->next = mFirst;
|
||||||
|
mFirst = node;
|
||||||
|
} else {
|
||||||
|
mFirst = mLast = node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mLength++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void detach(Node* node) {
|
||||||
|
if (node->next) {
|
||||||
|
node->next->prev = node->prev;
|
||||||
|
}
|
||||||
|
if (node->prev) {
|
||||||
|
node->prev->next = node->next;
|
||||||
|
}
|
||||||
|
if (node == mLast) {
|
||||||
|
mLast = mLast->prev;
|
||||||
|
}
|
||||||
|
if (node == mFirst) {
|
||||||
|
mFirst = mFirst->next;
|
||||||
|
}
|
||||||
|
mLength--;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Node* findIdx(Index idx) const {
|
||||||
|
DEBUG_ASSERT(!mFirst || idx > mLength - 1)
|
||||||
|
Node* found = mFirst;
|
||||||
|
for (int i = 0; i != idx; i++) {
|
||||||
|
found = found->next;
|
||||||
|
}
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Node* find(const TypeArg data) const {
|
||||||
|
Node* found = mFirst;
|
||||||
|
for (alni i = 0; data != found->data; i++) {
|
||||||
|
if (!found->next) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
found = found->next;
|
||||||
|
}
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline const Type& operator[](Index idx) const {
|
||||||
|
DEBUG_ASSERT(idx < mLength)
|
||||||
|
return find(idx)->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pushBack(Node* new_node) { attach(new_node, mLast); }
|
||||||
|
void pushFront(Node* new_node) { attach(new_node, nullptr); }
|
||||||
|
void pushBack(TypeArg data) { pushBack(newNode(data)); }
|
||||||
|
void pushFront(TypeArg data) { pushFront(newNode(data)); }
|
||||||
|
|
||||||
|
void popBack() {
|
||||||
|
DEBUG_ASSERT(mLast)
|
||||||
|
detach(mLast);
|
||||||
|
deleteNode(mLast);
|
||||||
|
}
|
||||||
|
|
||||||
|
void popFront() {
|
||||||
|
DEBUG_ASSERT(mFirst)
|
||||||
|
auto temp = mFirst;
|
||||||
|
detach(mFirst);
|
||||||
|
deleteNode(temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void insert(Node* node, Index idx) {
|
||||||
|
if (!mLength) {
|
||||||
|
attach(node, mLast);
|
||||||
|
} else if (idx >= mLength) {
|
||||||
|
attach(node, nullptr);
|
||||||
|
} else {
|
||||||
|
attach(node, find(idx)->prev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void insert(TypeArg data, Index idx) {
|
||||||
|
insert(newNode(data), idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeNode(Node* node) {
|
||||||
|
detach(node);
|
||||||
|
deleteNode(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeAll() {
|
||||||
|
while (mFirst) {
|
||||||
|
popFront();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// copies data
|
||||||
|
List& operator+=(const List& in) {
|
||||||
|
for (auto node : in) {
|
||||||
|
pushBack(node.data());
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
List& operator+=(const init_list<Type>& list) {
|
||||||
|
for (auto item : list) {
|
||||||
|
pushBack(item);
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
List& operator=(const List& in) {
|
||||||
|
if (this == &in) { return *this; }
|
||||||
|
removeAll();
|
||||||
|
(*this) += in;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
List& operator=(const init_list<Type>& list) {
|
||||||
|
removeAll();
|
||||||
|
*this += list;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool operator==(const List& in) const {
|
||||||
|
if (in == *this) { return true; }
|
||||||
|
if (in.length() != length()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Node* left = in.first();
|
||||||
|
Node* right = first();
|
||||||
|
while (left && right) {
|
||||||
|
if (left->data != right->data) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (left != right) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename compare_val>
|
||||||
|
[[nodiscard]] Node* find(bool (*found)(Node* node, compare_val val), compare_val value) const {
|
||||||
|
for (Node* node = mFirst; node; node = node->next) {
|
||||||
|
if (found(node, value)) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Iterator begin() const {
|
||||||
|
Iterator out(mFirst);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Iterator end() const {
|
||||||
|
return Iterator(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void invert() {
|
||||||
|
Node* iter = mFirst;
|
||||||
|
Node* tmp;
|
||||||
|
while (iter) {
|
||||||
|
tmp = iter;
|
||||||
|
iter = iter->next;
|
||||||
|
swap(tmp->next, tmp->prev);
|
||||||
|
}
|
||||||
|
swap(mFirst, mLast);
|
||||||
|
}
|
||||||
|
|
||||||
|
void detachAll() {
|
||||||
|
while (mFirst) {
|
||||||
|
detach(mFirst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Saver>
|
||||||
|
void write(Saver& file) const {
|
||||||
|
file.write(mLength);
|
||||||
|
for (auto item : *this) {
|
||||||
|
file.write(item.data());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Loader>
|
||||||
|
void read(Loader& file) {
|
||||||
|
removeAll();
|
||||||
|
ualni len;
|
||||||
|
file.read(len);
|
||||||
|
for (auto i = len; i; i--) {
|
||||||
|
auto node = newNodeNotConstructed();
|
||||||
|
file.read(node->data);
|
||||||
|
pushBack(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~List() {
|
||||||
|
removeAll();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
397
Containers/public/Map.hpp
Normal file
397
Containers/public/Map.hpp
Normal file
|
|
@ -0,0 +1,397 @@
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ContainersCommon.hpp"
|
||||||
|
#include "Common.hpp"
|
||||||
|
|
||||||
|
namespace tp {
|
||||||
|
|
||||||
|
template<typename Key>
|
||||||
|
ualni DefaultHashFunc(SelCopyArg<Key> key) {
|
||||||
|
return hash(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<
|
||||||
|
typename tKey, typename tVal,
|
||||||
|
class tAllocator = DefaultAllocator,
|
||||||
|
ualni(*tHashFunc)(SelCopyArg<tKey>) = DefaultHashFunc<tKey>,
|
||||||
|
int tTableInitialSize = 4
|
||||||
|
>
|
||||||
|
class Map {
|
||||||
|
|
||||||
|
enum {
|
||||||
|
MAP_PERTURB_SHIFT = 5,
|
||||||
|
MAP_MIN_SIZE = 4,
|
||||||
|
MAP_MAX_LOAD_PERCENTAGE = 66,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef SelCopyArg<tKey> KeyArg;
|
||||||
|
typedef SelCopyArg<tVal> ValArg;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
class Node {
|
||||||
|
friend Map;
|
||||||
|
Node(KeyArg aKey, ValArg aVal) : key(aKey), val(aVal) {}
|
||||||
|
public:
|
||||||
|
tKey key;
|
||||||
|
tVal val;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Idx {
|
||||||
|
alni idx = -1;
|
||||||
|
operator bool() { return idx != -1; }
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
tAllocator mAlloc;
|
||||||
|
Node** mTable;
|
||||||
|
ualni mNSlots = 0;
|
||||||
|
ualni mNEntries = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
constexpr halnf maxLoadFactor() { return halnf(MAP_MAX_LOAD_PERCENTAGE) / 100.f; }
|
||||||
|
|
||||||
|
inline Node** newTable(const ualni len) {
|
||||||
|
return new(mAlloc.allocate(sizeof(Node*) * len)) Node*[len]();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Node* newNode(KeyArg key, ValArg val) {
|
||||||
|
return new(mAlloc.allocate(sizeof(Node))) Node(key, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Node* newNodeNotConstructed() {
|
||||||
|
return (Node*) mAlloc.allocate(sizeof(Node));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void deleteTable(Node** table) {
|
||||||
|
mAlloc.deallocate(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void deleteNode(Node* p) {
|
||||||
|
p->~Node();
|
||||||
|
mAlloc.deallocate(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
void markDeletedSlot(ualni idx) const {
|
||||||
|
mTable[idx] = (Node*)-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isDeletedNode(Node* node) {
|
||||||
|
return node == (Node*)-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rehash() {
|
||||||
|
alni nSlotsOld = mNSlots;
|
||||||
|
Node** tableOld = mTable;
|
||||||
|
|
||||||
|
mNSlots = next2pow((uhalni)((1.f / (maxLoadFactor())) * mNEntries + 1));
|
||||||
|
mTable = newTable(mNSlots);
|
||||||
|
mNEntries = 0;
|
||||||
|
|
||||||
|
for (alni i = 0; i < nSlotsOld; i++) {
|
||||||
|
if (!tableOld[i] || isDeletedNode(tableOld[i])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
alni idx = findSlotWrite(tableOld[i]->key);
|
||||||
|
mTable[idx] = tableOld[i];
|
||||||
|
mNEntries++;
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteTable(tableOld);
|
||||||
|
}
|
||||||
|
|
||||||
|
alni findSlotRead(KeyArg key) const {
|
||||||
|
ualni const hashed_key = tHashFunc(key);
|
||||||
|
ualni const mask = mNSlots - 1;
|
||||||
|
ualni const shift = (hashed_key >> MAP_PERTURB_SHIFT) & ~1;
|
||||||
|
alni idx = hashed_key & mask;
|
||||||
|
NEXT:
|
||||||
|
if (isDeletedNode(mTable[idx])) {
|
||||||
|
goto SKIP;
|
||||||
|
}
|
||||||
|
if (!mTable[idx]) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (mTable[idx]->key == key) {
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
SKIP:
|
||||||
|
idx = ((5 * idx) + 1 + shift) & mask;
|
||||||
|
goto NEXT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// compares keys only when collisions occur
|
||||||
|
alni findSlotReadExisting(KeyArg key) const {
|
||||||
|
ualni const hashed_key = tHashFunc(key);
|
||||||
|
ualni const mask = mNSlots - 1;
|
||||||
|
ualni const shift = (hashed_key >> MAP_PERTURB_SHIFT) & ~1;
|
||||||
|
alni idx = hashed_key & mask;
|
||||||
|
NEXT:
|
||||||
|
if (isDeletedNode(mTable[idx])) {
|
||||||
|
goto SKIP;
|
||||||
|
}
|
||||||
|
if (!mTable[idx]) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (mTable[((5 * idx) + 1 + shift) & mask] == nullptr) {
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
if (mTable[idx]->key == key) {
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
SKIP:
|
||||||
|
idx = ((5 * idx) + 1 + shift) & mask;
|
||||||
|
goto NEXT;
|
||||||
|
}
|
||||||
|
|
||||||
|
ualni findSlotWrite(KeyArg key) const {
|
||||||
|
ualni const hashed_key = tHashFunc(key);
|
||||||
|
ualni const mask = mNSlots - 1;
|
||||||
|
ualni const shift = (hashed_key >> MAP_PERTURB_SHIFT) & ~1;
|
||||||
|
ualni idx = hashed_key & mask;
|
||||||
|
NEXT:
|
||||||
|
if (isDeletedNode(mTable[idx]) || !mTable[idx]) {
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
if (mTable[idx]->key == key) {
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
idx = ((5 * idx) + 1 + shift) & mask;
|
||||||
|
goto NEXT;
|
||||||
|
}
|
||||||
|
|
||||||
|
void put(Node* node) {
|
||||||
|
const ualni idx = findSlotWrite(node->key);
|
||||||
|
|
||||||
|
if (!mTable[idx] || isDeletedNode(mTable[idx])) {
|
||||||
|
mNEntries++;
|
||||||
|
}
|
||||||
|
|
||||||
|
mTable[idx] = node;
|
||||||
|
|
||||||
|
if ((halnf)mNEntries / mNSlots > maxLoadFactor()) {
|
||||||
|
rehash();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
Map() {
|
||||||
|
MODULE_SANITY_CHECK(gModuleContainers)
|
||||||
|
mNSlots = next2pow(uhalni(tTableInitialSize - 1));
|
||||||
|
mTable = newTable(mNSlots);
|
||||||
|
}
|
||||||
|
|
||||||
|
Node** buff() const {
|
||||||
|
return mTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] ualni size() const {
|
||||||
|
return mNEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] ualni slotsSize() const {
|
||||||
|
return mNEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const tAllocator& getAllocator() const {
|
||||||
|
return mAlloc;
|
||||||
|
}
|
||||||
|
|
||||||
|
void put(KeyArg key, ValArg val) {
|
||||||
|
const ualni idx = findSlotWrite(key);
|
||||||
|
if (!mTable[idx] || isDeletedNode(mTable[idx])) {
|
||||||
|
mTable[idx] = newNode(key, val);
|
||||||
|
mNEntries++;
|
||||||
|
}
|
||||||
|
mTable[idx]->val = val;
|
||||||
|
if ((halnf) mNEntries / mNSlots > maxLoadFactor()) {
|
||||||
|
rehash();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// undefined behavior if item is not presents
|
||||||
|
tVal& get(KeyArg key) {
|
||||||
|
DEBUG_ASSERT(findSlotRead(key) != -1 && "Key Error")
|
||||||
|
return mTable[findSlotReadExisting(key)]->val;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tVal& get(KeyArg key) const {
|
||||||
|
DEBUG_ASSERT(findSlotRead(key) != -1 && "Key Error")
|
||||||
|
return mTable[findSlotReadExisting(key)]->val;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Idx presents(KeyArg key) const { return { findSlotRead(key) }; }
|
||||||
|
|
||||||
|
void remove(KeyArg key) {
|
||||||
|
DEBUG_ASSERT(findSlotRead(key) != -1 && "Key Error")
|
||||||
|
auto idx = findSlotReadExisting(key);
|
||||||
|
|
||||||
|
deleteNode(mTable[idx]);
|
||||||
|
|
||||||
|
markDeletedSlot(idx);
|
||||||
|
|
||||||
|
mNEntries--;
|
||||||
|
if (halnf(mNEntries / mNSlots) < 1.f - maxLoadFactor()) {
|
||||||
|
rehash();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const tVal& getSlotVal(ualni slot) const {
|
||||||
|
DEBUG_ASSERT(slot < mNSlots && (mTable[slot] && !isDeletedNode(mTable[slot])) && "Key Error")
|
||||||
|
return mTable[slot]->val;
|
||||||
|
}
|
||||||
|
|
||||||
|
tVal& getSlotVal(ualni slot) {
|
||||||
|
DEBUG_ASSERT(slot < mNSlots && (mTable[slot] && !isDeletedNode(mTable[slot])) && "Key Error")
|
||||||
|
return mTable[slot]->val;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tVal& getSlotVal(Idx slot) const {
|
||||||
|
DEBUG_ASSERT(slot.idx < mNSlots && (mTable[slot.idx] && !isDeletedNode(mTable[slot.idx])) && "Key Error")
|
||||||
|
return mTable[slot]->val;
|
||||||
|
}
|
||||||
|
|
||||||
|
tVal& getSlotVal(Idx slot) {
|
||||||
|
DEBUG_ASSERT(slot.idx < mNSlots && (mTable[slot.idx] && !isDeletedNode(mTable[slot.idx])) && "Key Error")
|
||||||
|
return mTable[slot.idx]->val;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map& operator=(const Map& in) {
|
||||||
|
if (this == &in) {
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
removeAll();
|
||||||
|
mNSlots = in.mNSlots;
|
||||||
|
mTable = newTable(mNSlots);
|
||||||
|
for (alni i = 0; i < mNSlots; i++) {
|
||||||
|
if (in.mTable[i] && !isDeletedNode(in.mTable[i])) {
|
||||||
|
put(in.mTable[i]->key, in.mTable[i]->val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool operator==(const Map& in) const {
|
||||||
|
if (this == &in) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (in.mNEntries != mNEntries) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (auto i : in) {
|
||||||
|
if (!presents(i->key) || get(i->key) != i->val) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeAll() {
|
||||||
|
for (ualni i = 0; i < mNSlots; i++) {
|
||||||
|
if (mTable[i] && !isDeletedNode(mTable[i])) {
|
||||||
|
deleteNode(mTable[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deleteTable(mTable);
|
||||||
|
mTable = newTable(tTableInitialSize);
|
||||||
|
mNSlots = tTableInitialSize;
|
||||||
|
mNEntries = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] alni slotIdx(alni entry_idx_in) const {
|
||||||
|
alni entry_idx = -1;
|
||||||
|
for (alni slot_idx = 0; slot_idx < mNSlots; slot_idx++) {
|
||||||
|
if (mTable[slot_idx]) {
|
||||||
|
entry_idx++;
|
||||||
|
}
|
||||||
|
if (entry_idx == entry_idx_in) {
|
||||||
|
return slot_idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node* GetEntry(ualni idx) {
|
||||||
|
auto slot = slotIdx(idx);
|
||||||
|
DEBUG_ASSERT(slot != -1 && "Key error")
|
||||||
|
return mTable[slot];
|
||||||
|
}
|
||||||
|
|
||||||
|
const Node* GetEntry(ualni idx) const {
|
||||||
|
auto slot = slotIdx(idx);
|
||||||
|
DEBUG_ASSERT(slot != -1 && "Key error")
|
||||||
|
return mTable[slot];
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
class Iterator {
|
||||||
|
const Map* map;
|
||||||
|
Node* mIter;
|
||||||
|
alni mSlot;
|
||||||
|
alni mEntry;
|
||||||
|
|
||||||
|
friend Map;
|
||||||
|
explicit Iterator(const Map* _map) {
|
||||||
|
mSlot = -1;
|
||||||
|
mEntry = -1;
|
||||||
|
map = _map;
|
||||||
|
this->operator++();
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
Node* operator->() { return mIter; }
|
||||||
|
const Node* operator->() const { return mIter; }
|
||||||
|
const Iterator& operator*() const { return *this; }
|
||||||
|
|
||||||
|
bool operator!=(ualni idx) const { return mSlot != idx; }
|
||||||
|
|
||||||
|
void operator++() {
|
||||||
|
mSlot++;
|
||||||
|
while ((map->isDeletedNode(map->mTable[mSlot]) || !map->mTable[mSlot]) && (mSlot != map->mNSlots)) {
|
||||||
|
mSlot++;
|
||||||
|
}
|
||||||
|
if (mSlot != map->mNSlots) {
|
||||||
|
mIter = map->mTable[mSlot];
|
||||||
|
mEntry++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] Iterator begin() const {
|
||||||
|
return Iterator(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] ualni end() const {
|
||||||
|
return mNSlots;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Saver>
|
||||||
|
void write(Saver& file) {
|
||||||
|
file.write(mNEntries);
|
||||||
|
for (auto item : *this) {
|
||||||
|
file.write(item->val);
|
||||||
|
file.write(item->key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Loader>
|
||||||
|
void read(Loader& file) {
|
||||||
|
removeAll();
|
||||||
|
ualni len;
|
||||||
|
file.read(len);
|
||||||
|
for (auto i = len; i; i--) {
|
||||||
|
auto node = newNodeNotConstructed();
|
||||||
|
file.read(node->val);
|
||||||
|
file.read(node->key);
|
||||||
|
put(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~Map() { removeAll(); }
|
||||||
|
};
|
||||||
|
}
|
||||||
31
Containers/tests/AvlTest.cpp
Normal file
31
Containers/tests/AvlTest.cpp
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
|
||||||
|
#include "Tests.hpp"
|
||||||
|
|
||||||
|
#include "AvlTree.hpp"
|
||||||
|
|
||||||
|
#include "Testing.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
using namespace tp;
|
||||||
|
|
||||||
|
TEST_DEF_STATIC(Simple) {
|
||||||
|
AvlTree<AvlNumericKey<alni>, TestClass, TestAllocator> tree;
|
||||||
|
|
||||||
|
TEST(tree.size() == 0);
|
||||||
|
TEST(tree.head() == nullptr);
|
||||||
|
|
||||||
|
tree.insert(6, TestClass(6));
|
||||||
|
TEST(tree.isValid());
|
||||||
|
TEST(tree.size() == 1);
|
||||||
|
TEST(tree.head()->data == TestClass(6));
|
||||||
|
|
||||||
|
tree.remove(6);
|
||||||
|
TEST(tree.isValid());
|
||||||
|
TEST(tree.size() == 0);
|
||||||
|
TEST(tree.head() == nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_DEF(Avl) {
|
||||||
|
testSimple();
|
||||||
|
}
|
||||||
88
Containers/tests/ListTest.cpp
Normal file
88
Containers/tests/ListTest.cpp
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
|
||||||
|
#include "Tests.hpp"
|
||||||
|
#include "Testing.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
using namespace tp;
|
||||||
|
|
||||||
|
TEST_DEF_STATIC(SimpleReference) {
|
||||||
|
tp::List<TestClass, TestAllocator> list = { TestClass(1), TestClass(2), TestClass(3), TestClass(4) };
|
||||||
|
|
||||||
|
list.pushBack(TestClass(5));
|
||||||
|
list.pushFront(TestClass(0));
|
||||||
|
|
||||||
|
ualni i = -1;
|
||||||
|
for (auto iter : list) {
|
||||||
|
i++;
|
||||||
|
TEST_EQUAL(iter->getVal(), i);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(i == 5);
|
||||||
|
|
||||||
|
list.removeAll();
|
||||||
|
TEST(list.getAllocator().getAllocationsCount() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_DEF_STATIC(SimplePointer) {
|
||||||
|
tp::List<TestClass*, TestAllocator> list = { new TestClass(1), new TestClass(2), new TestClass(3), new TestClass(4) };
|
||||||
|
|
||||||
|
list.pushBack(new TestClass(5));
|
||||||
|
list.pushFront(new TestClass(0));
|
||||||
|
|
||||||
|
ualni i = -1;
|
||||||
|
for (auto iter : list) {
|
||||||
|
i++;
|
||||||
|
TEST_EQUAL(iter->getVal(), i);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(i == 5);
|
||||||
|
|
||||||
|
list.removeAll();
|
||||||
|
|
||||||
|
TEST(list.getAllocator().getAllocationsCount() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_DEF_STATIC(Copy) {
|
||||||
|
tp::List<TestClass, TestAllocator> list = { TestClass(1), TestClass(2), TestClass(3), TestClass(4) };
|
||||||
|
tp::List<TestClass, TestAllocator> list2 = list;
|
||||||
|
|
||||||
|
TEST_EQUAL(list, list2);
|
||||||
|
|
||||||
|
list.removeAll();
|
||||||
|
list2.removeAll();
|
||||||
|
|
||||||
|
TEST(list.getAllocator().getAllocationsCount() == 0);
|
||||||
|
TEST(list2.getAllocator().getAllocationsCount() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_DEF_STATIC(SaveLoad) {
|
||||||
|
tp::List<TestClass, TestAllocator> list = { TestClass(1), TestClass(2), TestClass(3), TestClass(4) };
|
||||||
|
|
||||||
|
TestFile file;
|
||||||
|
|
||||||
|
list.write(file);
|
||||||
|
|
||||||
|
list.removeAll();
|
||||||
|
|
||||||
|
file.setAddress(0);
|
||||||
|
|
||||||
|
list.read(file);
|
||||||
|
|
||||||
|
ualni i = 0;
|
||||||
|
for (auto iter : list) {
|
||||||
|
i++;
|
||||||
|
TEST_EQUAL(iter->getVal(), i);
|
||||||
|
}
|
||||||
|
TEST(i == 4);
|
||||||
|
|
||||||
|
list.removeAll();
|
||||||
|
|
||||||
|
TEST(list.getAllocator().getAllocationsCount() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_DEF(List) {
|
||||||
|
testSimplePointer();
|
||||||
|
testSimpleReference();
|
||||||
|
testSaveLoad();
|
||||||
|
}
|
||||||
137
Containers/tests/MapTest.cpp
Normal file
137
Containers/tests/MapTest.cpp
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
|
||||||
|
#include "Tests.hpp"
|
||||||
|
#include "Testing.hpp"
|
||||||
|
|
||||||
|
#include "Map.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
using namespace tp;
|
||||||
|
|
||||||
|
TEST_DEF_STATIC(SimpleReference) {
|
||||||
|
tp::Map<tp::ualni, TestClass, TestAllocator> map;
|
||||||
|
|
||||||
|
for (auto i : Range(1000, 100000)) {
|
||||||
|
map.put(i, TestClass(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto i : Range(1000, 100000)) {
|
||||||
|
TEST(map.presents(i));
|
||||||
|
TEST_EQUAL(map.get(i).getVal(), i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto i : Range(1000, 100000)) {
|
||||||
|
map.put(i, TestClass(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto i : Range(1000, 2000)) {
|
||||||
|
TEST(map.presents(i));
|
||||||
|
map.remove(i);
|
||||||
|
TEST(!map.presents(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto i : Range(2000, 100000)) {
|
||||||
|
TEST(map.presents(i));
|
||||||
|
TEST_EQUAL(map.get(i).getVal(), i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto i : map) {
|
||||||
|
i->val.setVal(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
map.removeAll();
|
||||||
|
|
||||||
|
TEST(map.getAllocator().getAllocationsCount() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_DEF_STATIC(SimplePointer) {
|
||||||
|
tp::Map<tp::ualni, TestClass*, TestAllocator> map;
|
||||||
|
|
||||||
|
for (auto i : Range(1000)) {
|
||||||
|
map.put(i, new TestClass(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto i : Range(1000)) {
|
||||||
|
TEST(map.presents(i));
|
||||||
|
TEST_EQUAL(map.get(i)->getVal(), i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto i : Range(1000)) {
|
||||||
|
map.put(i, new TestClass(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto i : Range(900, 1000)) {
|
||||||
|
TEST(map.presents(i));
|
||||||
|
map.remove(i);
|
||||||
|
TEST(!map.presents(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto i : Range(900)) {
|
||||||
|
TEST(map.presents(i));
|
||||||
|
TEST_EQUAL(map.get(i)->getVal(), i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto i : map) {
|
||||||
|
i->val->setVal(3);
|
||||||
|
delete i->val;
|
||||||
|
}
|
||||||
|
|
||||||
|
map.removeAll();
|
||||||
|
|
||||||
|
TEST(map.getAllocator().getAllocationsCount() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_DEF_STATIC(Copy) {
|
||||||
|
tp::Map<tp::ualni, TestClass, TestAllocator> map;
|
||||||
|
|
||||||
|
for (auto i : Range(10)) {
|
||||||
|
map.put(i, TestClass(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
tp::Map<tp::ualni, TestClass, TestAllocator> map2 = map;
|
||||||
|
|
||||||
|
TEST_EQUAL(map, map2);
|
||||||
|
|
||||||
|
map.removeAll();
|
||||||
|
map2.removeAll();
|
||||||
|
|
||||||
|
TEST(map.getAllocator().getAllocationsCount() == 1);
|
||||||
|
TEST(map2.getAllocator().getAllocationsCount() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_DEF_STATIC(SaveLoad) {
|
||||||
|
tp::Map<tp::ualni, TestClass, TestAllocator> map;
|
||||||
|
|
||||||
|
for (auto i : Range(10)) {
|
||||||
|
map.put(i, TestClass(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
TestFile file;
|
||||||
|
|
||||||
|
map.write(file);
|
||||||
|
|
||||||
|
map.removeAll();
|
||||||
|
|
||||||
|
TEST(map.getAllocator().getAllocationsCount() == 1);
|
||||||
|
|
||||||
|
file.setAddress(0);
|
||||||
|
|
||||||
|
map.read(file);
|
||||||
|
|
||||||
|
TEST(map.getAllocator().getAllocationsCount() == 11);
|
||||||
|
|
||||||
|
for (auto i : Range(10)) {
|
||||||
|
TEST(map.presents(i));
|
||||||
|
TEST_EQUAL(map.get(i).getVal(), i);
|
||||||
|
}
|
||||||
|
|
||||||
|
map.removeAll();
|
||||||
|
|
||||||
|
TEST(map.getAllocator().getAllocationsCount() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_DEF(Map) {
|
||||||
|
testSimplePointer();
|
||||||
|
testSimpleReference();
|
||||||
|
testSaveLoad();
|
||||||
|
}
|
||||||
41
Containers/tests/Tests.cpp
Normal file
41
Containers/tests/Tests.cpp
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
|
||||||
|
#include "Tests.hpp"
|
||||||
|
|
||||||
|
#include "Testing.hpp"
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
static bool init(const tp::ModuleManifest* self) {
|
||||||
|
tp::gTesting.setRootName(self->getName());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* TestAllocator::allocate(tp::ualni size) {
|
||||||
|
nAllocations++;
|
||||||
|
return malloc(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestAllocator::deallocate(void* p) {
|
||||||
|
nAllocations--;
|
||||||
|
free(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
tp::ualni TestAllocator::getAllocationsCount() const {
|
||||||
|
return nAllocations;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
|
||||||
|
tp::ModuleManifest* deps[] = { &tp::gModuleUtils, nullptr };
|
||||||
|
tp::ModuleManifest testModule("ContainersTest", init, nullptr, deps);
|
||||||
|
|
||||||
|
if (!testModule.initialize()) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
testList();
|
||||||
|
testMap();
|
||||||
|
testAvl();
|
||||||
|
|
||||||
|
testModule.deinitialize();
|
||||||
|
}
|
||||||
73
Containers/tests/Tests.hpp
Normal file
73
Containers/tests/Tests.hpp
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Utils.hpp"
|
||||||
|
|
||||||
|
class TestClass {
|
||||||
|
tp::ualni val2 = 0;
|
||||||
|
tp::ualni val1;
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit TestClass(tp::ualni val) : val1(val) {}
|
||||||
|
|
||||||
|
template<class Saver>
|
||||||
|
void write(Saver& file) const {
|
||||||
|
file.write(val1);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Loader>
|
||||||
|
void read(Loader& file) {
|
||||||
|
file.read(val1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool operator==(const TestClass& in) const {
|
||||||
|
return in.val1 == val1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] tp::ualni getVal() const { return val1; }
|
||||||
|
void setVal(tp::ualni val) { val1 = val; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class TestAllocator {
|
||||||
|
tp::ualni nAllocations = 0;
|
||||||
|
public:
|
||||||
|
TestAllocator() = default;
|
||||||
|
void* allocate(tp::ualni size);
|
||||||
|
void deallocate(void* p);
|
||||||
|
[[nodiscard]] tp::ualni getAllocationsCount() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TestFile {
|
||||||
|
tp::ualni mem[1024] = { 0 };
|
||||||
|
tp::ualni address = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
TestFile() = default;
|
||||||
|
|
||||||
|
template<typename Type>
|
||||||
|
void write(const Type& val) {
|
||||||
|
val.write(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
void write<tp::ualni>(const tp::ualni& val) {
|
||||||
|
mem[address] = val;
|
||||||
|
address++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setAddress(tp::ualni addr) { address = addr; }
|
||||||
|
|
||||||
|
template<typename Type>
|
||||||
|
void read(Type& val) {
|
||||||
|
val.read(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
void read<tp::ualni>(tp::ualni& val) {
|
||||||
|
val = mem[address];
|
||||||
|
address++;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void testList();
|
||||||
|
void testMap();
|
||||||
|
void testAvl();
|
||||||
1
README.MD
Normal file
1
README.MD
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Modules
|
||||||
24
Utils/CMakeLists.txt
Normal file
24
Utils/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.2)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
|
|
||||||
|
project(Utils)
|
||||||
|
|
||||||
|
### ---------------------- Static Library --------------------- ###
|
||||||
|
file(GLOB SOURCES "./private/*.cpp")
|
||||||
|
add_library(${PROJECT_NAME} STATIC ${SOURCES})
|
||||||
|
target_include_directories(${PROJECT_NAME} PUBLIC ./public/)
|
||||||
|
|
||||||
|
### ---------------------- Dependencies --------------------- ###
|
||||||
|
target_link_libraries(${PROJECT_NAME} PUBLIC Containers)
|
||||||
|
|
||||||
|
### -------------------------- Tests -------------------------- ###
|
||||||
|
enable_testing()
|
||||||
|
file(GLOB TEST_SOURCES "./tests/*.cpp")
|
||||||
|
add_executable(${PROJECT_NAME}Tests ${TEST_SOURCES})
|
||||||
|
target_link_libraries(${PROJECT_NAME}Tests ${PROJECT_NAME})
|
||||||
|
add_test(NAME ${PROJECT_NAME}Tests COMMAND ${PROJECT_NAME}Tests)
|
||||||
|
|
||||||
|
|
||||||
|
install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/${PROJECT_NAME}/lib)
|
||||||
207
Utils/private/Debugging.cpp
Normal file
207
Utils/private/Debugging.cpp
Normal file
|
|
@ -0,0 +1,207 @@
|
||||||
|
|
||||||
|
#include "Debugging.hpp"
|
||||||
|
#include "Utils.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
using namespace tp;
|
||||||
|
|
||||||
|
CallStackCapture* tp::gCSCapture = nullptr;
|
||||||
|
|
||||||
|
void initializeCallStackCapture() {
|
||||||
|
gCSCapture = new CallStackCapture();
|
||||||
|
}
|
||||||
|
|
||||||
|
void deinitializeCallStackCapture() {
|
||||||
|
delete gCSCapture;
|
||||||
|
}
|
||||||
|
|
||||||
|
ualni CallStackCapture::CallStack::getDepth() const {
|
||||||
|
ualni len = 0;
|
||||||
|
for (long long frame : frames) {
|
||||||
|
if (!frame) { break; }
|
||||||
|
len++;
|
||||||
|
}
|
||||||
|
ualni stripedLen = 0;
|
||||||
|
for (auto i = FRAMES_TO_SKIP_START; i < len - FRAMES_TO_SKIP_END; i++) {
|
||||||
|
if (!frames[i]) { break; }
|
||||||
|
stripedLen++;
|
||||||
|
}
|
||||||
|
return stripedLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
ualni CallStackCapture::hashCallStack(CallStackKey key) {
|
||||||
|
auto const cs = key.cs;
|
||||||
|
ualni out = 0;
|
||||||
|
for (ualni i = 0; cs->frames[i]; i++) { out += cs->frames[i]; }
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CallStackCapture::CallStackKey::operator==(const CallStackCapture::CallStackKey &in) const {
|
||||||
|
for (auto i : Range(MAX_CALL_DEPTH_CAPTURE)) {
|
||||||
|
if (cs->frames[i] != in.cs->frames[i]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (cs->frames[i] == 0 && in.cs->frames[i] == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DEBUG_ASSERT(0 && "Must Not Happen")
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
CallStackCapture::CallStackCapture() {
|
||||||
|
static_assert(MAX_CALL_DEPTH_CAPTURE >= 1);
|
||||||
|
static_assert(MAX_CALL_CAPTURES_MEM_SIZE_MB > 0);
|
||||||
|
static_assert(MAX_DEBUG_INFO_LEN > sizeof("unresolved"));
|
||||||
|
static_assert(FRAMES_TO_SKIP_END >= 0 && FRAMES_TO_SKIP_START >= 0);
|
||||||
|
static_assert(MAX_CALL_DEPTH_CAPTURE > FRAMES_TO_SKIP_START + FRAMES_TO_SKIP_END);
|
||||||
|
static_assert(MAX_CALL_CAPTURES_MEM_SIZE_MB * 1024 * 1024 > sizeof(CallStack));
|
||||||
|
|
||||||
|
MODULE_SANITY_CHECK(gModuleUtils)
|
||||||
|
mBuffLoad = 0;
|
||||||
|
mBuffLen = (MAX_CALL_CAPTURES_MEM_SIZE_MB * 1024 * 1024) / sizeof(CallStack);
|
||||||
|
mBuff = (CallStack*) malloc(mBuffLen * sizeof(CallStack));
|
||||||
|
}
|
||||||
|
|
||||||
|
const CallStackCapture::CallStack* CallStackCapture::getSnapshot() {
|
||||||
|
if (mBuffLoad > mBuffLen) {
|
||||||
|
static CallStack cs;
|
||||||
|
cs.frames[0] = 0;
|
||||||
|
return &cs;
|
||||||
|
}
|
||||||
|
|
||||||
|
CallStack* cs = &mBuff[mBuffLoad];
|
||||||
|
platformWriteStackTrace(cs);
|
||||||
|
|
||||||
|
auto idx = mSnapshots.presents({ cs });
|
||||||
|
if (idx) {
|
||||||
|
return mSnapshots.getSlotVal(idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
mSnapshots.put({ cs }, cs);
|
||||||
|
|
||||||
|
mBuffLoad++;
|
||||||
|
return cs;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CallStackCapture::DebugSymbols* CallStackCapture::getSymbols(FramePointer frame) {
|
||||||
|
auto idx = mSymbols.presents(frame);
|
||||||
|
if (idx) {
|
||||||
|
return &mSymbols.getSlotVal(idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
mSymbols.put(frame, {});
|
||||||
|
auto symbols = &mSymbols.get(frame);
|
||||||
|
|
||||||
|
platformWriteDebugSymbols(frame, symbols);
|
||||||
|
return symbols;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CallStackCapture::clear() {
|
||||||
|
mBuffLoad = 0;
|
||||||
|
mSnapshots.removeAll();
|
||||||
|
mSymbols.removeAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
CallStackCapture::~CallStackCapture() {
|
||||||
|
free(mBuff);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------- Platform Depended ---------------------------------- //
|
||||||
|
|
||||||
|
#if defined(ENV_OS_LINUX)
|
||||||
|
|
||||||
|
#include <malloc.h>
|
||||||
|
#include <execinfo.h>
|
||||||
|
#include <cstring>
|
||||||
|
#include <cxxabi.h>
|
||||||
|
|
||||||
|
void CallStackCapture::platformWriteStackTrace(CallStack* stack) {
|
||||||
|
auto depth = backtrace((void**)stack->frames, (int) MAX_CALL_DEPTH_CAPTURE - 1);
|
||||||
|
stack->frames[depth] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void getGetSourceFromBinaryAddress(const char* binary, const char* address, char* file, ualni* line) {
|
||||||
|
static char buff[1024];
|
||||||
|
|
||||||
|
snprintf(buff, sizeof(buff), "addr2line -e %s -a %s", binary, address);
|
||||||
|
FILE* pipe = popen(buff, "r");
|
||||||
|
if (pipe) {
|
||||||
|
fgets(buff, sizeof(buff), pipe);
|
||||||
|
fgets(buff, sizeof(buff), pipe);
|
||||||
|
pclose(pipe);
|
||||||
|
char* linePtr = strchr(buff, ':');
|
||||||
|
|
||||||
|
printf("%s\n", buff);
|
||||||
|
|
||||||
|
if (linePtr != nullptr && buff[0] != '?' && buff[1] != '?' && buff[2] != ':') {
|
||||||
|
*linePtr = '\0';
|
||||||
|
auto sourceLen = std::strlen(buff);
|
||||||
|
std::strcpy(file, buff + ((sourceLen > MAX_DEBUG_INFO_LEN) ? (sourceLen - MAX_DEBUG_INFO_LEN) : 0));
|
||||||
|
*line = strtoul(linePtr + 1, nullptr, 10);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::strcpy(file, "unresolved");
|
||||||
|
*line = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void getDemangledName(const char* func, char* out) {
|
||||||
|
int status;
|
||||||
|
size_t funcDemangledSize = MAX_DEBUG_INFO_LEN;
|
||||||
|
char* funcDemangled = (char*)malloc(funcDemangledSize);
|
||||||
|
char* ret = abi::__cxa_demangle(func, funcDemangled, &funcDemangledSize, &status);
|
||||||
|
if (status == 0) {
|
||||||
|
funcDemangled = ret;
|
||||||
|
auto funcLen = std::strlen(funcDemangled);
|
||||||
|
std::strcpy(out, funcDemangled + ((funcLen > MAX_DEBUG_INFO_LEN) ? (funcLen - MAX_DEBUG_INFO_LEN) : 0));
|
||||||
|
free(ret);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto funcLen = std::strlen(func);
|
||||||
|
std::strcpy(out, func + ((funcLen > MAX_DEBUG_INFO_LEN) ? (funcLen - MAX_DEBUG_INFO_LEN) : 0));
|
||||||
|
free(funcDemangled);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CallStackCapture::platformWriteDebugSymbols(FramePointer frame, DebugSymbols* out) {
|
||||||
|
void* addrList[1] = { (void*) frame };
|
||||||
|
auto symbolsArray = backtrace_symbols(addrList, 1);
|
||||||
|
|
||||||
|
// 'bin(fun+addr)'
|
||||||
|
char* bin = *symbolsArray;
|
||||||
|
char* func = nullptr;
|
||||||
|
char* offset = nullptr;
|
||||||
|
|
||||||
|
// 'bin fun+addr'
|
||||||
|
for (char *p = bin; *p; ++p) {
|
||||||
|
if (*p == '(') { *p = 0; func = p + 1; }
|
||||||
|
else if (*p == '+') { offset = p; }
|
||||||
|
else if (*p == ')' && offset) { *p = 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (func && offset) {
|
||||||
|
getGetSourceFromBinaryAddress(bin, func, out->file, &out->line);
|
||||||
|
|
||||||
|
if (offset != func) {
|
||||||
|
*offset = 0;
|
||||||
|
getDemangledName(func, out->function);
|
||||||
|
} else {
|
||||||
|
std::strcpy(out->function, "unresolved");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std::strcpy(out->file, "unresolved");
|
||||||
|
std::strcpy(out->function, "unresolved");
|
||||||
|
}
|
||||||
|
|
||||||
|
free(symbolsArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
void CallStackCapture::platformWriteStackTrace(CallStack* stack) { stack->frames[0] = 0; }
|
||||||
|
void CallStackCapture::platformWriteDebugSymbols(FramePointer frame, DebugSymbols* out) {
|
||||||
|
std::strcpy(out->file, "unresolved");
|
||||||
|
std::strcpy(out->function, "unresolved");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
73
Utils/private/Testing.cpp
Normal file
73
Utils/private/Testing.cpp
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
|
||||||
|
#include "Testing.hpp"
|
||||||
|
#include "Utils.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
using namespace tp;
|
||||||
|
|
||||||
|
Testing tp::gTesting;
|
||||||
|
|
||||||
|
void Testing::startTest(const char* name) {
|
||||||
|
MODULE_SANITY_CHECK(gModuleUtils)
|
||||||
|
mCurrent->mSubTests.pushBack(new TestingNode{ {}, {}, name, mCurrent });
|
||||||
|
mCurrent = mCurrent->mSubTests.last()->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Testing::endTest() {
|
||||||
|
if (mCurrent->mParent) {
|
||||||
|
mCurrent = mCurrent->mParent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Testing::addFailedCheck(const FailedCheck& info) {
|
||||||
|
mCurrent->mFailedChecks.pushBack(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Testing::reportState() {
|
||||||
|
mRootTest.updateState();
|
||||||
|
|
||||||
|
printf("\n");
|
||||||
|
mRootTest.report();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Testing::hasFailed() {
|
||||||
|
mRootTest.updateState();
|
||||||
|
return mRootTest.mHasFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Testing::setRootName(const char* name) {
|
||||||
|
mRootTest.mName = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Testing::TestingNode::updateState() {
|
||||||
|
for (auto child : mSubTests) {
|
||||||
|
child->updateState();
|
||||||
|
mHasFailed = child->mHasFailed;
|
||||||
|
if (mHasFailed) return;
|
||||||
|
}
|
||||||
|
mHasFailed = mFailedChecks.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Testing::TestingNode::report(const char* path) const {
|
||||||
|
if (!mHasFailed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto newPath = path ? std::string(path) + "/" + mName : std::string(mName);
|
||||||
|
|
||||||
|
for (auto check : mFailedChecks) {
|
||||||
|
printf("%s Failed - (%s) %s:%llu\n", newPath.c_str(), check.data().expression, check.data().file, check.data().line);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& child : mSubTests) {
|
||||||
|
child->report(newPath.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Testing::TestingNode::~TestingNode() {
|
||||||
|
for (const auto& child : mSubTests) {
|
||||||
|
delete child.data();
|
||||||
|
}
|
||||||
|
}
|
||||||
81
Utils/private/Timing.cpp
Normal file
81
Utils/private/Timing.cpp
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include "Timing.hpp"
|
||||||
|
#include "Utils.hpp"
|
||||||
|
|
||||||
|
#define GETTIMEMSC() \
|
||||||
|
(time_ms)( \
|
||||||
|
std::chrono::duration_cast<std::chrono::milliseconds>( \
|
||||||
|
std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now()) \
|
||||||
|
.time_since_epoch()) \
|
||||||
|
.count())
|
||||||
|
|
||||||
|
#define THREAD_SLEEP(time_ms) std::this_thread::sleep_for(std::chrono::milliseconds(time_ms))
|
||||||
|
|
||||||
|
tp::time_ms tp::gCurrentTime = tp::get_time();
|
||||||
|
|
||||||
|
namespace tp {
|
||||||
|
time_ms get_time() {
|
||||||
|
gCurrentTime = GETTIMEMSC();
|
||||||
|
return gCurrentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sleep(time_ms mDuration) {
|
||||||
|
THREAD_SLEEP(mDuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Timer::Timer() {
|
||||||
|
mDuration = 0;
|
||||||
|
mStart = GETTIMEMSC();
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer::Timer(time_ms mDuration) {
|
||||||
|
mStart = GETTIMEMSC();
|
||||||
|
this->mDuration = mDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Timer::isTimeout() {
|
||||||
|
return mDuration < GETTIMEMSC() - mStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Timer::reset() {
|
||||||
|
mStart = GETTIMEMSC();
|
||||||
|
}
|
||||||
|
|
||||||
|
time_ms Timer::timePassed() {
|
||||||
|
return GETTIMEMSC() - mStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
time_ms Timer::remainder() {
|
||||||
|
return mDuration - (GETTIMEMSC() - mStart);
|
||||||
|
}
|
||||||
|
|
||||||
|
time_ms Timer::start() { return mStart; }
|
||||||
|
time_ms Timer::duration() { return mDuration; }
|
||||||
|
void Timer::setDuration(time_ms dur) { mDuration = dur; }
|
||||||
|
|
||||||
|
void Timer::wait() {
|
||||||
|
if (!isTimeout()) {
|
||||||
|
sleep(remainder());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float Timer::easeIn(time_ms pDuration) {
|
||||||
|
if (!pDuration) {
|
||||||
|
pDuration = mDuration;
|
||||||
|
}
|
||||||
|
float x = (1.f / pDuration) * timePassed();
|
||||||
|
return clamp((1.1f * x) / (x + 0.1f), 0.f, 1.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
float Timer::easeOut(time_ms pDuration) {
|
||||||
|
if (!pDuration) {
|
||||||
|
pDuration = mDuration;
|
||||||
|
}
|
||||||
|
float x = (1.f / pDuration) * timePassed();
|
||||||
|
return clamp((0.1f * (1 - x)) / (x + 0.1f), 0.f, 1.f);
|
||||||
|
}
|
||||||
|
}
|
||||||
94
Utils/private/Utils.cpp
Normal file
94
Utils/private/Utils.cpp
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
|
||||||
|
#include "Utils.hpp"
|
||||||
|
|
||||||
|
#include "ContainersCommon.hpp"
|
||||||
|
|
||||||
|
#include "Testing.hpp"
|
||||||
|
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
void initializeCallStackCapture();
|
||||||
|
void deinitializeCallStackCapture();
|
||||||
|
|
||||||
|
static bool initialize(const tp::ModuleManifest* self) {
|
||||||
|
initializeCallStackCapture();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void deinitialize(const tp::ModuleManifest* self) {
|
||||||
|
deinitializeCallStackCapture();
|
||||||
|
tp::gTesting.reportState();
|
||||||
|
if (tp::gTesting.hasFailed()) { exit(1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace tp {
|
||||||
|
|
||||||
|
static ModuleManifest* sModuleUtilsDeps[] = { &gModuleContainers, nullptr };
|
||||||
|
ModuleManifest gModuleUtils = ModuleManifest("Utils", initialize, deinitialize, sModuleUtilsDeps);
|
||||||
|
|
||||||
|
void memsetv(void* p, uhalni bytesize, uint1 val) {
|
||||||
|
MODULE_SANITY_CHECK(gModuleBase)
|
||||||
|
|
||||||
|
alni alignedval = 0;
|
||||||
|
for (ualni idx = 0; idx < sizeof(alni); idx++) {
|
||||||
|
((uint1*) &alignedval)[idx] = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
ualni alignedlen = bytesize / sizeof(alni);
|
||||||
|
for (ualni idx = 0; idx < alignedlen; idx++) {
|
||||||
|
((alni*) p)[idx] = alignedval;
|
||||||
|
}
|
||||||
|
|
||||||
|
ualni unalignedlen = bytesize - (alignedlen * sizeof(alni));
|
||||||
|
for (ualni idx = 0; idx < unalignedlen; idx++) {
|
||||||
|
((uint1*) p)[bytesize - idx - 1] = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void memcp(void* left, const void* right, uhalni len) {
|
||||||
|
MODULE_SANITY_CHECK(gModuleBase)
|
||||||
|
|
||||||
|
ualni alignedlen = len / sizeof(alni);
|
||||||
|
for (ualni idx = 0; idx < alignedlen; idx++) {
|
||||||
|
((alni*) left)[idx] = ((alni*) right)[idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
ualni unalignedlen = len - (alignedlen * sizeof(alni));
|
||||||
|
for (ualni idx = 0; idx < unalignedlen; idx++) {
|
||||||
|
((uint1*) left)[len - idx - 1] = ((uint1*) right)[len - idx - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int1 memecomp(const void* left, const void* right, uhalni len) {
|
||||||
|
MODULE_SANITY_CHECK(gModuleBase)
|
||||||
|
if (!len) return 0;
|
||||||
|
|
||||||
|
ualni alignedLength = len / sizeof(alni);
|
||||||
|
for (ualni idx = 0; idx < alignedLength; idx++) {
|
||||||
|
if (((alni*) left)[idx] == ((alni*) right)[idx]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (((alni*) left)[idx] > ((alni*) right)[idx]) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ualni unalignedLength = len - (alignedLength * sizeof(alni));
|
||||||
|
for (ualni idx = 0; idx < unalignedLength; idx++) {
|
||||||
|
if (((uint1*) left)[len - idx - 1] == ((uint1*) right)[len - idx - 1]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (((uint1*) left)[len - idx - 1] > ((uint1*) right)[len - idx - 1]) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
alnf randf() {
|
||||||
|
alnf r = static_cast<alnf>(std::rand()) / static_cast<alnf>(RAND_MAX);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
122
Utils/public/Debugging.hpp
Normal file
122
Utils/public/Debugging.hpp
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Environment.hpp"
|
||||||
|
#include "Map.hpp"
|
||||||
|
|
||||||
|
#define MAX_CALL_DEPTH_CAPTURE 16
|
||||||
|
#define MAX_CALL_CAPTURES_MEM_SIZE_MB 16
|
||||||
|
#define MAX_DEBUG_INFO_LEN 63
|
||||||
|
#define FRAMES_TO_SKIP_START 2
|
||||||
|
#define FRAMES_TO_SKIP_END 3
|
||||||
|
|
||||||
|
namespace tp {
|
||||||
|
|
||||||
|
class CallStackCapture {
|
||||||
|
public:
|
||||||
|
typedef tp::alni FramePointer;
|
||||||
|
|
||||||
|
class CallStack {
|
||||||
|
friend CallStackCapture;
|
||||||
|
FramePointer frames[MAX_CALL_DEPTH_CAPTURE];
|
||||||
|
|
||||||
|
public:
|
||||||
|
[[nodiscard]] ualni getDepth() const;
|
||||||
|
|
||||||
|
class Iterator {
|
||||||
|
const FramePointer* mFrame;
|
||||||
|
public:
|
||||||
|
explicit Iterator(const FramePointer* frame) : mFrame(frame) {};
|
||||||
|
FramePointer getFrame() { return *mFrame; }
|
||||||
|
bool operator==(const Iterator& in) const { return in.mFrame == mFrame; }
|
||||||
|
void operator++() { mFrame++; }
|
||||||
|
const Iterator& operator*() const { return *this; }
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] Iterator begin() const { return Iterator(frames + FRAMES_TO_SKIP_START); }
|
||||||
|
[[nodiscard]] Iterator end() const { return Iterator(frames + FRAMES_TO_SKIP_START + getDepth()); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class DebugSymbols {
|
||||||
|
friend CallStackCapture;
|
||||||
|
char function[MAX_DEBUG_INFO_LEN + 1] = { 0 };
|
||||||
|
char file[MAX_DEBUG_INFO_LEN + 1] = { 0 };
|
||||||
|
ualni line = 0;
|
||||||
|
public:
|
||||||
|
[[nodiscard]] const char* getFunc() const { return function; }
|
||||||
|
[[nodiscard]] const char* getFile() const { return file; }
|
||||||
|
[[nodiscard]] ualni getLine() const { return line; }
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
CallStackCapture();
|
||||||
|
~CallStackCapture();
|
||||||
|
|
||||||
|
[[nodiscard]] const CallStack* getSnapshot();
|
||||||
|
const DebugSymbols* getSymbols(FramePointer fp);
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
template<class Saver>
|
||||||
|
void write(Saver& file) {
|
||||||
|
file.write(mBuffLoad);
|
||||||
|
for (auto cs : *this) {
|
||||||
|
file.write(cs.getCallStack()->getDepth());
|
||||||
|
for (auto frame : *cs.getCallStack()) {
|
||||||
|
file.write((ualni) frame.getFrame());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file.write(mSymbols);
|
||||||
|
}
|
||||||
|
|
||||||
|
// independent of the configuration
|
||||||
|
template<class Loader>
|
||||||
|
void read(Loader& file) {
|
||||||
|
clear();
|
||||||
|
ualni loadLen;
|
||||||
|
file.read(loadLen);
|
||||||
|
for (auto cs = loadLen; cs; cs--) {
|
||||||
|
ualni callStackLen;
|
||||||
|
file.read(callStackLen);
|
||||||
|
for (auto fp = callStackLen; fp; fp--) {
|
||||||
|
// --
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
class Iterator {
|
||||||
|
const CallStack* mSnapshot;
|
||||||
|
public:
|
||||||
|
explicit Iterator(const CallStack* start) : mSnapshot(start) {};
|
||||||
|
const CallStack* getCallStack() { return mSnapshot; }
|
||||||
|
bool operator==(const Iterator& in) const { return in.mSnapshot == mSnapshot; }
|
||||||
|
void operator++() { mSnapshot++; }
|
||||||
|
const Iterator& operator*() const { return *this; }
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] Iterator begin() const { return Iterator(mBuff); }
|
||||||
|
[[nodiscard]] Iterator end() const { return Iterator(mBuff + mBuffLoad); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
struct CallStackKey {
|
||||||
|
CallStack* cs;
|
||||||
|
bool operator==(const CallStackKey& in) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void platformWriteStackTrace(CallStack* stack);
|
||||||
|
static void platformWriteDebugSymbols(FramePointer frame, DebugSymbols* out);
|
||||||
|
[[nodiscard]] static ualni hashCallStack(CallStackKey key);
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
private:
|
||||||
|
ualni mBuffLen;
|
||||||
|
ualni mBuffLoad;
|
||||||
|
CallStack* mBuff;
|
||||||
|
Map<CallStackKey, CallStack*, DefaultAllocator, hashCallStack> mSnapshots;
|
||||||
|
Map<FramePointer, DebugSymbols> mSymbols;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern CallStackCapture* gCSCapture;
|
||||||
|
}
|
||||||
97
Utils/public/Sorting.hpp
Normal file
97
Utils/public/Sorting.hpp
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Utils.hpp"
|
||||||
|
|
||||||
|
namespace tp {
|
||||||
|
|
||||||
|
template <typename Type>
|
||||||
|
inline bool compare(const Type& val1, const Type& val2) {
|
||||||
|
return val1 > val2;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SortMerge {
|
||||||
|
|
||||||
|
template <typename Type>
|
||||||
|
static void sort(Type* buff, int length, bool (*grater)(const Type& obj1, const Type& obj2) = &compare) {
|
||||||
|
mergeSort(buff, 0, length - 1, grater);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
template <typename Type>
|
||||||
|
static void merge(Type* buff, int left, int middle, int right, bool (*grater)(const Type& obj1, const Type& obj2)) {
|
||||||
|
int n1 = middle - left + 1;
|
||||||
|
int n2 = right - middle;
|
||||||
|
|
||||||
|
Type* Left = new Type[n1];
|
||||||
|
Type* Right = new Type[n2];
|
||||||
|
|
||||||
|
for (int i = 0; i < n1; i++) {
|
||||||
|
Left[i] = buff[left + i];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int j = 0; j < n2; j++) {
|
||||||
|
Right[j] = buff[middle + 1 + j];
|
||||||
|
}
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
int j = 0;
|
||||||
|
int k = left;
|
||||||
|
|
||||||
|
while (i < n1 && j < n2) {
|
||||||
|
if (!(grater(Left[i], Right[j]))) {
|
||||||
|
buff[k] = Left[i];
|
||||||
|
i++;
|
||||||
|
} else {
|
||||||
|
buff[k] = Right[j];
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
k++;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (i < n1) {
|
||||||
|
buff[k] = Left[i];
|
||||||
|
i++;
|
||||||
|
k++;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (j < n2) {
|
||||||
|
buff[k] = Right[j];
|
||||||
|
j++;
|
||||||
|
k++;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete[] Left;
|
||||||
|
delete[] Right;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Type>
|
||||||
|
static void mergeSort(Type* buff, int left, int right, bool (*grater)(const Type& obj1, const Type& obj2)) {
|
||||||
|
|
||||||
|
if (left >= right) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int middle = left + (right - left) / 2;
|
||||||
|
|
||||||
|
mergeSort(buff, left, middle, grater);
|
||||||
|
mergeSort(buff, middle + 1, right, grater);
|
||||||
|
merge(buff, left, middle, right, grater);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SortInsert {
|
||||||
|
template <typename Type>
|
||||||
|
static void sort(Type* buff, int length, bool (*grater)(const Type& obj1, const Type& obj2) = &compare) {
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
for (int j = i + 1; j < length; j++) {
|
||||||
|
if (grater(*buff[i], *buff[j])) {
|
||||||
|
swap(buff[i], buff[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
63
Utils/public/Testing.hpp
Normal file
63
Utils/public/Testing.hpp
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "List.hpp"
|
||||||
|
|
||||||
|
namespace tp {
|
||||||
|
class Testing {
|
||||||
|
public:
|
||||||
|
|
||||||
|
struct FailedCheck {
|
||||||
|
const char* expression = nullptr;
|
||||||
|
const char* file = nullptr;
|
||||||
|
ualni line = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
Testing() = default;
|
||||||
|
void startTest(const char* name);
|
||||||
|
void endTest();
|
||||||
|
void addFailedCheck(const FailedCheck& info);
|
||||||
|
void reportState();
|
||||||
|
void setRootName(const char* name);
|
||||||
|
[[nodiscard]] bool hasFailed();
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct TestingNode {
|
||||||
|
List<FailedCheck> mFailedChecks;
|
||||||
|
List<TestingNode*> mSubTests;
|
||||||
|
const char* mName = "Unnamed";
|
||||||
|
TestingNode* mParent = nullptr;
|
||||||
|
bool mHasFailed = false;
|
||||||
|
|
||||||
|
void report(const char* path = nullptr) const;
|
||||||
|
void updateState();
|
||||||
|
~TestingNode();
|
||||||
|
};
|
||||||
|
|
||||||
|
TestingNode mRootTest;
|
||||||
|
TestingNode* mCurrent = &mRootTest;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern Testing gTesting;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define TEST_DEF(Name)\
|
||||||
|
static void Name##FunctorBody();\
|
||||||
|
void test##Name() { \
|
||||||
|
tp::gTesting.startTest(#Name);\
|
||||||
|
Name##FunctorBody();\
|
||||||
|
tp::gTesting.endTest();\
|
||||||
|
} \
|
||||||
|
void Name##FunctorBody()
|
||||||
|
|
||||||
|
#define TEST_DEF_STATIC(Name)\
|
||||||
|
static void Name##FunctorBody();\
|
||||||
|
static void test##Name() { \
|
||||||
|
tp::gTesting.startTest(#Name);\
|
||||||
|
Name##FunctorBody();\
|
||||||
|
tp::gTesting.endTest();\
|
||||||
|
} \
|
||||||
|
void Name##FunctorBody()
|
||||||
|
|
||||||
|
#define TEST(expr) if (!(expr)) tp::gTesting.addFailedCheck({ #expr, __FILE__, __LINE__ })
|
||||||
|
#define TEST_EQUAL(l, r) if (!((l) == (r))) tp::gTesting.addFailedCheck({ #l" == "#r, __FILE__, __LINE__ })
|
||||||
58
Utils/public/Timing.hpp
Normal file
58
Utils/public/Timing.hpp
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Environment.hpp"
|
||||||
|
|
||||||
|
namespace tp {
|
||||||
|
|
||||||
|
typedef alni time_ms;
|
||||||
|
typedef alni time_ns;
|
||||||
|
|
||||||
|
extern time_ms gCurrentTime;
|
||||||
|
|
||||||
|
class Timer {
|
||||||
|
|
||||||
|
time_ms mStart;
|
||||||
|
time_ms mDuration;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
Timer();
|
||||||
|
explicit Timer(time_ms time);
|
||||||
|
|
||||||
|
time_ms start();
|
||||||
|
time_ms duration();
|
||||||
|
void setDuration(time_ms dur);
|
||||||
|
|
||||||
|
bool isTimeout();
|
||||||
|
void reset();
|
||||||
|
time_ms timePassed();
|
||||||
|
time_ms remainder();
|
||||||
|
void wait();
|
||||||
|
|
||||||
|
float easeIn(time_ms duration = 0);
|
||||||
|
float easeOut(time_ms duration = 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
void sleep(time_ms duration);
|
||||||
|
time_ms get_time();
|
||||||
|
|
||||||
|
struct FpsCounter {
|
||||||
|
halni frames = 0;
|
||||||
|
Timer time;
|
||||||
|
halni fps = 0;
|
||||||
|
|
||||||
|
FpsCounter() : time(1000) {}
|
||||||
|
|
||||||
|
void update(bool log = true) {
|
||||||
|
frames++;
|
||||||
|
if (time.isTimeout()) {
|
||||||
|
fps = frames;
|
||||||
|
if (log) {
|
||||||
|
// printf("fps %i \n", fps);
|
||||||
|
}
|
||||||
|
frames = 0;
|
||||||
|
time.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
79
Utils/public/Utils.hpp
Normal file
79
Utils/public/Utils.hpp
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "BaseModule.hpp"
|
||||||
|
|
||||||
|
#define PTR_OFFSET(first, offset) (*((&first) + offset))
|
||||||
|
#define MEMBER_OFFSET(s, m) (alni(&(((s*)0)->m)))
|
||||||
|
|
||||||
|
namespace tp {
|
||||||
|
|
||||||
|
extern ModuleManifest gModuleUtils;
|
||||||
|
|
||||||
|
void memsetv(void* p, uhalni bytesize, uint1 val);
|
||||||
|
void memcp(void* left, const void* right, uhalni len);
|
||||||
|
int1 memequal(const void* left, const void* right, uhalni len);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace tp {
|
||||||
|
[[nodiscard]] alnf randf();
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace tp {
|
||||||
|
|
||||||
|
template <typename T1, typename T2>
|
||||||
|
class Pair {
|
||||||
|
public:
|
||||||
|
Pair() {}
|
||||||
|
Pair(T1 t1, T2 t2) : head(t1), tail(t2) {}
|
||||||
|
union { T1 t1; T1 head; T1 x; };
|
||||||
|
union { T2 t2; T2 tail; T2 y; };
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Type = alni>
|
||||||
|
class Bits {
|
||||||
|
Type mFlags = 0;
|
||||||
|
public:
|
||||||
|
Bits() = default;
|
||||||
|
explicit Bits(Type val) { mFlags = val; }
|
||||||
|
explicit Bits(bool val) { for (int bit = 0; bit < sizeof(Type); bit++) { set(bit, val); } }
|
||||||
|
bool get(int1 idx) { return mFlags & (1l << idx); }
|
||||||
|
void set(int1 idx, bool val) {
|
||||||
|
if (val) {
|
||||||
|
mFlags |= (1l << idx);
|
||||||
|
} else {
|
||||||
|
mFlags &= ~(1l << idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template<typename tType = ualni>
|
||||||
|
class Range {
|
||||||
|
public:
|
||||||
|
class Iterator {
|
||||||
|
public:
|
||||||
|
tType mIndex;
|
||||||
|
explicit Iterator(tType pStartIndex) : mIndex(pStartIndex) {}
|
||||||
|
tType index() const { return mIndex; }
|
||||||
|
inline void operator++() { mIndex++; }
|
||||||
|
inline operator tType() const { return mIndex; }
|
||||||
|
inline bool operator==(Iterator pIndex) { return mIndex == pIndex.mIndex; }
|
||||||
|
inline bool operator!=(Iterator pIndex) { return mIndex != pIndex.mIndex; }
|
||||||
|
inline const Iterator& operator*() { return *this; }
|
||||||
|
};
|
||||||
|
|
||||||
|
tType mBegin = 0;
|
||||||
|
tType mEnd = 0;
|
||||||
|
|
||||||
|
Range() = default;
|
||||||
|
explicit Range(tType pEndIndex) : mBegin(0), mEnd(pEndIndex) {}
|
||||||
|
Range(tType pStartIndex, tType pEndIndex) : mBegin(pStartIndex), mEnd(pEndIndex) {}
|
||||||
|
|
||||||
|
bool valid() { return mBegin < mEnd; }
|
||||||
|
|
||||||
|
tType idxBegin() const { return mBegin; }
|
||||||
|
tType idxEnd() const { return mEnd; }
|
||||||
|
Iterator begin() { return Iterator(mBegin); }
|
||||||
|
Iterator end() { return Iterator(mEnd); }
|
||||||
|
};
|
||||||
|
}
|
||||||
67
Utils/tests/Tests.cpp
Normal file
67
Utils/tests/Tests.cpp
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
|
||||||
|
#include "Utils.hpp"
|
||||||
|
#include "Debugging.hpp"
|
||||||
|
#include "Testing.hpp"
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
using namespace tp;
|
||||||
|
|
||||||
|
void printSnapshot(const tp::CallStackCapture::CallStack* snapshot) {
|
||||||
|
printf("CallStack: \n");
|
||||||
|
for (auto frame : *snapshot) {
|
||||||
|
auto symbols = gCSCapture->getSymbols(frame.getFrame());
|
||||||
|
printf(" %s ----- %s:%llu\n", symbols->getFunc(), symbols->getFile(), symbols->getLine());
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void common() {
|
||||||
|
gCSCapture->getSnapshot();
|
||||||
|
}
|
||||||
|
|
||||||
|
void first() {
|
||||||
|
common();
|
||||||
|
common();
|
||||||
|
common();
|
||||||
|
}
|
||||||
|
|
||||||
|
void second() {
|
||||||
|
common();
|
||||||
|
common();
|
||||||
|
common();
|
||||||
|
common();
|
||||||
|
}
|
||||||
|
|
||||||
|
void third() {
|
||||||
|
common();
|
||||||
|
common();
|
||||||
|
}
|
||||||
|
|
||||||
|
void root() {
|
||||||
|
first();
|
||||||
|
second();
|
||||||
|
third();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_DEF(Debugging) {
|
||||||
|
root();
|
||||||
|
|
||||||
|
for (auto cs : *gCSCapture) {
|
||||||
|
printSnapshot(cs.getCallStack());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
|
||||||
|
tp::ModuleManifest* deps[] = { &tp::gModuleUtils, nullptr };
|
||||||
|
tp::ModuleManifest testModule("UtilsTest", nullptr, nullptr, deps);
|
||||||
|
|
||||||
|
if (!testModule.initialize()) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
testDebugging();
|
||||||
|
|
||||||
|
testModule.deinitialize();
|
||||||
|
}
|
||||||
|
|
@ -1,186 +0,0 @@
|
||||||
|
|
||||||
#include "StackTrace.hpp"
|
|
||||||
#include "PrivateConfig.hpp"
|
|
||||||
|
|
||||||
#include <Windows.h>
|
|
||||||
#include <DbgHelp.h>
|
|
||||||
#include <iostream>
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
#pragma comment(lib, "dbghelp.lib")
|
|
||||||
|
|
||||||
using namespace tp;
|
|
||||||
|
|
||||||
tp::CallStackSnapshots tp::gCallStackSnapshots;
|
|
||||||
|
|
||||||
// ----------------------- Dict ----------------------- //
|
|
||||||
|
|
||||||
CallStackSnapshots::SnapshotsDict::SnapshotsDict() {
|
|
||||||
mTable = (StackShapshot*)malloc(sizeof(StackShapshot) * mSize);
|
|
||||||
memsetv(mTable, sizeof(StackShapshot) * mSize, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
CallStackSnapshots::StackShapshot CallStackSnapshots::SnapshotsDict::getSlot(ualni aIdx) {
|
|
||||||
return mTable[aIdx];
|
|
||||||
}
|
|
||||||
|
|
||||||
alni CallStackSnapshots::SnapshotsDict::presents(StackShapshot aPtr) {
|
|
||||||
return findSlotRead(aPtr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CallStackSnapshots::SnapshotsDict::put(StackShapshot aPtr) {
|
|
||||||
auto idx = findSlotWrite(aPtr);
|
|
||||||
mTable[idx] = aPtr;
|
|
||||||
mEntries++;
|
|
||||||
|
|
||||||
if ((halnf)mEntries / mSize > 2.f / 3.f) {
|
|
||||||
resize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CallStackSnapshots::SnapshotsDict::resize() {
|
|
||||||
alni nslots_old = mSize;
|
|
||||||
auto table_old = mTable;
|
|
||||||
|
|
||||||
mSize *= 2;
|
|
||||||
mTable = (StackShapshot*)malloc(sizeof(StackShapshot) * mSize);
|
|
||||||
memsetv(mTable, sizeof(StackShapshot) * mSize, 0);
|
|
||||||
mEntries = 0;
|
|
||||||
|
|
||||||
for (alni i = 0; i < nslots_old; i++) {
|
|
||||||
if (!table_old[i]) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
alni idx = findSlotWrite(table_old[i]);
|
|
||||||
mTable[idx] = table_old[i];
|
|
||||||
mEntries++;
|
|
||||||
}
|
|
||||||
|
|
||||||
delete[] table_old;
|
|
||||||
}
|
|
||||||
|
|
||||||
alni CallStackSnapshots::SnapshotsDict::findSlotRead(StackShapshot key) {
|
|
||||||
ualni const hased_key = hash(key);
|
|
||||||
ualni const mask = mSize - 1;
|
|
||||||
ualni const shift = (hased_key >> 5) & ~1;
|
|
||||||
alni idx = hased_key & mask;
|
|
||||||
NEXT:
|
|
||||||
if (!mTable[idx]) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (compare(mTable[idx], key)) {
|
|
||||||
return idx;
|
|
||||||
}
|
|
||||||
SKIP:
|
|
||||||
idx = ((5 * idx) + 1 + shift) & mask;
|
|
||||||
goto NEXT;
|
|
||||||
}
|
|
||||||
|
|
||||||
ualni CallStackSnapshots::SnapshotsDict::findSlotWrite(StackShapshot key) {
|
|
||||||
ualni const hased_key = hash(key);
|
|
||||||
ualni const mask = mSize - 1;
|
|
||||||
ualni const shift = (hased_key >> 5) & ~1;
|
|
||||||
ualni idx = hased_key & mask;
|
|
||||||
NEXT:
|
|
||||||
if (!mTable[idx]) {
|
|
||||||
return idx;
|
|
||||||
}
|
|
||||||
idx = ((5 * idx) + 1 + shift) & mask;
|
|
||||||
goto NEXT;
|
|
||||||
}
|
|
||||||
|
|
||||||
CallStackSnapshots::StackShapshot CallStackSnapshots::SnapshotsDict::newStackSnapshot(ualni aDepth) {
|
|
||||||
auto const size = sizeof(FramePointer) * (aDepth + 1);
|
|
||||||
auto out = (StackShapshot*)malloc(size);
|
|
||||||
memsetv(out, size, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
ualni CallStackSnapshots::SnapshotsDict::hash(StackShapshot snapshot) {
|
|
||||||
ualni out = 0;
|
|
||||||
for (FramePointer* iter = snapshot; iter; iter++) { out += *iter; }
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CallStackSnapshots::SnapshotsDict::compare(StackShapshot left, StackShapshot right) {
|
|
||||||
FramePointer* iter_left = left;
|
|
||||||
FramePointer* iter_right = right;
|
|
||||||
do {
|
|
||||||
if (*iter_left != *iter_right) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
iter_left++;
|
|
||||||
iter_right++;
|
|
||||||
} while (iter_left && iter_right);
|
|
||||||
if (*iter_left != *iter_right) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
CallStackSnapshots::SnapshotsDict::~SnapshotsDict() {
|
|
||||||
for (ualni idx = 0; idx < mSize; idx++) {
|
|
||||||
if (mTable[idx]) {
|
|
||||||
free(mTable[idx]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
free(mTable);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------- CallStackSnapshots ----------------------- //
|
|
||||||
|
|
||||||
CallStackSnapshots::StackShapshot CallStackSnapshots::getStack(ualni& len) {
|
|
||||||
enum { MAX_DEPTH = MEM_STACK_TRACE_MAX_DEPTH };
|
|
||||||
static FramePointer pointers[MAX_DEPTH];
|
|
||||||
len = 0;
|
|
||||||
|
|
||||||
CONTEXT context;
|
|
||||||
RtlCaptureContext(&context);
|
|
||||||
|
|
||||||
STACKFRAME64 stackFrame;
|
|
||||||
ZeroMemory(&stackFrame, sizeof(STACKFRAME64));
|
|
||||||
stackFrame.AddrPC.Mode = AddrModeFlat;
|
|
||||||
stackFrame.AddrFrame.Mode = AddrModeFlat;
|
|
||||||
stackFrame.AddrStack.Mode = AddrModeFlat;
|
|
||||||
stackFrame.AddrPC.Offset = context.Rip;
|
|
||||||
stackFrame.AddrFrame.Offset = context.Rbp;
|
|
||||||
stackFrame.AddrStack.Offset = context.Rsp;
|
|
||||||
|
|
||||||
HANDLE processHandle = GetCurrentProcess();
|
|
||||||
HANDLE threadHandle = GetCurrentThread();
|
|
||||||
|
|
||||||
while (
|
|
||||||
len < MAX_DEPTH &&
|
|
||||||
StackWalk64(IMAGE_FILE_MACHINE_AMD64, processHandle, threadHandle, &stackFrame, &context, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL)
|
|
||||||
)
|
|
||||||
{
|
|
||||||
pointers[len] = stackFrame.AddrFrame.Offset;
|
|
||||||
len++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return pointers;
|
|
||||||
}
|
|
||||||
|
|
||||||
CallStackSnapshots::StackShapshot CallStackSnapshots::capture() {
|
|
||||||
ualni len;
|
|
||||||
auto stack = getStack(len);
|
|
||||||
auto idx = mSnapshots.presents(stack);
|
|
||||||
|
|
||||||
if (idx == -1) {
|
|
||||||
auto new_snapshot = mSnapshots.newStackSnapshot(len + 1);
|
|
||||||
memcp(new_snapshot, stack, len * sizeof(FramePointer));
|
|
||||||
new_snapshot[len] = 0;
|
|
||||||
mSnapshots.put(new_snapshot);
|
|
||||||
return new_snapshot;
|
|
||||||
}
|
|
||||||
|
|
||||||
return mSnapshots.getSlot(idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CallStackSnapshots::saveToFile(StackShapshot* snapshots, const char* filepath) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
CallStackSnapshots::StackShapshot* CallStackSnapshots::loadFromFile(const char* filepath) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "common.h"
|
|
||||||
|
|
||||||
namespace tp {
|
|
||||||
|
|
||||||
struct CallStackSnapshots {
|
|
||||||
|
|
||||||
typedef alni FramePointer;
|
|
||||||
typedef FramePointer* StackShapshot; // NULL Terminated Frames
|
|
||||||
|
|
||||||
StackShapshot capture();
|
|
||||||
|
|
||||||
void saveToFile(StackShapshot* snapshots, const char* filepath);
|
|
||||||
StackShapshot* loadFromFile(const char* filepath);
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
struct SnapshotsDict {
|
|
||||||
SnapshotsDict();
|
|
||||||
StackShapshot newStackSnapshot(ualni aDepth);
|
|
||||||
void put(StackShapshot aPtr);
|
|
||||||
StackShapshot getSlot(ualni aIdx);
|
|
||||||
alni presents(StackShapshot aPtr);
|
|
||||||
~SnapshotsDict();
|
|
||||||
|
|
||||||
private:
|
|
||||||
StackShapshot* mTable = NULL;
|
|
||||||
uhalni mSize = 512;
|
|
||||||
uhalni mEntries = 0;
|
|
||||||
|
|
||||||
ualni hash(StackShapshot snapshot);
|
|
||||||
bool compare(StackShapshot left, StackShapshot right);
|
|
||||||
alni findSlotRead(StackShapshot key);
|
|
||||||
ualni findSlotWrite(StackShapshot key);
|
|
||||||
void resize();
|
|
||||||
} mSnapshots;
|
|
||||||
|
|
||||||
StackShapshot getStack(ualni& len);
|
|
||||||
};
|
|
||||||
|
|
||||||
extern CallStackSnapshots gCallStackSnapshots;
|
|
||||||
};
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "HeapAllocator.hpp"
|
|
||||||
#include "ChunkAllocator.hpp"
|
|
||||||
#include "PoolAllocator.hpp"
|
|
||||||
|
|
||||||
namespace tp {
|
|
||||||
extern ModuleManifest gModuleAllocator;
|
|
||||||
};
|
|
||||||
|
|
||||||
inline void* operator new(size_t aSize, void* aWhere) noexcept { return aWhere; }
|
|
||||||
|
|
||||||
void* operator new(size_t aSize);
|
|
||||||
void* operator new[](size_t _Size);
|
|
||||||
void operator delete(void* aPtr);
|
|
||||||
void operator delete[](void* aPtr);
|
|
||||||
|
|
||||||
void* operator new(size_t aSize, tp::HeapAlloc& aAlloc);
|
|
||||||
void* operator new[](size_t _Size, tp::HeapAlloc& aAlloc);
|
|
||||||
void operator delete(void* aPtr, tp::HeapAlloc& aAlloc);
|
|
||||||
void operator delete[](void* aPtr, tp::HeapAlloc& aAlloc);
|
|
||||||
|
|
||||||
void* operator new(size_t aSize, tp::HeapAllocGlobal& aAlloc);
|
|
||||||
void* operator new[](size_t _Size, tp::HeapAllocGlobal& aAlloc);
|
|
||||||
void operator delete(void* aPtr, tp::HeapAllocGlobal& aAlloc);
|
|
||||||
void operator delete[](void* aPtr, tp::HeapAllocGlobal& aAlloc);
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
|
|
||||||
#include "allocators.hpp"
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
|
|
||||||
tp::ModuleManifest* ModuleDependencies[] = { &tp::gModuleAllocator, NULL };
|
|
||||||
tp::ModuleManifest TestModule("Test", NULL, NULL, ModuleDependencies);
|
|
||||||
if (!TestModule.initialize()) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
tp::HeapAllocGlobal alloc;
|
|
||||||
|
|
||||||
int* val = new(alloc) int();
|
|
||||||
delete(alloc, val);
|
|
||||||
|
|
||||||
TestModule.deinitialize();
|
|
||||||
}
|
|
||||||
|
|
@ -1,291 +0,0 @@
|
||||||
|
|
||||||
#include <math.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
#include "allocators.h"
|
|
||||||
|
|
||||||
struct test_struct {
|
|
||||||
tp::alni val = 0;
|
|
||||||
|
|
||||||
test_struct() { val = 1; }
|
|
||||||
~test_struct() { val = -1; }
|
|
||||||
|
|
||||||
bool operator==(const test_struct& in) { return in.val == val; }
|
|
||||||
};
|
|
||||||
|
|
||||||
template <tp::alni size>
|
|
||||||
struct allocator_test {
|
|
||||||
test_struct data[size];
|
|
||||||
bool is_allocated[size];
|
|
||||||
tp::alni n_loaded = 0;
|
|
||||||
|
|
||||||
test_struct* allocations[size];
|
|
||||||
|
|
||||||
tp::AbstractAllocator* alloc;
|
|
||||||
tp::AbstractAllocator* parent_alloc;
|
|
||||||
const char* allocator_name = NULL;
|
|
||||||
|
|
||||||
tp::alni rand_idx(bool state) {
|
|
||||||
RAND:
|
|
||||||
|
|
||||||
tp::alni idx = (tp::alni)(tp::randf() * (size + 1));
|
|
||||||
CLAMP(idx, 0, size - 1);
|
|
||||||
|
|
||||||
if (state == is_allocated[idx]) {
|
|
||||||
goto RAND;
|
|
||||||
}
|
|
||||||
return idx;
|
|
||||||
}
|
|
||||||
|
|
||||||
allocator_test(tp::AbstractAllocator* palloc, const char* pallocator_name,
|
|
||||||
tp::AbstractAllocator* p_parent_alloc) {
|
|
||||||
allocator_name = pallocator_name;
|
|
||||||
parent_alloc = p_parent_alloc;
|
|
||||||
alloc = palloc;
|
|
||||||
for (tp::alni i = 0; i < size; i++) {
|
|
||||||
RAND:
|
|
||||||
tp::alni val = tp::alni(tp::randf() * (size + 100.f));
|
|
||||||
for (tp::alni check_idx = 0; check_idx < size; check_idx++) {
|
|
||||||
if (data[check_idx].val == val) {
|
|
||||||
goto RAND;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data[i].val = val;
|
|
||||||
is_allocated[i] = false;
|
|
||||||
allocations[i] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void verify_integrity() {
|
|
||||||
// verify data integrity
|
|
||||||
for (tp::alni i = 0; i < size; i++) {
|
|
||||||
if (is_allocated[i]) {
|
|
||||||
assert(*allocations[i] == data[i] && "data is currupted\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (alloc->isWrapSupport()) assert(!alloc->isWrapCorrupted());
|
|
||||||
if (parent_alloc && parent_alloc->isWrapSupport())
|
|
||||||
assert(!parent_alloc->isWrapCorrupted());
|
|
||||||
|
|
||||||
verify_sizes();
|
|
||||||
}
|
|
||||||
|
|
||||||
void verify_sizes() {
|
|
||||||
return;
|
|
||||||
#ifdef MEM_TRACE
|
|
||||||
assert(alloc->sizeInuse() == n_loaded * sizeof(test_struct) &&
|
|
||||||
"invalid inuse size\n");
|
|
||||||
assert(alloc->sizeReserved() >= n_loaded * (tp::alni)sizeof(test_struct) &&
|
|
||||||
"invalid reserved size\n");
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void load_item(tp::alni idx) {
|
|
||||||
if (!is_allocated[idx]) {
|
|
||||||
allocations[idx] = new (alloc) test_struct();
|
|
||||||
|
|
||||||
assert(allocations[idx] && "allocator returned NULL");
|
|
||||||
|
|
||||||
allocations[idx]->val = data[idx].val;
|
|
||||||
is_allocated[idx] = true;
|
|
||||||
n_loaded++;
|
|
||||||
verify_integrity();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void unload_item(tp::alni idx) {
|
|
||||||
if (is_allocated[idx]) {
|
|
||||||
verify_integrity();
|
|
||||||
delete allocations[idx];
|
|
||||||
is_allocated[idx] = false;
|
|
||||||
n_loaded--;
|
|
||||||
verify_integrity();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void change_states(tp::Range<tp::alni> rg, bool state, bool reversed = false,
|
|
||||||
bool random = false) {
|
|
||||||
for (auto i : rg) {
|
|
||||||
tp::alni idx = i;
|
|
||||||
|
|
||||||
if (random) {
|
|
||||||
idx = rand_idx(state);
|
|
||||||
}
|
|
||||||
else if (reversed) {
|
|
||||||
idx = size - i - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
(state) ? load_item(idx) : unload_item(idx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// full down-up load then up-down unload
|
|
||||||
void test1() {
|
|
||||||
change_states({ 0, size }, 1);
|
|
||||||
change_states({ 0, size }, 0, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// full down-up load then down-up unload
|
|
||||||
void test2() {
|
|
||||||
change_states({ 0, size }, 1);
|
|
||||||
change_states({ 0, size }, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// full random load then random unload
|
|
||||||
void test3() {
|
|
||||||
change_states({ 0, size }, 1, 0, 1);
|
|
||||||
change_states({ 0, size }, 0, 0, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// multipul tests 1-3
|
|
||||||
void test4() {
|
|
||||||
test1();
|
|
||||||
test1();
|
|
||||||
|
|
||||||
test2();
|
|
||||||
test2();
|
|
||||||
|
|
||||||
test3();
|
|
||||||
test3();
|
|
||||||
}
|
|
||||||
|
|
||||||
static tp::alnf sineupf(tp::alnf asize, tp::alnf x, bool reverse) {
|
|
||||||
tp::alnf end = 4 * PI;
|
|
||||||
tp::alnf a = (2 / 7.f) * asize;
|
|
||||||
tp::alnf b = end / asize;
|
|
||||||
|
|
||||||
tp::alni c = ((-1 * reverse) + (1 * !reverse));
|
|
||||||
tp::alnf c1 = (x - (end * reverse)) / b;
|
|
||||||
tp::alnf c2 = (a * sin(x - (end * reverse)));
|
|
||||||
tp::alnf out = c1 + c2;
|
|
||||||
return c * out;
|
|
||||||
}
|
|
||||||
|
|
||||||
// sin load & sin unload with ~1/2 drop factor
|
|
||||||
void test5() {
|
|
||||||
tp::alnf end = 4 * PI;
|
|
||||||
tp::alnf step = end / 4.f;
|
|
||||||
|
|
||||||
for (char i = 0; i < 2; i++) {
|
|
||||||
for (tp::alnf x = 0; x <= end; x += step) {
|
|
||||||
tp::alni target_alloc_count = (tp::alni)ceil(sineupf(size, x, i));
|
|
||||||
CLAMP(target_alloc_count, 0, size);
|
|
||||||
|
|
||||||
while (n_loaded > target_alloc_count) {
|
|
||||||
unload_item(rand_idx(0));
|
|
||||||
}
|
|
||||||
while (n_loaded < target_alloc_count) {
|
|
||||||
load_item(rand_idx(1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef MEM_WRAP
|
|
||||||
void check_wrap(tp::alni offset, bool after) {
|
|
||||||
CLAMP(offset, 1, WRAP_LEN);
|
|
||||||
|
|
||||||
test_struct* ts = allocations[rand_idx(0)];
|
|
||||||
tp::alni shift = (sizeof(test_struct) * after) + (offset - 1) * after -
|
|
||||||
offset * (!after);
|
|
||||||
tp::uint1* address = (((tp::uint1*)ts) + shift);
|
|
||||||
|
|
||||||
tp::uint1 val = *address;
|
|
||||||
*address = 5;
|
|
||||||
assert(alloc->isWrapCorrupted());
|
|
||||||
*address = val;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// mem guards test
|
|
||||||
void test6() {
|
|
||||||
change_states({ 0, size }, 1);
|
|
||||||
|
|
||||||
#ifdef MEM_WRAP
|
|
||||||
for (tp::alni after = 0; after < 2; after++) {
|
|
||||||
for (tp::alni offset = 1; offset <= WRAP_LEN; offset++) {
|
|
||||||
check_wrap(offset, after);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
change_states({ 0, size }, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void run_tests() {
|
|
||||||
try {
|
|
||||||
test1();
|
|
||||||
test2();
|
|
||||||
test3();
|
|
||||||
test4();
|
|
||||||
test5();
|
|
||||||
if (alloc->isWrapSupport()) {
|
|
||||||
test6();
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("%s - passed\n", allocator_name);
|
|
||||||
if (!alloc->isWrapSupport()) {
|
|
||||||
printf(" WARNING: %s has no wrap support!! \n", allocator_name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (...) {
|
|
||||||
printf("%s - failed\n", allocator_name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void heap_alloc_test() {
|
|
||||||
tp::alloc_init(); {
|
|
||||||
try {
|
|
||||||
allocator_test<150> hatest(tp::heapalloc, "heap allocator", NULL);
|
|
||||||
hatest.run_tests();
|
|
||||||
}
|
|
||||||
catch (...) {
|
|
||||||
printf("heap alloc failed\n");
|
|
||||||
}
|
|
||||||
} tp::alloc_uninit();
|
|
||||||
}
|
|
||||||
|
|
||||||
void chunk_alloc_test() {
|
|
||||||
tp::alloc_init(); {
|
|
||||||
try {
|
|
||||||
tp::ChunkAlloc calloc(sizeof(test_struct), 50);
|
|
||||||
allocator_test<50> ca_test(&calloc, "chunk allocator", tp::heapalloc);
|
|
||||||
ca_test.run_tests();
|
|
||||||
}
|
|
||||||
catch (...) {
|
|
||||||
printf("chunk alloc failed\n");
|
|
||||||
}
|
|
||||||
} tp::alloc_uninit();
|
|
||||||
}
|
|
||||||
|
|
||||||
void pool_alloc_test() {
|
|
||||||
tp::alloc_init(); {
|
|
||||||
try {
|
|
||||||
tp::PoolAlloc palloc(sizeof(test_struct), 50);
|
|
||||||
allocator_test<150> pa_test(&palloc, "pool allocator", tp::heapalloc);
|
|
||||||
pa_test.run_tests();
|
|
||||||
}
|
|
||||||
catch (...) {
|
|
||||||
printf("chunk alloc failed\n");
|
|
||||||
}
|
|
||||||
} tp::alloc_uninit();
|
|
||||||
}
|
|
||||||
|
|
||||||
void allocators_test() {
|
|
||||||
|
|
||||||
printf("running tests on alocators:\n");
|
|
||||||
|
|
||||||
heap_alloc_test();
|
|
||||||
chunk_alloc_test();
|
|
||||||
pool_alloc_test();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
CROSSPLATFORM_MAIN;
|
|
||||||
int main(int argc, char* argv[]) {
|
|
||||||
tp::print_env_info();
|
|
||||||
allocators_test();
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
|
|
||||||
#pragma once
|
|
||||||
void allocators_test();
|
|
||||||
|
|
@ -1,172 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
#include "allocators.h"
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include "timer.h"
|
|
||||||
|
|
||||||
struct AllocSpeedTets {
|
|
||||||
const char* test_desc = NULL;
|
|
||||||
virtual void exec() {};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Test1 : AllocSpeedTets {
|
|
||||||
Test1() {
|
|
||||||
test_desc = "allocate lots of memory of same sizes, then free it all";
|
|
||||||
}
|
|
||||||
|
|
||||||
const int len = 1000;
|
|
||||||
int size = 100;
|
|
||||||
void* buff[1000];
|
|
||||||
|
|
||||||
void exec_util(tp::AbstractAllocator* alloc) {
|
|
||||||
for (size_t i = 0; i < len; i++) {
|
|
||||||
buff[i] = alloc->Alloc(size);
|
|
||||||
}
|
|
||||||
for (size_t i = 0; i < len; i++) {
|
|
||||||
alloc->Free(buff[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void exec() {
|
|
||||||
tp::time_ms start, end;
|
|
||||||
|
|
||||||
tp::HeapAlloc malloc;
|
|
||||||
tp::PoolAlloc pool(0, 0);
|
|
||||||
tp::ChunkAlloc chunck(0, 0);
|
|
||||||
tp::PickAlloc pick;
|
|
||||||
|
|
||||||
start = tp::get_time();
|
|
||||||
|
|
||||||
end = tp::get_time();
|
|
||||||
printf("Malloc : %lli ms \n", end - start);
|
|
||||||
|
|
||||||
start = tp::get_time();
|
|
||||||
end = tp::get_time();
|
|
||||||
printf("Pool : %lli ms \n", end - start);
|
|
||||||
|
|
||||||
start = tp::get_time();
|
|
||||||
end = tp::get_time();
|
|
||||||
printf("Chunk : %lli ms \n", end - start);
|
|
||||||
|
|
||||||
start = tp::get_time();
|
|
||||||
end = tp::get_time();
|
|
||||||
printf("Pick : %lli ms \n", end - start);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Test2 : AllocSpeedTets {
|
|
||||||
Test2() {
|
|
||||||
test_desc = "allocate lots of memory of different sizes, then free it all";
|
|
||||||
}
|
|
||||||
|
|
||||||
void exec() {
|
|
||||||
tp::time_ms start, end;
|
|
||||||
|
|
||||||
tp::HeapAlloc malloc;
|
|
||||||
tp::PoolAlloc pool(0, 0);
|
|
||||||
tp::ChunkAlloc chunck(0, 0);
|
|
||||||
tp::PickAlloc pick;
|
|
||||||
|
|
||||||
start = tp::get_time();
|
|
||||||
end = tp::get_time();
|
|
||||||
printf("Malloc : %lli ms \n", end - start);
|
|
||||||
|
|
||||||
start = tp::get_time();
|
|
||||||
end = tp::get_time();
|
|
||||||
printf("Pool : %lli ms \n", end - start);
|
|
||||||
|
|
||||||
start = tp::get_time();
|
|
||||||
end = tp::get_time();
|
|
||||||
printf("Chunk : %lli ms \n", end - start);
|
|
||||||
|
|
||||||
start = tp::get_time();
|
|
||||||
end = tp::get_time();
|
|
||||||
printf("Pick : %lli ms \n", end - start);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Test3 : AllocSpeedTets {
|
|
||||||
Test3() {
|
|
||||||
test_desc = "allocate only a few blocks of memory, free them, and repeat this loop several times \
|
|
||||||
\n (repeat for same - sized blocks and different - sized blocks)\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
void exec() {
|
|
||||||
tp::time_ms start, end;
|
|
||||||
|
|
||||||
tp::HeapAlloc malloc;
|
|
||||||
tp::PoolAlloc pool(0, 0);
|
|
||||||
tp::ChunkAlloc chunck(0, 0);
|
|
||||||
tp::PickAlloc pick;
|
|
||||||
|
|
||||||
start = tp::get_time();
|
|
||||||
end = tp::get_time();
|
|
||||||
printf("Malloc : %lli ms \n", end - start);
|
|
||||||
|
|
||||||
start = tp::get_time();
|
|
||||||
end = tp::get_time();
|
|
||||||
printf("Pool : %lli ms \n", end - start);
|
|
||||||
|
|
||||||
start = tp::get_time();
|
|
||||||
end = tp::get_time();
|
|
||||||
printf("Chunk : %lli ms \n", end - start);
|
|
||||||
|
|
||||||
start = tp::get_time();
|
|
||||||
end = tp::get_time();
|
|
||||||
printf("Pick : %lli ms \n", end - start);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Test4 : AllocSpeedTets {
|
|
||||||
Test4() {
|
|
||||||
test_desc = "allocate lots of memory of different sizes, free half of it(e.g.the even allocations), then allocateand free memory in a loop\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
void exec() {
|
|
||||||
tp::time_ms start, end;
|
|
||||||
|
|
||||||
tp::HeapAlloc malloc;
|
|
||||||
tp::PoolAlloc pool(0, 0);
|
|
||||||
tp::ChunkAlloc chunck(0, 0);
|
|
||||||
tp::PickAlloc pick;
|
|
||||||
|
|
||||||
start = tp::get_time();
|
|
||||||
end = tp::get_time();
|
|
||||||
printf("Malloc : %lli ms \n", end - start);
|
|
||||||
|
|
||||||
start = tp::get_time();
|
|
||||||
end = tp::get_time();
|
|
||||||
printf("Pool : %lli ms \n", end - start);
|
|
||||||
|
|
||||||
start = tp::get_time();
|
|
||||||
end = tp::get_time();
|
|
||||||
printf("Chunk : %lli ms \n", end - start);
|
|
||||||
|
|
||||||
start = tp::get_time();
|
|
||||||
end = tp::get_time();
|
|
||||||
printf("Pick : %lli ms \n", end - start);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void banckmark() {
|
|
||||||
|
|
||||||
tp::alloc_init();
|
|
||||||
|
|
||||||
const int tests_len = 1;
|
|
||||||
AllocSpeedTets* tests[] = {
|
|
||||||
new Test1(),
|
|
||||||
new Test2(),
|
|
||||||
new Test3(),
|
|
||||||
new Test4(),
|
|
||||||
};
|
|
||||||
|
|
||||||
for (int i = 0; i < tests_len; i++) {
|
|
||||||
printf("Test %i: \n %s\n", i, tests[i]->test_desc);
|
|
||||||
tests[i]->exec();
|
|
||||||
|
|
||||||
delete tests[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
tp::alloc_uninit();
|
|
||||||
}
|
|
||||||
|
|
@ -1,563 +0,0 @@
|
||||||
|
|
||||||
#include "benchmarker.h"
|
|
||||||
|
|
||||||
#include "implot.h"
|
|
||||||
#include "ImGuiUtils.h"
|
|
||||||
|
|
||||||
tp::alni hash(const tp::string& val) {
|
|
||||||
return tp::hash(val.cstr());
|
|
||||||
}
|
|
||||||
|
|
||||||
config glb_cfg = {
|
|
||||||
|
|
||||||
.live_update = false,
|
|
||||||
|
|
||||||
.heap = true,
|
|
||||||
.pool = true,
|
|
||||||
.chunk = true,
|
|
||||||
|
|
||||||
.current_sizing_pattern = 0,
|
|
||||||
.current_loading_pattern = 0,
|
|
||||||
.current_ordering_pattern = 0,
|
|
||||||
|
|
||||||
.update = 0,
|
|
||||||
|
|
||||||
.time_per_instruction = true,
|
|
||||||
.mem_per_instruction = true,
|
|
||||||
.avreging = 1,
|
|
||||||
|
|
||||||
.chunk_bsize = 100,
|
|
||||||
.chunk_blen = 100,
|
|
||||||
|
|
||||||
.pool_bsize = 100,
|
|
||||||
.pool_blen = 100,
|
|
||||||
};
|
|
||||||
|
|
||||||
benchmarker::benchmarker() /*: ImGui::CompleteApp()*/ {
|
|
||||||
|
|
||||||
i_count = 0;
|
|
||||||
pattern_out = NULL;
|
|
||||||
out.reserve(3);
|
|
||||||
|
|
||||||
patterns.put("constant", new const_pattern());
|
|
||||||
patterns.put("linear", new linear_pattern());
|
|
||||||
patterns.put("random", new random_pattern());
|
|
||||||
}
|
|
||||||
|
|
||||||
void benchmarker::output_draw() {
|
|
||||||
if (ImGui::Begin("Overview")) {
|
|
||||||
|
|
||||||
if (is_output) {
|
|
||||||
if (ImGui::TreeNode("total time")) {
|
|
||||||
for (auto& i : tp::Range(0, 3)) {
|
|
||||||
if (out[i]) {
|
|
||||||
if (out[i]->total_time < 1000) {
|
|
||||||
ImGui::Text("%s : %f 10e-9", out[i]->alloc_type, out[i]->total_time);
|
|
||||||
} else if (out[i]->total_time < 1000000) {
|
|
||||||
ImGui::Text("%s : %f 10e-6", out[i]->alloc_type, out[i]->total_time / 1000.f);
|
|
||||||
} else if (out[i]->total_time < 1000000000) {
|
|
||||||
ImGui::Text("%s : %f 10e-3", out[i]->alloc_type, out[i]->total_time / 1000000.f);
|
|
||||||
} else {
|
|
||||||
ImGui::Text("%s : %f ", out[i]->alloc_type, out[i]->total_time / 1000000000.f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui::TreePop();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui::TreeNode("failures")) {
|
|
||||||
if ((out[0] && out[0]->failed) || (out[1] && out[1]->failed) || (out[2] && out[2]->failed)) {
|
|
||||||
for (auto& i : tp::Range(0, 3)) {
|
|
||||||
if (out[i] && out[i]->failed)
|
|
||||||
ImGui::Text("%s failed", out[i]->alloc_type);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ImGui::Text("all succeeded");
|
|
||||||
}
|
|
||||||
ImGui::TreePop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui::End();
|
|
||||||
|
|
||||||
|
|
||||||
if (cfg.time_per_instruction) {
|
|
||||||
if (ImGui::Begin("Graphs")) {
|
|
||||||
if (is_output) {
|
|
||||||
if (ImPlot::BeginPlot("Time(ns) vs Alloc / Free inst")) {
|
|
||||||
if (cfg.heap) ImPlot::PlotLine("halloc", x_axis, out[0]->time.buff(), (int) i_count);
|
|
||||||
if (cfg.pool) ImPlot::PlotLine("pool", x_axis, out[1]->time.buff(), (int) i_count);
|
|
||||||
if (cfg.chunk) ImPlot::PlotLine("chunk", x_axis, out[2]->time.buff(), (int) i_count);
|
|
||||||
ImPlot::EndPlot();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui::End();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cfg.mem_per_instruction) {
|
|
||||||
if (ImGui::Begin("Graphs")) {
|
|
||||||
if (is_output) {
|
|
||||||
if (ImPlot::BeginPlot("Memory (bytes) at Instruction state")) {
|
|
||||||
if (cfg.heap) ImPlot::PlotLine("halloc", x_axis, out[0]->mem.buff(), (int) i_count);
|
|
||||||
if (cfg.pool) ImPlot::PlotLine("pool", x_axis, out[1]->mem.buff(), (int) i_count);
|
|
||||||
if (cfg.chunk) ImPlot::PlotLine("chunk", x_axis, out[2]->mem.buff(), (int) i_count);
|
|
||||||
ImPlot::EndPlot();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui::End();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui::Begin("Pattern")) {
|
|
||||||
if (is_output) {
|
|
||||||
if (ImPlot::BeginPlot("Alloc/Free instruction info vs instruction idx")) {
|
|
||||||
if (cfg.current_sizing_pattern) ImPlot::PlotLine("allocated item size", x_axis, pattern_out->alloc_size.buff(), (int) i_count);
|
|
||||||
if (cfg.current_ordering_pattern) ImPlot::PlotLine("used data item idx", x_axis, pattern_out->data_idx.buff(), (int) i_count);
|
|
||||||
if (cfg.current_loading_pattern) ImPlot::PlotLine("total items allocated", x_axis, pattern_out->items_loaded.buff(), (int) i_count);
|
|
||||||
ImPlot::EndPlot();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui::End();
|
|
||||||
}
|
|
||||||
|
|
||||||
benchmarker::~benchmarker() {
|
|
||||||
for (auto iter : patterns) {
|
|
||||||
delete iter->val;
|
|
||||||
}
|
|
||||||
clear_out();
|
|
||||||
}
|
|
||||||
|
|
||||||
void benchmarker::select_pattern() {
|
|
||||||
if (patterns.size()) {
|
|
||||||
const char** pattern_names = new const char* [patterns.size()];
|
|
||||||
|
|
||||||
const char* current_opattern_name = NULL;
|
|
||||||
const char* current_lpattern_name = NULL;
|
|
||||||
const char* current_spattern_name = NULL;
|
|
||||||
|
|
||||||
for (auto pattern_name : patterns) {
|
|
||||||
pattern_names[pattern_name.entry_idx] = pattern_name->key.cstr();
|
|
||||||
if (pattern_name->val == glb_cfg.current_ordering_pattern)
|
|
||||||
current_opattern_name = pattern_name->key.cstr();
|
|
||||||
if (pattern_name->val == glb_cfg.current_sizing_pattern)
|
|
||||||
current_spattern_name = pattern_name->key.cstr();
|
|
||||||
if (pattern_name->val == glb_cfg.current_loading_pattern)
|
|
||||||
current_lpattern_name = pattern_name->key.cstr();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui::BeginCombo("Ordering", current_opattern_name)) {
|
|
||||||
for (int n = 0; n < patterns.size(); n++) {
|
|
||||||
bool is_selected = (current_opattern_name == pattern_names[n]);
|
|
||||||
if (ImGui::Selectable(pattern_names[n], is_selected)) {
|
|
||||||
current_opattern_name = pattern_names[n];
|
|
||||||
glb_cfg.current_ordering_pattern = patterns.get(pattern_names[n]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui::EndCombo();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui::BeginCombo("Loading", current_lpattern_name)) {
|
|
||||||
for (int n = 0; n < patterns.size(); n++) {
|
|
||||||
bool is_selected = (current_lpattern_name == pattern_names[n]);
|
|
||||||
if (ImGui::Selectable(pattern_names[n], is_selected)) {
|
|
||||||
current_lpattern_name = pattern_names[n];
|
|
||||||
glb_cfg.current_loading_pattern = patterns.get(pattern_names[n]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui::EndCombo();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui::BeginCombo("Sizing", current_spattern_name)) {
|
|
||||||
for (int n = 0; n < patterns.size(); n++) {
|
|
||||||
bool is_selected = (current_spattern_name == pattern_names[n]);
|
|
||||||
if (ImGui::Selectable(pattern_names[n], is_selected)) {
|
|
||||||
current_spattern_name = pattern_names[n];
|
|
||||||
glb_cfg.current_sizing_pattern = patterns.get(pattern_names[n]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui::EndCombo();
|
|
||||||
}
|
|
||||||
delete pattern_names;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void benchmarker::pattern_combo(const char*& current) {
|
|
||||||
if (patterns.size()) {
|
|
||||||
const char** pattern_names = new const char* [patterns.size()];
|
|
||||||
|
|
||||||
for (auto pattern_name : patterns) {
|
|
||||||
pattern_names[pattern_name.entry_idx] = pattern_name->key.cstr();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui::BeginCombo("Patterns", current)) {
|
|
||||||
for (int n = 0; n < patterns.size(); n++) {
|
|
||||||
bool is_selected = (current == pattern_names[n]);
|
|
||||||
if (ImGui::Selectable(pattern_names[n], is_selected)) {
|
|
||||||
current = pattern_names[n];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui::EndCombo();
|
|
||||||
}
|
|
||||||
delete pattern_names;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void benchmarker::pattern_generator() {
|
|
||||||
|
|
||||||
|
|
||||||
static tp::alni selected_idx = -1;
|
|
||||||
static pattern* child_pattern_active = NULL;
|
|
||||||
|
|
||||||
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x * 0.1f);
|
|
||||||
|
|
||||||
pattern* pattern_edit = NULL;
|
|
||||||
if (ImGui::Begin("Pattern Generator")) {
|
|
||||||
|
|
||||||
if (ImGui::SubMenuBegin("Selector", 1)) {
|
|
||||||
|
|
||||||
const char* prev_pattern = pattern_generator_active;
|
|
||||||
pattern_combo(pattern_generator_active);
|
|
||||||
|
|
||||||
child_pattern_active = (prev_pattern == pattern_generator_active) ? child_pattern_active : NULL;
|
|
||||||
|
|
||||||
if (pattern_generator_active) {
|
|
||||||
tp::alni idx = patterns.presents(pattern_generator_active);
|
|
||||||
if (!MAP_VALID_IDX(idx)) {
|
|
||||||
pattern_edit = 0;
|
|
||||||
pattern_generator_active = 0;
|
|
||||||
} else {
|
|
||||||
pattern_edit = patterns[idx];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//ImGui::Separator();
|
|
||||||
|
|
||||||
static char create_name[100] = {"new pattern name"};
|
|
||||||
ImGui::InputText("pattern name", create_name, 100);
|
|
||||||
|
|
||||||
if (ImGui::Button("Create")) {
|
|
||||||
if (!patterns.presents(create_name)) {
|
|
||||||
pattern* new_pt = new pattern();
|
|
||||||
new_pt->build_in = false;
|
|
||||||
new_pt->pattern_name = create_name;
|
|
||||||
tp::string id = create_name;
|
|
||||||
id.capture();
|
|
||||||
patterns.put(id, new_pt);
|
|
||||||
} else {
|
|
||||||
ImGui::Notify("Such Pattern Already Exists", 3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pattern_edit) {
|
|
||||||
ImGui::SameLine();
|
|
||||||
if (ImGui::Button("Delete")) {
|
|
||||||
if (pattern_edit->build_in) {
|
|
||||||
ImGui::Notify("Cant Remove Built-in Patterns", 3);
|
|
||||||
} else {
|
|
||||||
patterns.remove(pattern_generator_active);
|
|
||||||
delete pattern_edit;
|
|
||||||
pattern_edit = NULL;
|
|
||||||
pattern_generator_active = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::SameLine();
|
|
||||||
if (ImGui::Button("Rename")) {
|
|
||||||
if (pattern_edit->build_in) {
|
|
||||||
ImGui::Notify("Cant Rename Built-in Patterns", 3);
|
|
||||||
} else {
|
|
||||||
patterns.remove(pattern_generator_active);
|
|
||||||
patterns.put(create_name, pattern_edit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui::SubMenuEnd(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pattern_generator_active) {
|
|
||||||
tp::alni idx = patterns.presents(pattern_generator_active);
|
|
||||||
if (!MAP_VALID_IDX(idx)) {
|
|
||||||
pattern_edit = 0;
|
|
||||||
pattern_generator_active = 0;
|
|
||||||
} else {
|
|
||||||
pattern_edit = patterns[idx];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!pattern_edit) {
|
|
||||||
ImGui::Text("Select pattern to edit or create one");
|
|
||||||
ImGui::End();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui::SubMenuBegin("Preview", 1)) {
|
|
||||||
static float preview_res_scale = 0.05f;
|
|
||||||
int resolution = (int) (1000 * preview_res_scale);
|
|
||||||
CLAMP(resolution, 3, 10000);
|
|
||||||
float* x_axis = new float[resolution];
|
|
||||||
float* y_axis = new float[resolution];
|
|
||||||
|
|
||||||
float x = 0;
|
|
||||||
float step = 1.f / (resolution - 1);
|
|
||||||
for (tp::alni idx = 0; idx < resolution; idx++) {
|
|
||||||
x_axis[idx] = x;
|
|
||||||
y_axis[idx] = (tp::flt4) pattern_edit->get_y(&patterns, x);
|
|
||||||
x += step;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (ImPlot::BeginPlot("Pattern")) {
|
|
||||||
ImPlot::PlotLine("toggle", x_axis, y_axis, resolution);
|
|
||||||
ImPlot::EndPlot();
|
|
||||||
}
|
|
||||||
|
|
||||||
delete x_axis;
|
|
||||||
delete y_axis;
|
|
||||||
ImGui::SliderFloat("graph resolution", &preview_res_scale, 0.f, 1.f);
|
|
||||||
ImGui::SubMenuEnd(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui::SubMenuBegin("Compositor", 1)) {
|
|
||||||
|
|
||||||
if (!pattern_edit->build_in) {
|
|
||||||
|
|
||||||
if (ImGui::SubMenuBegin("Child Patterns", 2)) {
|
|
||||||
ImGui::BeginListBox("");
|
|
||||||
for (tp::alni idx = 0; idx < pattern_edit->regions.length(); idx++) {
|
|
||||||
ImGui::PushID((int) idx);
|
|
||||||
if (ImGui::Button(pattern_edit->regions[idx].name.cstr())) {
|
|
||||||
child_pattern_active = patterns.get(pattern_edit->regions[idx].name);
|
|
||||||
selected_idx = idx;
|
|
||||||
}
|
|
||||||
if (selected_idx == idx) {
|
|
||||||
ImGui::SameLine(); ImGui::Text(" - Active");
|
|
||||||
}
|
|
||||||
ImGui::PopID();
|
|
||||||
}
|
|
||||||
ImGui::EndListBox();
|
|
||||||
|
|
||||||
if (selected_idx >= pattern_edit->regions.length()) {
|
|
||||||
selected_idx = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char* append_pattern = NULL;
|
|
||||||
bool add = ImGui::Button(" + ");
|
|
||||||
ImGui::SameLine();
|
|
||||||
pattern_combo(append_pattern);
|
|
||||||
if (add) {
|
|
||||||
if (append_pattern) {
|
|
||||||
pattern_edit->regions.pushBack(child_pattern(append_pattern));
|
|
||||||
} else {
|
|
||||||
ImGui::Notify("Select a Pattern to Append");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (child_pattern_active && selected_idx != -1) {
|
|
||||||
|
|
||||||
if (ImGui::Button(" Up ")) {
|
|
||||||
if (selected_idx > 0) {
|
|
||||||
tp::string tmp = pattern_edit->regions[selected_idx].name;
|
|
||||||
pattern_edit->regions[selected_idx] = pattern_edit->regions[selected_idx - 1];
|
|
||||||
pattern_edit->regions[selected_idx - 1] = tmp;
|
|
||||||
selected_idx--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui::SameLine();
|
|
||||||
if (ImGui::Button("Down")) {
|
|
||||||
if (selected_idx < pattern_edit->regions.length() - 1) {
|
|
||||||
tp::string tmp = pattern_edit->regions[selected_idx].name;
|
|
||||||
pattern_edit->regions[selected_idx] = pattern_edit->regions[selected_idx + 1];
|
|
||||||
pattern_edit->regions[selected_idx + 1] = tmp;
|
|
||||||
selected_idx++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui::SameLine();
|
|
||||||
if (ImGui::Button("Remove")) {
|
|
||||||
pattern_edit->regions.remove(selected_idx);
|
|
||||||
selected_idx = pattern_edit->regions.length() - 1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ImGui::Text("Select Child Pattern");
|
|
||||||
}
|
|
||||||
ImGui::SubMenuEnd(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (child_pattern_active && selected_idx != -1) {
|
|
||||||
if (ImGui::SubMenuBegin("child Pattern Properties", 2)) {
|
|
||||||
ImGui::SliderFloat("Point", &pattern_edit->regions[selected_idx].point, 0.f, 1.f);
|
|
||||||
ImGui::SliderFloat("Lower lim", &pattern_edit->regions[selected_idx].lowerlim, 0.f, 1.f);
|
|
||||||
ImGui::SliderFloat("Upper lim", &pattern_edit->regions[selected_idx].uppernlim, 0.f, 1.f);
|
|
||||||
if (child_pattern_active->build_in) {
|
|
||||||
}
|
|
||||||
ImGui::SubMenuEnd(2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ImGui::Text("Can't Edit Built-In Patterns");
|
|
||||||
}
|
|
||||||
ImGui::SubMenuEnd(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::Separator();
|
|
||||||
|
|
||||||
}
|
|
||||||
ImGui::End();
|
|
||||||
}
|
|
||||||
|
|
||||||
void benchmarker::MainDrawTick() {
|
|
||||||
|
|
||||||
analize(glb_cfg);
|
|
||||||
|
|
||||||
|
|
||||||
ImGui::BeginGroup();
|
|
||||||
|
|
||||||
if (ImGui::WindowEditor("Properties")) {
|
|
||||||
|
|
||||||
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x * 0.5f);
|
|
||||||
|
|
||||||
{
|
|
||||||
if (ImGui::Button("Run")) {
|
|
||||||
glb_cfg.update = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool prev_val = glb_cfg.live_update;
|
|
||||||
ImGui::SameLine(); ImGui::Checkbox("Live Update", &glb_cfg.live_update);
|
|
||||||
if (prev_val != glb_cfg.live_update) {
|
|
||||||
glb_cfg.update = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui::SubMenuBegin("General", 1)) {
|
|
||||||
ImGui::InputInt("Averaging", &glb_cfg.avreging, 1, 100); ImGui::ToolTip("Number of tests to be averaged");
|
|
||||||
ImGui::Checkbox("Collect time per instruction", &glb_cfg.time_per_instruction);
|
|
||||||
ImGui::Checkbox("Collect mem per instruction", &glb_cfg.mem_per_instruction);
|
|
||||||
|
|
||||||
ImGui::SubMenuEnd(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui::SubMenuBegin("Testing Pattern", 1)) {
|
|
||||||
|
|
||||||
select_pattern();
|
|
||||||
|
|
||||||
ImGui::InputInt("size", &glb_cfg.pt_scale.size, 1, 100); ImGui::ToolTip("Y Scale of sizing pattern");
|
|
||||||
ImGui::InputInt("items", &glb_cfg.pt_scale.items, 1, 100); ImGui::ToolTip("Y Scale of ordering and loading patterns");
|
|
||||||
ImGui::InputInt("iterations", &glb_cfg.pt_scale.iterations, 1, 100); ImGui::ToolTip("X Scale of all patterns");
|
|
||||||
|
|
||||||
ImGui::SubMenuEnd(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui::SubMenuBegin("Allocators", 1)) {
|
|
||||||
ImGui::Checkbox("heap", &glb_cfg.heap); ImGui::SameLine(); ImGui::Checkbox("pool", &glb_cfg.pool); ImGui::SameLine(); ImGui::Checkbox("chunk", &glb_cfg.chunk);
|
|
||||||
|
|
||||||
if (glb_cfg.chunk && ImGui::SubMenuBegin("Chunk", 2)) {
|
|
||||||
ImGui::InputInt("size", &glb_cfg.chunk_bsize, 1, 100); ImGui::ToolTip("Size of a slot in the chunk buffer");
|
|
||||||
ImGui::InputInt("length", &glb_cfg.chunk_blen, 1, 100); ImGui::ToolTip("Number of slots in the chunk buffer");
|
|
||||||
ImGui::SubMenuEnd(2);
|
|
||||||
}
|
|
||||||
if (glb_cfg.pool && ImGui::SubMenuBegin("Pool", 2)) {
|
|
||||||
ImGui::ToolTip("Generalized use of chunk allocator");
|
|
||||||
ImGui::InputInt("size", &glb_cfg.pool_bsize, 1, 100);
|
|
||||||
ImGui::InputInt("length", &glb_cfg.pool_blen, 1, 100);
|
|
||||||
ImGui::SubMenuEnd(2);
|
|
||||||
}
|
|
||||||
ImGui::SubMenuEnd(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
//ImGui::PopItemWidth();
|
|
||||||
}
|
|
||||||
ImGui::End();
|
|
||||||
|
|
||||||
output_draw();
|
|
||||||
|
|
||||||
pattern_generator();
|
|
||||||
|
|
||||||
ImGui::EndGroup();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void benchmarker::analize(config pcfg) {
|
|
||||||
|
|
||||||
if (!((pcfg.update) || (!(this->cfg == pcfg) && cfg.live_update))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->cfg = pcfg;
|
|
||||||
|
|
||||||
clear_out();
|
|
||||||
|
|
||||||
if (!pattern_analizer.init(&patterns, cfg.current_loading_pattern, cfg.current_ordering_pattern, cfg.current_sizing_pattern, &cfg.pt_scale)) {
|
|
||||||
if (pcfg.update) ImGui::Notify("invalid pattern configuration", 3);
|
|
||||||
glb_cfg.update = false;
|
|
||||||
cfg.update = false;
|
|
||||||
is_output = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
reserve_out(&pattern_analizer);
|
|
||||||
|
|
||||||
for (tp::alni iter = 0; iter < pcfg.avreging; iter++) {
|
|
||||||
init_allocators(pcfg);
|
|
||||||
if (cfg.heap) collect(&pattern_analizer, halloc, out[0]);
|
|
||||||
if (cfg.pool) collect(&pattern_analizer, palloc, out[1]);
|
|
||||||
if (cfg.chunk) collect(&pattern_analizer, calloc, out[2]);
|
|
||||||
dest_allocators();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto& i : tp::Range(0, 3)) {
|
|
||||||
if (out[i]) out[i]->scale_all(1.f / pcfg.avreging);
|
|
||||||
}
|
|
||||||
|
|
||||||
i_count = pattern_analizer.max_iterations();
|
|
||||||
if (x_axis) {
|
|
||||||
delete x_axis;
|
|
||||||
x_axis = NULL;
|
|
||||||
}
|
|
||||||
x_axis = new tp::alnf[i_count];
|
|
||||||
for (tp::alni iter = 0; iter < i_count; iter++) {
|
|
||||||
x_axis[iter] = (tp::alnf) iter;
|
|
||||||
}
|
|
||||||
|
|
||||||
glb_cfg.update = false;
|
|
||||||
cfg.update = false;
|
|
||||||
is_output = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void benchmarker::init_allocators(config& pcfg) {
|
|
||||||
if (cfg.heap) halloc = new tp::HeapAlloc();
|
|
||||||
if (cfg.pool) palloc = new tp::PoolAlloc(pcfg.pool_bsize, pcfg.pool_blen);
|
|
||||||
if (cfg.chunk) calloc = new tp::ChunkAlloc(pcfg.chunk_bsize, pcfg.chunk_blen);
|
|
||||||
}
|
|
||||||
|
|
||||||
void benchmarker::dest_allocators() {
|
|
||||||
try {
|
|
||||||
if (cfg.heap) delete halloc;
|
|
||||||
if (cfg.pool) delete palloc;
|
|
||||||
if (cfg.chunk) delete calloc;
|
|
||||||
} catch (...) {
|
|
||||||
}
|
|
||||||
|
|
||||||
halloc = NULL;
|
|
||||||
palloc = NULL;
|
|
||||||
calloc = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
void benchmarker::clear_out() {
|
|
||||||
for (auto i : tp::Range(0, 3)) {
|
|
||||||
if (out[i]) {
|
|
||||||
delete out[i];
|
|
||||||
out[i] = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pattern_out) delete pattern_out;
|
|
||||||
pattern_out = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
void benchmarker::reserve_out(test_pattern* pattern) {
|
|
||||||
pattern_out = new pattern_histogram(pattern);
|
|
||||||
if (cfg.heap) out[0] = new allocator_histogram(pattern, "heap", cfg.time_per_instruction, cfg.mem_per_instruction);
|
|
||||||
if (cfg.pool) out[1] = new allocator_histogram(pattern, "pool", cfg.time_per_instruction, cfg.mem_per_instruction);
|
|
||||||
if (cfg.chunk) out[2] = new allocator_histogram(pattern, "chunk", cfg.time_per_instruction, cfg.mem_per_instruction);
|
|
||||||
}
|
|
||||||
|
|
@ -1,100 +0,0 @@
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "patterns.h"
|
|
||||||
|
|
||||||
#include "allocators.h"
|
|
||||||
#include "array.h"
|
|
||||||
|
|
||||||
#include "gl.h"
|
|
||||||
#include "glcommon.h"
|
|
||||||
#include "window.h"
|
|
||||||
|
|
||||||
enum class load_type {
|
|
||||||
LINEAR,
|
|
||||||
SINE,
|
|
||||||
STEPS,
|
|
||||||
RANDOM
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class order_type {
|
|
||||||
LINEAR,
|
|
||||||
LINEAR_REVERSED,
|
|
||||||
RANDOM,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct config {
|
|
||||||
// general
|
|
||||||
bool live_update = false;
|
|
||||||
|
|
||||||
bool heap = true;
|
|
||||||
bool pool = true;
|
|
||||||
bool chunk = true;
|
|
||||||
|
|
||||||
// pattern
|
|
||||||
pattern* current_sizing_pattern = 0;
|
|
||||||
pattern* current_loading_pattern = 0;
|
|
||||||
pattern* current_ordering_pattern = 0;
|
|
||||||
pattern_scale pt_scale;
|
|
||||||
|
|
||||||
tp::alni update = 0;
|
|
||||||
bool time_per_instruction = 0;
|
|
||||||
bool mem_per_instruction = 0;
|
|
||||||
int avreging;
|
|
||||||
|
|
||||||
// allocators
|
|
||||||
int chunk_bsize = 0;
|
|
||||||
int chunk_blen = 0;
|
|
||||||
|
|
||||||
int pool_bsize = 0;
|
|
||||||
int pool_blen = 0;
|
|
||||||
|
|
||||||
bool operator==(const config& in) {
|
|
||||||
return tp::memequal(this, (config*)(&in), sizeof(config));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct benchmarker {
|
|
||||||
|
|
||||||
config cfg;
|
|
||||||
bool is_output = false;
|
|
||||||
|
|
||||||
// allocators
|
|
||||||
tp::HeapAlloc* halloc = NULL;
|
|
||||||
tp::PoolAlloc* palloc = NULL;
|
|
||||||
tp::ChunkAlloc* calloc = NULL;
|
|
||||||
|
|
||||||
|
|
||||||
tp::Array<allocator_histogram*> out;
|
|
||||||
pattern_histogram* pattern_out;
|
|
||||||
|
|
||||||
tp::alni i_count;
|
|
||||||
tp::alnf* x_axis = NULL;
|
|
||||||
|
|
||||||
tp::HashMap<pattern*, tp::string> patterns;
|
|
||||||
|
|
||||||
pattern_reader pattern_analizer;
|
|
||||||
|
|
||||||
const char* pattern_generator_active = NULL;
|
|
||||||
|
|
||||||
benchmarker();
|
|
||||||
~benchmarker();
|
|
||||||
|
|
||||||
test_pattern* get_pattern(config& cfg);
|
|
||||||
|
|
||||||
void pattern_combo(const char*&);
|
|
||||||
void analize(config cfg);
|
|
||||||
void select_pattern();
|
|
||||||
void output_draw();
|
|
||||||
|
|
||||||
void MainDrawTick();
|
|
||||||
|
|
||||||
void pattern_generator();
|
|
||||||
|
|
||||||
void init_allocators(config& cfg);
|
|
||||||
|
|
||||||
void dest_allocators();
|
|
||||||
|
|
||||||
void clear_out();
|
|
||||||
void reserve_out(test_pattern* pattern);
|
|
||||||
};
|
|
||||||
|
|
@ -1,119 +0,0 @@
|
||||||
|
|
||||||
#include "collector.h"
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
|
|
||||||
allocator_histogram::allocator_histogram(test_pattern* pt, const char* alloc_type, bool p_time_per_inst, bool p_mem_per_inst) {
|
|
||||||
this->alloc_type = alloc_type;
|
|
||||||
time_per_inst = p_time_per_inst;
|
|
||||||
mem_per_inst = p_mem_per_inst;
|
|
||||||
|
|
||||||
if (time_per_inst) {
|
|
||||||
time.reserve(pt->max_iterations());
|
|
||||||
}
|
|
||||||
if (mem_per_inst) {
|
|
||||||
mem.reserve(pt->max_iterations());
|
|
||||||
}
|
|
||||||
data.reserve(pt->data_count());
|
|
||||||
|
|
||||||
total_time = 0;
|
|
||||||
failed = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void allocator_histogram::scale_all(tp::alnf fac) {
|
|
||||||
for (auto& iter : time) {
|
|
||||||
iter.data() = fac * iter.data();
|
|
||||||
}
|
|
||||||
for (auto& iter : mem) {
|
|
||||||
iter.data() = fac * iter.data();
|
|
||||||
}
|
|
||||||
total_time = fac * total_time;
|
|
||||||
}
|
|
||||||
|
|
||||||
allocator_histogram::~allocator_histogram() {
|
|
||||||
}
|
|
||||||
|
|
||||||
void allocator_histogram::mark_resourses_usage(tp::alni idx, tp::alnf p_time, tp::alni p_mem, bool add) {
|
|
||||||
if (mem.length() > idx)
|
|
||||||
add ? mem[idx] += (tp::alnf)p_mem : mem[idx] = (tp::alnf)p_mem;
|
|
||||||
if (time.length() > idx)
|
|
||||||
add ? time[idx] += p_time : time[idx] = p_time;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool execute_instruction(tp::AbstractAllocator* alloc, bool load, tp::alni size, tp::uint1*& data) {
|
|
||||||
bool failed = false;
|
|
||||||
try {
|
|
||||||
if (load) {
|
|
||||||
if (!data) {
|
|
||||||
data = (tp::uint1*)alloc->Alloc(size);
|
|
||||||
if (!data) {
|
|
||||||
failed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (data) {
|
|
||||||
alloc->Free(data);
|
|
||||||
data = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (...) {
|
|
||||||
failed = true;
|
|
||||||
}
|
|
||||||
return !failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void collect(test_pattern* pattern, tp::AbstractAllocator* alloc, allocator_histogram* histogram) {
|
|
||||||
|
|
||||||
tp::alni iter_idx = 0;
|
|
||||||
tp::alni nimtems_loaded = 0;
|
|
||||||
|
|
||||||
auto total_st = std::chrono::high_resolution_clock::now();
|
|
||||||
|
|
||||||
for (iter_idx = 0; iter_idx < pattern->max_iterations(); iter_idx++) {
|
|
||||||
|
|
||||||
tp::alni target_nimtems_loaded = pattern->pick_alloc_count(iter_idx);
|
|
||||||
tp::alni data_idx = pattern->pick_idx(iter_idx);
|
|
||||||
tp::alni load_size = pattern->pick_size(iter_idx);
|
|
||||||
|
|
||||||
auto iter_st = std::chrono::high_resolution_clock::now();
|
|
||||||
while (nimtems_loaded != target_nimtems_loaded) {
|
|
||||||
bool load = nimtems_loaded < target_nimtems_loaded;
|
|
||||||
if (!execute_instruction(alloc, load, load_size, histogram->data[data_idx])) {
|
|
||||||
histogram->mark_resourses_usage(iter_idx, 0, 0, 0);
|
|
||||||
histogram->failed = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
nimtems_loaded += (tp::alni)load + (-1 * (tp::alni)(!load));
|
|
||||||
}
|
|
||||||
auto iter_nd = std::chrono::high_resolution_clock::now();
|
|
||||||
tp::alni dur = std::chrono::duration_cast<std::chrono::nanoseconds>(iter_nd - iter_st).count();
|
|
||||||
histogram->mark_resourses_usage(iter_idx, tp::alnf(dur), alloc->sizeReserved(), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear all out
|
|
||||||
for (iter_idx = 0; iter_idx < pattern->max_iterations(); iter_idx++) {
|
|
||||||
if (histogram->data[iter_idx]) {
|
|
||||||
execute_instruction(alloc, false, 0, histogram->data[iter_idx]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto total_nd = std::chrono::high_resolution_clock::now();
|
|
||||||
tp::alni dur = std::chrono::duration_cast<std::chrono::nanoseconds>(total_nd - total_st).count();
|
|
||||||
histogram->total_time += dur;
|
|
||||||
}
|
|
||||||
|
|
||||||
pattern_histogram::pattern_histogram(test_pattern* pt) {
|
|
||||||
alloc_size.reserve(pt->max_iterations());
|
|
||||||
data_idx.reserve(pt->max_iterations());
|
|
||||||
items_loaded.reserve(pt->max_iterations());
|
|
||||||
|
|
||||||
for (auto& i : tp::Range(0, pt->max_iterations())) {
|
|
||||||
alloc_size[i] = (tp::alnf) pt->pick_size(i);
|
|
||||||
data_idx[i] = (tp::alnf)pt->pick_idx(i);
|
|
||||||
items_loaded[i] = (tp::alnf)pt->pick_alloc_count(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "array.h"
|
|
||||||
|
|
||||||
class test_pattern {
|
|
||||||
public:
|
|
||||||
virtual tp::alni pick_size(tp::alni iter) { return 0; };
|
|
||||||
virtual tp::alni max_size() { return 0; };
|
|
||||||
virtual tp::alni pick_alloc_count(tp::alni iter) { return 0; };
|
|
||||||
virtual tp::alni max_iterations() { return 0; };
|
|
||||||
virtual tp::alni pick_idx(tp::alni iter) { return 0; };
|
|
||||||
virtual tp::alni data_count() { return 0; };
|
|
||||||
};
|
|
||||||
|
|
||||||
struct pattern_histogram {
|
|
||||||
tp::Array<tp::alnf> alloc_size;
|
|
||||||
tp::Array<tp::alnf> data_idx;
|
|
||||||
tp::Array<tp::alnf> items_loaded;
|
|
||||||
|
|
||||||
pattern_histogram(test_pattern* pt);
|
|
||||||
};
|
|
||||||
|
|
||||||
struct allocator_histogram {
|
|
||||||
const char* alloc_type;
|
|
||||||
|
|
||||||
tp::alnf total_time;
|
|
||||||
tp::Array<tp::alnf> time;
|
|
||||||
tp::Array<tp::alnf> mem;
|
|
||||||
bool failed;
|
|
||||||
|
|
||||||
tp::Array<tp::uint1*> data;
|
|
||||||
bool time_per_inst;
|
|
||||||
bool mem_per_inst;
|
|
||||||
|
|
||||||
allocator_histogram(test_pattern* pt, const char* alloc_type, bool time_per_inst, bool mem_per_inst);
|
|
||||||
~allocator_histogram();
|
|
||||||
|
|
||||||
void mark_resourses_usage(tp::alni idx, tp::alnf time, tp::alni mem, bool add);
|
|
||||||
void scale_all(tp::alnf fac);
|
|
||||||
};
|
|
||||||
|
|
||||||
void collect(test_pattern* pattern, tp::AbstractAllocator* alloc, allocator_histogram* histogram);
|
|
||||||
|
|
@ -1,174 +0,0 @@
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "strings.h"
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
#include "collector.h"
|
|
||||||
|
|
||||||
tp::alni hash(const tp::string& val);
|
|
||||||
|
|
||||||
#include "map.h"
|
|
||||||
|
|
||||||
enum class leav_pattern_type {
|
|
||||||
LINEAR,
|
|
||||||
RANDOM,
|
|
||||||
SINE,
|
|
||||||
CONST,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct child_pattern {
|
|
||||||
child_pattern() {}
|
|
||||||
|
|
||||||
child_pattern(tp::string _name) { name = _name; }
|
|
||||||
|
|
||||||
float uppernlim = 1.f;
|
|
||||||
float lowerlim = 0.f;
|
|
||||||
float point = 1.f;
|
|
||||||
tp::string name;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct pattern {
|
|
||||||
|
|
||||||
leav_pattern_type type = leav_pattern_type::CONST;
|
|
||||||
tp::string pattern_name;
|
|
||||||
|
|
||||||
tp::Array<child_pattern> regions;
|
|
||||||
bool build_in = true;
|
|
||||||
|
|
||||||
pattern() {
|
|
||||||
}
|
|
||||||
|
|
||||||
tp::alnf get_y(tp::HashMap<pattern*, tp::string>* patterns, tp::alnf x) {
|
|
||||||
assert(x <= 1.0001f && x >= -0.00001f);
|
|
||||||
|
|
||||||
if (!regions.length()) {
|
|
||||||
return pure_get_y(x);
|
|
||||||
}
|
|
||||||
|
|
||||||
float offset = 0.f;
|
|
||||||
for (tp::alni i = 0; i < regions.length(); i++) {
|
|
||||||
tp::alni idx = patterns->presents(regions[i].name);
|
|
||||||
if (!MAP_VALID_IDX(idx)) {
|
|
||||||
return 0.f;
|
|
||||||
}
|
|
||||||
pattern* child = (*patterns)[idx];
|
|
||||||
assert(child);
|
|
||||||
|
|
||||||
float range = regions[i].point * (1.f - offset);
|
|
||||||
if (offset + range > x) {
|
|
||||||
return regions[i].lowerlim +
|
|
||||||
(child->get_y(patterns, (x - offset) / range) *
|
|
||||||
(regions[i].uppernlim - regions[i].lowerlim));
|
|
||||||
}
|
|
||||||
offset += range;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual tp::alnf pure_get_y(tp::alnf x) { return 0; }
|
|
||||||
|
|
||||||
~pattern() {}
|
|
||||||
};
|
|
||||||
|
|
||||||
// -------------------- build-in patterns ---------------------------- //
|
|
||||||
|
|
||||||
struct const_pattern : pattern {
|
|
||||||
float val = 1.f;
|
|
||||||
const_pattern() {
|
|
||||||
type = leav_pattern_type::CONST;
|
|
||||||
pattern_name = "const";
|
|
||||||
build_in = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
tp::alnf pure_get_y(tp::alnf x) override { return val; }
|
|
||||||
};
|
|
||||||
|
|
||||||
struct linear_pattern : pattern {
|
|
||||||
bool reversed = false;
|
|
||||||
linear_pattern() {
|
|
||||||
type = leav_pattern_type::LINEAR;
|
|
||||||
pattern_name = "linear";
|
|
||||||
build_in = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
tp::alnf pure_get_y(tp::alnf x) override { return reversed ? 1.f - x : x; }
|
|
||||||
};
|
|
||||||
|
|
||||||
struct random_pattern : pattern {
|
|
||||||
random_pattern() {
|
|
||||||
type = leav_pattern_type::RANDOM;
|
|
||||||
build_in = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
tp::alnf pure_get_y(tp::alnf x) override { return tp::randf(); }
|
|
||||||
};
|
|
||||||
|
|
||||||
// -------------------- build-in patterns end ---------------------------- //
|
|
||||||
|
|
||||||
struct pattern_scale {
|
|
||||||
int items = 0;
|
|
||||||
int size = 0;
|
|
||||||
int iterations = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class pattern_reader : public test_pattern {
|
|
||||||
public:
|
|
||||||
bool init(tp::HashMap<pattern*, tp::string>* p_patterns, pattern* p_lpattern,
|
|
||||||
pattern* p_opattern, pattern* p_spattern, pattern_scale* p_scale) {
|
|
||||||
lpattern = p_lpattern;
|
|
||||||
opattern = p_opattern;
|
|
||||||
spattern = p_spattern;
|
|
||||||
|
|
||||||
scale = p_scale;
|
|
||||||
|
|
||||||
patterns = p_patterns;
|
|
||||||
return verify_rulles();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool verify_rulles() {
|
|
||||||
if (!lpattern || !opattern || !spattern) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool out = true;
|
|
||||||
out &= scale->items > 0;
|
|
||||||
out &= scale->size > 0;
|
|
||||||
out &= scale->iterations > 0;
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
tp::HashMap<pattern*, tp::string>* patterns;
|
|
||||||
|
|
||||||
pattern* lpattern = NULL;
|
|
||||||
pattern* opattern = NULL;
|
|
||||||
pattern* spattern = NULL;
|
|
||||||
|
|
||||||
pattern_scale* scale = 0;
|
|
||||||
|
|
||||||
tp::alnf get_x_val(tp::alni iter) {
|
|
||||||
tp::alnf out = iter / (tp::alnf)scale->iterations;
|
|
||||||
return out > 1 ? 1.f : out;
|
|
||||||
}
|
|
||||||
|
|
||||||
tp::alni pick_size(tp::alni iter) override {
|
|
||||||
return (tp::alni)(scale->size * spattern->get_y(patterns, get_x_val(iter)));
|
|
||||||
}
|
|
||||||
|
|
||||||
tp::alni pick_alloc_count(tp::alni iter) override {
|
|
||||||
return (tp::alni)(scale->items * lpattern->get_y(patterns, get_x_val(iter)));
|
|
||||||
}
|
|
||||||
|
|
||||||
tp::alni pick_idx(tp::alni iter) override {
|
|
||||||
tp::alni out = (tp::alni) (scale->items * opattern->get_y(patterns, get_x_val(iter)));
|
|
||||||
CLAMP(out, 0, scale->items - 1);
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
tp::alni max_size() override { return scale->size; }
|
|
||||||
|
|
||||||
tp::alni max_iterations() override { return scale->iterations; }
|
|
||||||
|
|
||||||
tp::alni data_count() override { return scale->iterations; }
|
|
||||||
};
|
|
||||||
|
|
@ -1,93 +0,0 @@
|
||||||
|
|
||||||
#include "SimpleGui.h"
|
|
||||||
#include "debugui.h"
|
|
||||||
#include "memleaks.h"
|
|
||||||
#include "timer.h"
|
|
||||||
|
|
||||||
struct MemLeaksGUI {
|
|
||||||
|
|
||||||
tp::glw::Window window;
|
|
||||||
tp::glw::DebugUI ui;
|
|
||||||
tp::glw::Canvas canvas;
|
|
||||||
tp::glw::WindowSimpleInputs inputs;
|
|
||||||
tp::glw::WindowsHeaderWidget header;
|
|
||||||
tp::MemLeaksData mLeaks;
|
|
||||||
tp::MemLeaksTreeView mView;
|
|
||||||
tp::halnf mHeaderHeight = 5.5f;
|
|
||||||
tp::Timer timer;
|
|
||||||
|
|
||||||
MemLeaksGUI(const char* path) : window(), ui(window, debuguiCallBack, this), mLeaks(path), timer(10) {
|
|
||||||
canvas.setCol1({ 0.5f, 0.5f, 0.5f, 0.5f });
|
|
||||||
window.mAppearence.mHiden = false;
|
|
||||||
mView.setTarget(&mLeaks);
|
|
||||||
}
|
|
||||||
|
|
||||||
void proc() {
|
|
||||||
window.pollEvents();
|
|
||||||
inputs.update(window, 1.f);
|
|
||||||
|
|
||||||
header.header_rec = { (inputs.mWindowSizeMM.x - 60.f) / 2.f, 3.f, 60.f, mHeaderHeight };
|
|
||||||
mView.rect = { 0.f, 0.f, inputs.mWindowSizeMM.x, inputs.mWindowSizeMM.y };
|
|
||||||
|
|
||||||
mView.proc();
|
|
||||||
|
|
||||||
if (inputs.MoveUp()) {
|
|
||||||
mView.zoom_factor -= 0.2f;
|
|
||||||
}
|
|
||||||
else if (inputs.MoveDown()) {
|
|
||||||
mView.zoom_factor += 0.2f;
|
|
||||||
}
|
|
||||||
mView.zoom_factor = tp::clamp(mView.zoom_factor, 0.f, 1.f);
|
|
||||||
|
|
||||||
mView.vieport_crs = inputs.mCrs;
|
|
||||||
mView.vieport_crs_delta = inputs.mCrsmDelta * -1.f;
|
|
||||||
mView.mouse_down = inputs.Activated();
|
|
||||||
mView.mouse_hold = inputs.Anticipating();
|
|
||||||
mView.mouse_up = inputs.Selected();
|
|
||||||
|
|
||||||
header.proc(window, inputs);
|
|
||||||
}
|
|
||||||
|
|
||||||
void draw() {
|
|
||||||
window.beginDraw(); {
|
|
||||||
canvas.beginDraw({ { 0, 0 }, inputs.mWindowSizeMM }, window.mDevice.mDPMM, 1.f); {
|
|
||||||
mView.draw(&canvas);
|
|
||||||
header.draw(canvas);
|
|
||||||
} canvas.endDraw();
|
|
||||||
ui.drawDebugUI(window.mDevice.mDPMM);
|
|
||||||
} window.endDraw();
|
|
||||||
}
|
|
||||||
|
|
||||||
void run() {
|
|
||||||
while (!window.mEvents.mClose) {
|
|
||||||
proc();
|
|
||||||
if (window.mEvents.mRedraw) {
|
|
||||||
draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
timer.wait();
|
|
||||||
timer.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void debugui() {}
|
|
||||||
static void debuguiCallBack(void* self) { ((MemLeaksGUI*)self)->debugui(); }
|
|
||||||
~MemLeaksGUI() {}
|
|
||||||
};
|
|
||||||
|
|
||||||
int main(char argc, char* argv[]) {
|
|
||||||
tp::set_working_dir();
|
|
||||||
|
|
||||||
tp::ModuleManifest* ModuleDependencies[] = { &tp::gModuleGlw, NULL };
|
|
||||||
tp::ModuleManifest TestModule("Test", NULL, NULL, ModuleDependencies);
|
|
||||||
if (!TestModule.initialize()) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
MemLeaksGUI gui(argc > 1 ? argv[1] : "rsc/debug.memleaks");
|
|
||||||
gui.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
TestModule.deinitialize();
|
|
||||||
}
|
|
||||||
|
|
@ -1,403 +0,0 @@
|
||||||
|
|
||||||
#include "memleaks.h"
|
|
||||||
|
|
||||||
using namespace tp;
|
|
||||||
|
|
||||||
void MemLeaksData::Frame::dfs(Frame& frame, void (*exec)(Frame& frame, void* custom), void* custom) {
|
|
||||||
if (frame.flag == Frame::Flags::INUSE) { return; }
|
|
||||||
frame.flag = Frame::Flags::INUSE;
|
|
||||||
exec(frame, custom);
|
|
||||||
for (auto call_info : frame.callers) {
|
|
||||||
dfs(*call_info->val.caller, exec, custom);
|
|
||||||
}
|
|
||||||
frame.flag = Frame::Flags::NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
MemLeaksData::MemLeaksData(tp::string afilename) {
|
|
||||||
filename = afilename;
|
|
||||||
load_leaks();
|
|
||||||
|
|
||||||
rootframe.name = "root";
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemLeaksData::increase_caller_count(Frame& frame, FrameId caller_id) {
|
|
||||||
auto idx = frame.callers.presents(caller_id);
|
|
||||||
if (idx) {
|
|
||||||
frame.callers.getSlotVal(idx).count++;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Frame* caller_frame = &frames.get(caller_id);
|
|
||||||
frame.callers.put(caller_id, { caller_frame, 1 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemLeaksData::calc_max_level(Frame& frame, tp::alni& max_level) {
|
|
||||||
frame.flag = Frame::Flags::INUSE;
|
|
||||||
|
|
||||||
for (auto call_info : frame.callers) {
|
|
||||||
auto caller = call_info->val.caller;
|
|
||||||
if (caller->flag != Frame::Flags::INUSE) {
|
|
||||||
if (caller->depth_level < frame.depth_level + 1) {
|
|
||||||
caller->depth_level = frame.depth_level + 1;
|
|
||||||
max_level = MAX(caller->depth_level, max_level);
|
|
||||||
calc_max_level(*call_info->val.caller, max_level);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
frame.flag = Frame::Flags::NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemLeaksData::count_level_users(Frame& frame, tp::alni& max_level, tp::Array<LevelInfo>& levels) {
|
|
||||||
if (frame.flag2 == 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
frame.flag2 = 1;
|
|
||||||
levels[frame.depth_level].count_users += 1;
|
|
||||||
for (auto call_info : frame.callers) {
|
|
||||||
count_level_users(*call_info->val.caller, max_level, levels);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemLeaksData::apply_position(Frame& frame, tp::Array<LevelInfo>& levels) {
|
|
||||||
if (frame.flag2 == 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
frame.flag2 = 1;
|
|
||||||
|
|
||||||
|
|
||||||
auto x = -(levels[frame.depth_level].count_users - 1) * sapacing.x / 2 + sapacing.x * levels[frame.depth_level].used_idx;
|
|
||||||
frame.tree_view_pos.assign(x, frame.depth_level * sapacing.y);
|
|
||||||
levels[frame.depth_level].used_idx++;
|
|
||||||
|
|
||||||
for (auto call_info : frame.callers) {
|
|
||||||
apply_position(*call_info->val.caller, levels);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void MemLeaksData::construct_tree() {
|
|
||||||
using namespace tp;
|
|
||||||
|
|
||||||
// construct tree
|
|
||||||
for (auto leak : leaks) {
|
|
||||||
increase_caller_count(rootframe, leak.data()[0]);
|
|
||||||
for (auto i : Range<alni>(leak->length() - 1)) {
|
|
||||||
increase_caller_count(frames.get(leak.data()[i]), leak.data()[i + 1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tp::alni max_depth_level = 0;
|
|
||||||
calc_max_level(rootframe, max_depth_level);
|
|
||||||
|
|
||||||
levels.reserve(max_depth_level + 1);
|
|
||||||
|
|
||||||
count_level_users(rootframe, max_depth_level, levels);
|
|
||||||
Frame::dfs(rootframe, [](Frame& frame, void*) { frame.flag2 = 0; });
|
|
||||||
|
|
||||||
apply_position(rootframe, levels);
|
|
||||||
Frame::dfs(rootframe, [](Frame& frame, void*) { frame.flag2 = 0; });
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemLeaksData::make_connections() {
|
|
||||||
tp::halni len = 0;
|
|
||||||
for (auto& frame : frames) {
|
|
||||||
len += frame.iter->val.callers.size();
|
|
||||||
}
|
|
||||||
mConnections.reserve(len);
|
|
||||||
tp::halni idx = 0;
|
|
||||||
for (auto& frame : frames) {
|
|
||||||
for (auto& caller : frame.iter->val.callers) {
|
|
||||||
caller.iter->val.caller;
|
|
||||||
mConnections[idx] = { caller.iter->val.caller, &frame.iter->val, caller.iter->val.count };
|
|
||||||
idx++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemLeaksData::load_leaks() {
|
|
||||||
using namespace tp;
|
|
||||||
|
|
||||||
File log(filename.cstr(), osfile_openflags::LOAD);
|
|
||||||
|
|
||||||
if (!log.opened) {
|
|
||||||
status = LoadStatus::INVALID_FILE_PATH;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Preload();
|
|
||||||
|
|
||||||
const char logo[] = "memleaks\0";
|
|
||||||
const char logo_len = 10;
|
|
||||||
char logo_loaded[logo_len];
|
|
||||||
|
|
||||||
log.read_bytes(logo_loaded, logo_len);
|
|
||||||
|
|
||||||
if (!tp::memequal(logo_loaded, logo, logo_len)) {
|
|
||||||
status = LoadStatus::INVALID_FILE_FORMAT;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
alni leaks_len;
|
|
||||||
log.read<alni>(&leaks_len);
|
|
||||||
|
|
||||||
leaks.reserve(leaks_len);
|
|
||||||
|
|
||||||
Frame frame;
|
|
||||||
|
|
||||||
for (alni idx = 0; idx < leaks_len; idx++) {
|
|
||||||
|
|
||||||
tp::alni frames_len;
|
|
||||||
log.read<tp::alni>(&frames_len);
|
|
||||||
|
|
||||||
leaks[idx].reserve(frames_len);
|
|
||||||
|
|
||||||
for (alni frame_idx = 0; frame_idx < frames_len; frame_idx++) {
|
|
||||||
|
|
||||||
FrameId id;
|
|
||||||
|
|
||||||
|
|
||||||
log.read<tp::ualni>(&id);
|
|
||||||
leaks[idx][frame_idx] = id;
|
|
||||||
|
|
||||||
//if (frames.presents(id)) {
|
|
||||||
//continue;
|
|
||||||
//}
|
|
||||||
|
|
||||||
frame.name.load(&log);
|
|
||||||
frame.file.load(&log);
|
|
||||||
log.read<tp::ualni>(&frame.line);
|
|
||||||
|
|
||||||
frame.id = id;
|
|
||||||
|
|
||||||
frames.put(id, frame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
construct_tree();
|
|
||||||
make_connections();
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (...) {
|
|
||||||
status = LoadStatus::INTERNAL_ERROR;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
status = LoadStatus::DONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ------------------------- Tree View Drawer -------------------------------------- //
|
|
||||||
|
|
||||||
void MemLeaksTreeView::setTarget(MemLeaksData* aLeaks) {
|
|
||||||
leaks = aLeaks;
|
|
||||||
selected_node = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemLeaksTreeView::proc() {
|
|
||||||
if (!leaks) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle selection
|
|
||||||
if (mouse_down) {
|
|
||||||
selected_node = NULL;
|
|
||||||
|
|
||||||
void (*find_selected)(Frame & frame, void* vec) = [](Frame& frame, void* self_ptr) {
|
|
||||||
auto self = (MemLeaksTreeView*)self_ptr;
|
|
||||||
|
|
||||||
auto node_rec = self->nodeBoundsScaled(frame);
|
|
||||||
//auto vieport = self->rect;
|
|
||||||
|
|
||||||
if (node_rec.inside(self->vieport_crs)) {
|
|
||||||
self->selected_node = &frame;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Frame::dfs(leaks->rootframe, find_selected, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle mouse drag
|
|
||||||
if (vieport_crs_delta != 0.f && rect.inside(vieport_crs) && mouse_hold) {
|
|
||||||
if (selected_node) {
|
|
||||||
selected_node->tree_view_pos -= vieport_crs_delta / (tp::halnf)scaleval;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
tree_view_pos -= vieport_crs_delta / (tp::halnf)scaleval;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// calc scale fac
|
|
||||||
{
|
|
||||||
// zoom_factor :
|
|
||||||
// 0 - 3 nodes visiable
|
|
||||||
// 1 - all nodes visiable
|
|
||||||
|
|
||||||
auto max_tree_size = MAX(tree_size.x, tree_size.y);
|
|
||||||
auto max_node_size = MAX(node_size.x, node_size.y);
|
|
||||||
auto min_view_size = MIN(rect.z, rect.w) - 100;
|
|
||||||
|
|
||||||
auto max = min_view_size / max_tree_size;
|
|
||||||
auto min = min_view_size / max_node_size;
|
|
||||||
|
|
||||||
scaleval = (max - min) * zoom_factor + min;
|
|
||||||
}
|
|
||||||
|
|
||||||
// calc tree size
|
|
||||||
void (*execf)(Frame & frame, void* vec) = [](Frame& frame, void* vecp) {
|
|
||||||
auto tree_min_max_pos = (tp::rectf*)vecp;
|
|
||||||
// if smaller than min
|
|
||||||
if (tree_min_max_pos->v1.x > frame.tree_view_pos.x) {
|
|
||||||
tree_min_max_pos->v1.x = frame.tree_view_pos.x;
|
|
||||||
}
|
|
||||||
if (tree_min_max_pos->v1.y > frame.tree_view_pos.y) {
|
|
||||||
tree_min_max_pos->v1.y = frame.tree_view_pos.y;
|
|
||||||
}
|
|
||||||
// if bigger than max
|
|
||||||
if (tree_min_max_pos->v2.x < frame.tree_view_pos.x) {
|
|
||||||
tree_min_max_pos->v2.x = frame.tree_view_pos.x;
|
|
||||||
}
|
|
||||||
if (tree_min_max_pos->v2.y < frame.tree_view_pos.y) {
|
|
||||||
tree_min_max_pos->v2.y = frame.tree_view_pos.y;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
tree_size = 0;
|
|
||||||
tp::rectf tree_min_max_pos = { FLT_MAX, FLT_MIN };
|
|
||||||
Frame::dfs(leaks->rootframe, execf, &tree_min_max_pos);
|
|
||||||
tree_size = tree_min_max_pos.v2 - tree_min_max_pos.v1;
|
|
||||||
tree_size += node_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemLeaksTreeView::draw(tp::glw::Canvas* drawer) {
|
|
||||||
|
|
||||||
drawer->setCol1(col.bg);
|
|
||||||
drawer->rect(rect, 2.f);
|
|
||||||
|
|
||||||
drawer->setCol1(col.text);
|
|
||||||
if (!leaks) {
|
|
||||||
drawer->text("Not Loaded", rect, 5);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (leaks->status) {
|
|
||||||
case MemLeaksData::LoadStatus::INVALID_FILE_PATH:
|
|
||||||
drawer->text("INVALID_FILE_PATH", rect, 5);
|
|
||||||
return;
|
|
||||||
case MemLeaksData::LoadStatus::INVALID_FILE_FORMAT:
|
|
||||||
drawer->text("INVALID_FILE_FORMAT", rect, 5);
|
|
||||||
return;
|
|
||||||
case MemLeaksData::LoadStatus::INTERNAL_ERROR:
|
|
||||||
drawer->text("INTERNAL_ERROR", rect, 5);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto& frame : leaks->frames) {
|
|
||||||
drawNode(frame.iter->val, drawer);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto& connection : leaks->mConnections) {
|
|
||||||
drawConnection(*connection.data().caller, *connection.data().target, connection.data().count, drawer);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selected_node) {
|
|
||||||
auto flag = tp::glw::Canvas::Align(tp::glw::Canvas::LEFT | tp::glw::Canvas::TOP);
|
|
||||||
tp::rectf info_rec = rect;
|
|
||||||
info_rec.pos.x += 5;
|
|
||||||
info_rec.pos.y += 25;
|
|
||||||
drawer->text(selected_node->name, info_rec, 4, flag);
|
|
||||||
info_rec.pos.y += 10;
|
|
||||||
drawer->text(selected_node->file, info_rec, 4, flag);
|
|
||||||
info_rec.pos.y += 10;
|
|
||||||
drawer->text(tp::halni(selected_node->line), info_rec, 4, flag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemLeaksTreeView::drawNode(Frame& node, tp::glw::Canvas* drawer) {
|
|
||||||
auto rec = nodeBoundsScaled(node);
|
|
||||||
|
|
||||||
// outside
|
|
||||||
if (!rec.overlap(rect)) {
|
|
||||||
node.flag = Frame::Flags::NONE;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
rec.clamp(rect);
|
|
||||||
|
|
||||||
drawer->setCol1((&node == selected_node) ? col.node_active : col.node);
|
|
||||||
drawer->setCol2(col.node_outline);
|
|
||||||
drawer->rect(rec, node_rounding, outline_size);
|
|
||||||
|
|
||||||
drawer->setCol1(col.text);
|
|
||||||
auto prev = drawer->mClamping;
|
|
||||||
rec.pos.x += 2;
|
|
||||||
rec.size.x -= 4;
|
|
||||||
drawer->setClamping(rec);
|
|
||||||
drawer->text(node.name, rec, text_size, tp::glw::Canvas::LEFT_MIDDLE);
|
|
||||||
drawer->setClamping(prev);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemLeaksTreeView::drawConnection(Frame& from, Frame& to, tp::halni call_count, tp::glw::Canvas* drawer) {
|
|
||||||
|
|
||||||
node_size *= 1.1;
|
|
||||||
auto rec1 = nodeBoundsScaled(from);
|
|
||||||
auto rec2 = nodeBoundsScaled(to);
|
|
||||||
node_size /= 1.1;
|
|
||||||
|
|
||||||
auto c1 = scalePoint(from.tree_view_pos + tree_view_pos);
|
|
||||||
auto c2 = scalePoint(to.tree_view_pos + tree_view_pos);
|
|
||||||
|
|
||||||
rec1.clamp_outside(c1, c2);
|
|
||||||
rec2.clamp_outside(c1, c2);
|
|
||||||
|
|
||||||
auto viewport = rect;
|
|
||||||
|
|
||||||
if (!viewport.clamp_inside(c1, c2)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto dir = (c2 - c1).unitv();
|
|
||||||
auto side = dir.normal() * arrow_size * scaleval;
|
|
||||||
|
|
||||||
auto ab = c2 - dir * arrow_size * scaleval;
|
|
||||||
auto ae = c2;
|
|
||||||
auto al = ab + side;
|
|
||||||
auto ar = ab - side;
|
|
||||||
|
|
||||||
if (&from == selected_node || &to == selected_node) {
|
|
||||||
//col = ImColor(ImGui::GetStyle().Colors[ImGuiCol_ButtonActive]);
|
|
||||||
}
|
|
||||||
|
|
||||||
drawer->setCol1(col.arrow);
|
|
||||||
|
|
||||||
drawer->trig({ ae.x, ae.y }, { al.x, al.y }, { ar.x, ar.y });
|
|
||||||
drawer->line({ c1.x, c1.y }, { c2.x, c2.y }, line_thik);
|
|
||||||
|
|
||||||
if (&from == selected_node || &to == selected_node) {
|
|
||||||
auto text_pos = c1 + (dir * (tp::halnf)(c1 - c2).length() * 0.8);
|
|
||||||
tp::string count = call_count;
|
|
||||||
drawer->setCol1(col.text);
|
|
||||||
drawer->text(count.cstr(), { text_pos.x, text_pos.y, 0, 0 }, text_size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tp::vec2f MemLeaksTreeView::scalePoint(const tp::vec2f in) {
|
|
||||||
return in * (tp::halnf)scaleval + rect.pos + rect.size / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
tp::rectf MemLeaksTreeView::nodeBounds(Frame& node) {
|
|
||||||
tp::rectf out;
|
|
||||||
out.pos = node.tree_view_pos + tree_view_pos;
|
|
||||||
out.pos -= node_size / 2;
|
|
||||||
out.size = node_size;
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
tp::rectf MemLeaksTreeView::nodeBoundsScaled(Frame& node) {
|
|
||||||
auto out = nodeBounds(node);
|
|
||||||
auto p1 = scalePoint(out.pos);
|
|
||||||
auto p3 = scalePoint(out.p3());
|
|
||||||
out.pos = p1;
|
|
||||||
out.size = p3 - p1;
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
@ -1,137 +0,0 @@
|
||||||
|
|
||||||
#include "map.h"
|
|
||||||
#include "array.h"
|
|
||||||
#include "stringt.h"
|
|
||||||
#include "rect.h"
|
|
||||||
#include "color.h"
|
|
||||||
#include "canvas.h"
|
|
||||||
|
|
||||||
namespace tp {
|
|
||||||
|
|
||||||
struct MemLeaksData {
|
|
||||||
|
|
||||||
typedef tp::ualni FrameId;
|
|
||||||
typedef tp::Array<FrameId> MemLeak;
|
|
||||||
|
|
||||||
struct Frame;
|
|
||||||
|
|
||||||
struct CallerInfo {
|
|
||||||
Frame* caller = NULL;
|
|
||||||
tp::ualni count = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Frame {
|
|
||||||
tp::string name;
|
|
||||||
tp::string file;
|
|
||||||
tp::ualni line = 0;
|
|
||||||
tp::ualni id = 0;
|
|
||||||
|
|
||||||
tp::HashMap<CallerInfo, FrameId> callers;
|
|
||||||
|
|
||||||
tp::vec2f tree_view_pos = 0;
|
|
||||||
bool collapced = true;
|
|
||||||
|
|
||||||
enum class Flags {
|
|
||||||
NONE,
|
|
||||||
INUSE,
|
|
||||||
} flag = Flags::NONE;
|
|
||||||
|
|
||||||
tp::alni flag2 = 0;
|
|
||||||
tp::alni depth_level = 0;
|
|
||||||
|
|
||||||
static void dfs(Frame& frame, void (*exec)(Frame& frame, void* custom), void* custom = 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
Frame rootframe;
|
|
||||||
tp::HashMap<Frame, FrameId> frames;
|
|
||||||
tp::Array<MemLeak> leaks;
|
|
||||||
|
|
||||||
struct Connection {
|
|
||||||
Frame* caller = NULL;
|
|
||||||
Frame* target = NULL;
|
|
||||||
tp::ualni count = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
tp::Array<Connection> mConnections;
|
|
||||||
|
|
||||||
tp::string filename;
|
|
||||||
tp::vec2f sapacing = { 60.f, 15.f };
|
|
||||||
|
|
||||||
enum class LoadStatus {
|
|
||||||
DONE,
|
|
||||||
INVALID_FILE_PATH,
|
|
||||||
INVALID_FILE_FORMAT,
|
|
||||||
INTERNAL_ERROR,
|
|
||||||
} status;
|
|
||||||
|
|
||||||
MemLeaksData(tp::string afilename);
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
struct LevelInfo {
|
|
||||||
tp::alni count_users;
|
|
||||||
tp::alni used_idx;
|
|
||||||
};
|
|
||||||
|
|
||||||
tp::Array<LevelInfo> levels;
|
|
||||||
|
|
||||||
void increase_caller_count(Frame& frame, FrameId caller_id);
|
|
||||||
void calc_max_level(Frame& frame, tp::alni& max_level);
|
|
||||||
void count_level_users(Frame& frame, tp::alni& max_level, tp::Array<LevelInfo>& levels);
|
|
||||||
void apply_position(Frame& frame, tp::Array<LevelInfo>& levels);
|
|
||||||
void construct_tree();
|
|
||||||
void load_leaks();
|
|
||||||
void make_connections();
|
|
||||||
};
|
|
||||||
|
|
||||||
struct MemLeaksTreeView {
|
|
||||||
|
|
||||||
// inputs
|
|
||||||
tp::halnf zoom_factor = 1.f;
|
|
||||||
tp::rectf rect = 0.f;
|
|
||||||
tp::vec2f vieport_crs = 0.f;
|
|
||||||
tp::vec2f vieport_crs_delta = 0.f;
|
|
||||||
bool mouse_down = false;
|
|
||||||
bool mouse_hold = false;
|
|
||||||
bool mouse_up = false;
|
|
||||||
|
|
||||||
// appearence
|
|
||||||
struct Colors {
|
|
||||||
tp::rgba node = { 0.1f, 0.1f, 0.1f, 1.f };
|
|
||||||
tp::rgba node_outline = { 0.5f, 0.5f, 0.5f, 1.f };
|
|
||||||
tp::rgba node_active = { 0.2f, 0.2f, 0.2f, 1.f };
|
|
||||||
tp::rgba bg = { 0.15f, 0.15f, 0.15f, 1.f };
|
|
||||||
tp::rgba text = { 0.9f, 0.9f, 0.9f, 1.f };
|
|
||||||
tp::rgba arrow = { 0.5f, 0.5f, 0.5f, 1.f };
|
|
||||||
} col;
|
|
||||||
|
|
||||||
tp::halnf text_size = 3;
|
|
||||||
tp::halnf arrow_size = 2;
|
|
||||||
tp::halnf outline_size = 0.5f;
|
|
||||||
tp::halnf line_thik = 0.2f;
|
|
||||||
tp::halnf node_rounding = 1;
|
|
||||||
|
|
||||||
void setTarget(MemLeaksData* aLeaks);
|
|
||||||
|
|
||||||
void proc();
|
|
||||||
void draw(tp::glw::Canvas* drawer);
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
typedef MemLeaksData::Frame Frame;
|
|
||||||
|
|
||||||
MemLeaksData* leaks = NULL;
|
|
||||||
Frame* selected_node = NULL;
|
|
||||||
bool inside_node = false;
|
|
||||||
tp::alnf scaleval = 1.0;
|
|
||||||
tp::vec2f tree_size = 0.f;
|
|
||||||
tp::vec2f tree_view_pos = { 0.f, -50.f };
|
|
||||||
tp::vec2f node_size = { 50, 10 };
|
|
||||||
|
|
||||||
void drawNode(Frame& node, tp::glw::Canvas* drawer);
|
|
||||||
void drawConnection(Frame& from, Frame& to, tp::halni call_count, tp::glw::Canvas* drawer);
|
|
||||||
tp::vec2f scalePoint(const tp::vec2f in);
|
|
||||||
tp::rectf nodeBounds(Frame& node);
|
|
||||||
tp::rectf nodeBoundsScaled(Frame& node);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
|
|
||||||
|
|
@ -1,401 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
#include "GuiWindow.h"
|
|
||||||
|
|
||||||
#include "imgui.h"
|
|
||||||
#include "implot.h"
|
|
||||||
#include "nanovg.h"
|
|
||||||
|
|
||||||
#include "strings.h"
|
|
||||||
#include "filesystem.h"
|
|
||||||
#include "array.h"
|
|
||||||
#include "list.h"
|
|
||||||
#include "map.h"
|
|
||||||
|
|
||||||
#include "intersections.h"
|
|
||||||
|
|
||||||
#include "pickalloc_analizer.h"
|
|
||||||
#include "pickalloc_cfg.h"
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
class Memusage : public tp::GuiWindow {
|
|
||||||
|
|
||||||
tp::string filename;
|
|
||||||
|
|
||||||
static const char logo_len = 16;
|
|
||||||
const char logo[logo_len] = "memusage\0\0\0\0\0\0\0";
|
|
||||||
char logo_loaded[logo_len];
|
|
||||||
|
|
||||||
PickAllocConfig cfg;
|
|
||||||
|
|
||||||
enum class LoadStatus {
|
|
||||||
NONE,
|
|
||||||
DONE,
|
|
||||||
INVALID_FILE_PATH,
|
|
||||||
INVALID_FILE_FORMAT,
|
|
||||||
INTERNAL_ERROR,
|
|
||||||
} status;
|
|
||||||
|
|
||||||
typedef tp::PickAllocDataAnalizer::Event Event;
|
|
||||||
typedef tp::alni SizeKey;
|
|
||||||
|
|
||||||
struct Timeline {
|
|
||||||
tp::Array<tp::alnf> size;
|
|
||||||
tp::Array<tp::alnf> time;
|
|
||||||
tp::ualni peak = 0;
|
|
||||||
tp::ualni avereging = 0;
|
|
||||||
|
|
||||||
void recordEvents(tp::Array<Event>& events) {
|
|
||||||
tp::ualni total_mem_usage = 0;
|
|
||||||
|
|
||||||
size.extend(events.length());
|
|
||||||
time.extend(events.length());
|
|
||||||
|
|
||||||
for (auto ev : events) {
|
|
||||||
|
|
||||||
if (ev->dealloc) {
|
|
||||||
total_mem_usage -= ev->size;
|
|
||||||
} else {
|
|
||||||
total_mem_usage += ev->size;
|
|
||||||
}
|
|
||||||
|
|
||||||
time[ev.idx()] = (tp::alnf) ev->time;
|
|
||||||
size[ev.idx()] = (tp::alnf) total_mem_usage;
|
|
||||||
|
|
||||||
if (peak < total_mem_usage) {
|
|
||||||
peak = total_mem_usage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
avereging = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void to_seconds() {
|
|
||||||
for (auto tm : time) {
|
|
||||||
tm.data() /= 1000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Contains events of fixed size
|
|
||||||
struct Group {
|
|
||||||
tp::ualni group_size = 0;
|
|
||||||
tp::Array<Event> events;
|
|
||||||
|
|
||||||
tp::ualni flag = 0;
|
|
||||||
|
|
||||||
Timeline timeline;
|
|
||||||
|
|
||||||
Group(tp::ualni grp_size) : group_size(grp_size) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
tp::HashMap<Group*, SizeKey> groups;
|
|
||||||
tp::Array<Group*> groups_sorted;
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
Memusage(const char* filename) : GuiWindow() {
|
|
||||||
|
|
||||||
this->mHeader.mAppTitle = "Mamusage Analizer";
|
|
||||||
this->mHeader.mPadding = 10;
|
|
||||||
tp::string file_path = __FILE__;
|
|
||||||
tp::make_dir_str(file_path.get_writable());
|
|
||||||
tp::make_dir_str(file_path.get_writable());
|
|
||||||
this->mRscDirectory = file_path + "/";
|
|
||||||
this->mGuiRootIsWindow = false;
|
|
||||||
LoadFonts();
|
|
||||||
|
|
||||||
this->filename = filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
~Memusage() {
|
|
||||||
for (auto tl : groups) {
|
|
||||||
delete tl->val;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cfg.mAllocationSizes) delete[] cfg.mAllocationSizes;
|
|
||||||
if (cfg.mChunkLengths) delete[] cfg.mChunkLengths;
|
|
||||||
}
|
|
||||||
|
|
||||||
void run() {
|
|
||||||
|
|
||||||
// display status
|
|
||||||
for (auto i : tp::Range(2)) {
|
|
||||||
procInputs();
|
|
||||||
beginDraw();
|
|
||||||
if (mNativeWindow.mEvents.mRedraw) {
|
|
||||||
DrawCallback();
|
|
||||||
}
|
|
||||||
endDraw();
|
|
||||||
mNativeWindow.mEvents.mRedraw = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (!mNativeWindow.mEvents.mTerminate) {
|
|
||||||
tp::Timer timer(5);
|
|
||||||
procInputs();
|
|
||||||
beginDraw();
|
|
||||||
if (mNativeWindow.mEvents.mRedraw) {
|
|
||||||
DrawCallback();
|
|
||||||
}
|
|
||||||
endDraw();
|
|
||||||
timer.wait();
|
|
||||||
|
|
||||||
if (status == LoadStatus::NONE) {
|
|
||||||
openFile();
|
|
||||||
mNativeWindow.mEvents.mRedraw = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
tp::ualni fileContentSize(tp::File& file) {
|
|
||||||
return file.size() - fileContentStartIdx();
|
|
||||||
}
|
|
||||||
|
|
||||||
tp::ualni fileContentStartIdx() {
|
|
||||||
return logo_len;
|
|
||||||
}
|
|
||||||
|
|
||||||
void readFile(tp::File& file) {
|
|
||||||
using namespace tp;
|
|
||||||
|
|
||||||
// read cfg
|
|
||||||
file.read(&cfg.mCapacity);
|
|
||||||
cfg.mAllocationSizes = new tp::int4[cfg.mCapacity];
|
|
||||||
cfg.mChunkLengths = new tp::int4[cfg.mCapacity];
|
|
||||||
|
|
||||||
file.read_bytes((tp::int1*)cfg.mAllocationSizes, sizeof(tp::int4) * cfg.mCapacity);
|
|
||||||
file.read_bytes((tp::int1*)cfg.mChunkLengths, sizeof(tp::int4) * cfg.mCapacity);
|
|
||||||
|
|
||||||
// read evens
|
|
||||||
tp::ualni events_length;
|
|
||||||
file.read(&events_length);
|
|
||||||
|
|
||||||
// one pass to collect data about groups in 'flag'
|
|
||||||
auto events_adress = file.adress;
|
|
||||||
for (tp::ualni i = 0; i < events_length; i++) {
|
|
||||||
Event evnt;
|
|
||||||
file.read(&evnt);
|
|
||||||
auto idx = groups.presents(evnt.size);
|
|
||||||
Group* group = NULL;
|
|
||||||
if (idx) {
|
|
||||||
group = groups.getSlotVal(idx);
|
|
||||||
} else {
|
|
||||||
group = new Group(evnt.size);
|
|
||||||
groups.put(evnt.size, group);
|
|
||||||
}
|
|
||||||
group->flag++;
|
|
||||||
}
|
|
||||||
for (auto grp : groups) {
|
|
||||||
grp->val->events.reserve(grp->val->flag);
|
|
||||||
grp->val->flag = 0;
|
|
||||||
}
|
|
||||||
file.adress = events_adress;
|
|
||||||
|
|
||||||
// read actual events
|
|
||||||
for (tp::ualni idx = 0; idx < events_length; idx++) {
|
|
||||||
Event evnt;
|
|
||||||
file.read(&evnt);
|
|
||||||
auto grp = groups.get(evnt.size);
|
|
||||||
grp->events[grp->flag] = evnt;
|
|
||||||
grp->flag++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// initialize additional structure for sorting
|
|
||||||
groups_sorted.reserve(groups.size());
|
|
||||||
for (auto grp : groups) {
|
|
||||||
groups_sorted[grp.entry_idx] = grp->val;
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate timelines
|
|
||||||
for (auto grp : groups_sorted) {
|
|
||||||
grp.data()->timeline.recordEvents(grp.data()->events);
|
|
||||||
grp.data()->timeline.to_seconds();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void openFile() {
|
|
||||||
using namespace tp;
|
|
||||||
File log(filename.cstr(), osfile_openflags::LOAD);
|
|
||||||
|
|
||||||
if (!log.opened) {
|
|
||||||
status = LoadStatus::INVALID_FILE_PATH;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.read_bytes(logo_loaded, logo_len);
|
|
||||||
if (!tp::memequal(logo_loaded, logo, logo_len)) {
|
|
||||||
status = LoadStatus::INVALID_FILE_FORMAT;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Preload();
|
|
||||||
|
|
||||||
try {
|
|
||||||
readFile(log);
|
|
||||||
status = LoadStatus::DONE;
|
|
||||||
} catch (...) {
|
|
||||||
status = LoadStatus::INTERNAL_ERROR;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void HeaderDrawCallback() override {
|
|
||||||
using namespace ImGui;
|
|
||||||
Button("Analize");
|
|
||||||
Button("Save To Header File");
|
|
||||||
}
|
|
||||||
|
|
||||||
void DrawCallback() {
|
|
||||||
|
|
||||||
auto pos = this->mNativeWindow.mAppearence.mSize / 2;
|
|
||||||
if (status != LoadStatus::DONE) {
|
|
||||||
|
|
||||||
nvgFillColor(mNvg, nvgRGB(250, 250, 250));
|
|
||||||
nvgFontSize(mNvg, 24);
|
|
||||||
nvgTextAlign(mNvg, NVG_ALIGN_MIDDLE | NVG_ALIGN_CENTER);
|
|
||||||
|
|
||||||
switch (status) {
|
|
||||||
case LoadStatus::NONE:
|
|
||||||
nvgText(mNvg, pos.x, pos.y, "Loading...", 0);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LoadStatus::INVALID_FILE_PATH:
|
|
||||||
nvgText(mNvg, pos.x, pos.y, "Invalid File Path", 0);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LoadStatus::INVALID_FILE_FORMAT:
|
|
||||||
nvgText(mNvg, pos.x, pos.y, "Invalid File Format", 0);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LoadStatus::INTERNAL_ERROR:
|
|
||||||
nvgText(mNvg, pos.x, pos.y, "Internal Error", 0);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
drawEditor();
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class GroupSorting {
|
|
||||||
LARGER_SIZE,
|
|
||||||
SMALLER_SIZE,
|
|
||||||
LARGER_TOTAL_SIZE,
|
|
||||||
SMALLER_TOTAL_SIZE,
|
|
||||||
} sorting = GroupSorting::LARGER_SIZE;
|
|
||||||
|
|
||||||
void sortGroups(GroupSorting type) {
|
|
||||||
if (type == sorting) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case Memusage::GroupSorting::LARGER_SIZE:
|
|
||||||
groups_sorted.sort([](Group* const& i1, Group* const& i2) {
|
|
||||||
return i1->group_size > i2->group_size;
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case Memusage::GroupSorting::SMALLER_SIZE:
|
|
||||||
groups_sorted.sort([](Group* const& i1, Group* const& i2) {
|
|
||||||
return i1->group_size < i2->group_size;
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case Memusage::GroupSorting::LARGER_TOTAL_SIZE:
|
|
||||||
break;
|
|
||||||
case Memusage::GroupSorting::SMALLER_TOTAL_SIZE:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
sorting = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
Group* mGroupActive = NULL;
|
|
||||||
|
|
||||||
void groupView() {
|
|
||||||
using namespace ImGui;
|
|
||||||
int tmp;
|
|
||||||
|
|
||||||
const char* names[] = {
|
|
||||||
{"Larger Group Size"},
|
|
||||||
{"Smaller Group Size"},
|
|
||||||
//{"LARGER_TOTAL_SIZE"},
|
|
||||||
//{"SMALER_TOTAL_SIZE"},
|
|
||||||
};
|
|
||||||
|
|
||||||
tmp = (int) sorting;
|
|
||||||
Combo(" ", &tmp, names, 2);
|
|
||||||
Separator();
|
|
||||||
sortGroups((GroupSorting) tmp);
|
|
||||||
|
|
||||||
for (auto grp : groups_sorted) {
|
|
||||||
if (Selectable(tp::string((tp::alni)grp.data()->group_size).cstr())) {
|
|
||||||
mGroupActive = grp.data();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void drawTimeline(const Timeline& timeline) {
|
|
||||||
if (ImPlot::BeginPlot(" Plot ", {-1, -1}, ImPlotFlags_CanvasOnly | ImPlotFlags_AntiAliased)) {
|
|
||||||
ImPlot::PlotLine("Memory Usage", timeline.time.buff(), timeline.size.buff(), (tp::halni) timeline.time.length());
|
|
||||||
ImPlot::EndPlot();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void infoView() {
|
|
||||||
using namespace ImGui;
|
|
||||||
if (!mGroupActive) {
|
|
||||||
Text("No Group Selected");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Text("Total Events : %i", mGroupActive->events.length());
|
|
||||||
|
|
||||||
Separator();
|
|
||||||
|
|
||||||
drawTimeline(mGroupActive->timeline);
|
|
||||||
}
|
|
||||||
|
|
||||||
void drawEditor() {
|
|
||||||
using namespace ImGui;
|
|
||||||
|
|
||||||
Begin("Groups View");
|
|
||||||
groupView();
|
|
||||||
End();
|
|
||||||
|
|
||||||
Begin("Info");
|
|
||||||
infoView();
|
|
||||||
End();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
int main(char argc, char* argv[]) {
|
|
||||||
|
|
||||||
if (argc != 2) {
|
|
||||||
printf("invalid arguments");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
//ImPlot::
|
|
||||||
|
|
||||||
Memusage::InitializeTypes();
|
|
||||||
|
|
||||||
{
|
|
||||||
Memusage app(argv[1]);
|
|
||||||
|
|
||||||
ImPlot::CreateContext();
|
|
||||||
|
|
||||||
app.run();
|
|
||||||
|
|
||||||
ImPlot::DestroyContext();
|
|
||||||
|
|
||||||
printf("internal error");
|
|
||||||
}
|
|
||||||
|
|
||||||
Memusage::UnInitializeTypes();
|
|
||||||
|
|
||||||
tp::terminate();
|
|
||||||
}
|
|
||||||
|
|
@ -1,401 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
#include "GuiWindow.h"
|
|
||||||
|
|
||||||
#include "imgui.h"
|
|
||||||
#include "implot.h"
|
|
||||||
#include "nanovg.h"
|
|
||||||
|
|
||||||
#include "strings.h"
|
|
||||||
#include "filesystem.h"
|
|
||||||
#include "array.h"
|
|
||||||
#include "list.h"
|
|
||||||
#include "map.h"
|
|
||||||
|
|
||||||
#include "intersections.h"
|
|
||||||
|
|
||||||
#include "pickalloc_analizer.h"
|
|
||||||
#include "pickalloc_cfg.h"
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
class Memusage : public tp::GuiWindow {
|
|
||||||
|
|
||||||
tp::string filename;
|
|
||||||
|
|
||||||
static const char logo_len = 16;
|
|
||||||
const char logo[logo_len] = "memusage\0\0\0\0\0\0\0";
|
|
||||||
char logo_loaded[logo_len];
|
|
||||||
|
|
||||||
PickAllocConfig cfg;
|
|
||||||
|
|
||||||
enum class LoadStatus {
|
|
||||||
NONE,
|
|
||||||
DONE,
|
|
||||||
INVALID_FILE_PATH,
|
|
||||||
INVALID_FILE_FORMAT,
|
|
||||||
INTERNAL_ERROR,
|
|
||||||
} status;
|
|
||||||
|
|
||||||
typedef tp::PickAllocDataAnalizer::Event Event;
|
|
||||||
typedef tp::alni SizeKey;
|
|
||||||
|
|
||||||
struct Timeline {
|
|
||||||
tp::Array<tp::alnf> size;
|
|
||||||
tp::Array<tp::alnf> time;
|
|
||||||
tp::ualni peak = 0;
|
|
||||||
tp::ualni avereging = 0;
|
|
||||||
|
|
||||||
void recordEvents(tp::Array<Event>& events) {
|
|
||||||
tp::ualni total_mem_usage = 0;
|
|
||||||
|
|
||||||
size.extend(events.length());
|
|
||||||
time.extend(events.length());
|
|
||||||
|
|
||||||
for (auto ev : events) {
|
|
||||||
|
|
||||||
if (ev->dealloc) {
|
|
||||||
total_mem_usage -= ev->size;
|
|
||||||
} else {
|
|
||||||
total_mem_usage += ev->size;
|
|
||||||
}
|
|
||||||
|
|
||||||
time[ev.idx()] = (tp::alnf) ev->time;
|
|
||||||
size[ev.idx()] = (tp::alnf) total_mem_usage;
|
|
||||||
|
|
||||||
if (peak < total_mem_usage) {
|
|
||||||
peak = total_mem_usage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
avereging = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void to_seconds() {
|
|
||||||
for (auto tm : time) {
|
|
||||||
tm.data() /= 1000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Contains events of fixed size
|
|
||||||
struct Group {
|
|
||||||
tp::ualni group_size = 0;
|
|
||||||
tp::Array<Event> events;
|
|
||||||
|
|
||||||
tp::ualni flag = 0;
|
|
||||||
|
|
||||||
Timeline timeline;
|
|
||||||
|
|
||||||
Group(tp::ualni grp_size) : group_size(grp_size) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
tp::HashMap<Group*, SizeKey> groups;
|
|
||||||
tp::Array<Group*> groups_sorted;
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
Memusage(const char* filename) : GuiWindow() {
|
|
||||||
|
|
||||||
this->mHeader.mAppTitle = "Mamusage Analizer";
|
|
||||||
this->mHeader.mPadding = 10;
|
|
||||||
tp::string file_path = __FILE__;
|
|
||||||
tp::make_dir_str(file_path.get_writable());
|
|
||||||
tp::make_dir_str(file_path.get_writable());
|
|
||||||
this->mRscDirectory = file_path + "/";
|
|
||||||
this->mGuiRootIsWindow = false;
|
|
||||||
LoadFonts();
|
|
||||||
|
|
||||||
this->filename = filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
~Memusage() {
|
|
||||||
for (auto tl : groups) {
|
|
||||||
delete tl->val;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cfg.mAllocationSizes) delete[] cfg.mAllocationSizes;
|
|
||||||
if (cfg.mChunkLengths) delete[] cfg.mChunkLengths;
|
|
||||||
}
|
|
||||||
|
|
||||||
void run() {
|
|
||||||
|
|
||||||
// display status
|
|
||||||
for (auto i : tp::Range(2)) {
|
|
||||||
procInputs();
|
|
||||||
beginDraw();
|
|
||||||
if (mNativeWindow.mEvents.mRedraw) {
|
|
||||||
DrawCallback();
|
|
||||||
}
|
|
||||||
endDraw();
|
|
||||||
mNativeWindow.mEvents.mRedraw = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (!mNativeWindow.mEvents.mTerminate) {
|
|
||||||
tp::Timer timer(5);
|
|
||||||
procInputs();
|
|
||||||
beginDraw();
|
|
||||||
if (mNativeWindow.mEvents.mRedraw) {
|
|
||||||
DrawCallback();
|
|
||||||
}
|
|
||||||
endDraw();
|
|
||||||
timer.wait();
|
|
||||||
|
|
||||||
if (status == LoadStatus::NONE) {
|
|
||||||
openFile();
|
|
||||||
mNativeWindow.mEvents.mRedraw = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
tp::ualni fileContentSize(tp::File& file) {
|
|
||||||
return file.size() - fileContentStartIdx();
|
|
||||||
}
|
|
||||||
|
|
||||||
tp::ualni fileContentStartIdx() {
|
|
||||||
return logo_len;
|
|
||||||
}
|
|
||||||
|
|
||||||
void readFile(tp::File& file) {
|
|
||||||
using namespace tp;
|
|
||||||
|
|
||||||
// read cfg
|
|
||||||
file.read(&cfg.mCapacity);
|
|
||||||
cfg.mAllocationSizes = new tp::int4[cfg.mCapacity];
|
|
||||||
cfg.mChunkLengths = new tp::int4[cfg.mCapacity];
|
|
||||||
|
|
||||||
file.read_bytes((tp::int1*)cfg.mAllocationSizes, sizeof(tp::int4) * cfg.mCapacity);
|
|
||||||
file.read_bytes((tp::int1*)cfg.mChunkLengths, sizeof(tp::int4) * cfg.mCapacity);
|
|
||||||
|
|
||||||
// read evens
|
|
||||||
tp::ualni events_length;
|
|
||||||
file.read(&events_length);
|
|
||||||
|
|
||||||
// one pass to collect data about groups in 'flag'
|
|
||||||
auto events_adress = file.adress;
|
|
||||||
for (tp::ualni i = 0; i < events_length; i++) {
|
|
||||||
Event evnt;
|
|
||||||
file.read(&evnt);
|
|
||||||
auto idx = groups.presents(evnt.size);
|
|
||||||
Group* group = NULL;
|
|
||||||
if (idx) {
|
|
||||||
group = groups.getSlotVal(idx);
|
|
||||||
} else {
|
|
||||||
group = new Group(evnt.size);
|
|
||||||
groups.put(evnt.size, group);
|
|
||||||
}
|
|
||||||
group->flag++;
|
|
||||||
}
|
|
||||||
for (auto grp : groups) {
|
|
||||||
grp->val->events.reserve(grp->val->flag);
|
|
||||||
grp->val->flag = 0;
|
|
||||||
}
|
|
||||||
file.adress = events_adress;
|
|
||||||
|
|
||||||
// read actual events
|
|
||||||
for (tp::ualni idx = 0; idx < events_length; idx++) {
|
|
||||||
Event evnt;
|
|
||||||
file.read(&evnt);
|
|
||||||
auto grp = groups.get(evnt.size);
|
|
||||||
grp->events[grp->flag] = evnt;
|
|
||||||
grp->flag++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// initialize additional structure for sorting
|
|
||||||
groups_sorted.reserve(groups.size());
|
|
||||||
for (auto grp : groups) {
|
|
||||||
groups_sorted[grp.entry_idx] = grp->val;
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate timelines
|
|
||||||
for (auto grp : groups_sorted) {
|
|
||||||
grp.data()->timeline.recordEvents(grp.data()->events);
|
|
||||||
grp.data()->timeline.to_seconds();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void openFile() {
|
|
||||||
using namespace tp;
|
|
||||||
File log(filename.cstr(), osfile_openflags::LOAD);
|
|
||||||
|
|
||||||
if (!log.opened) {
|
|
||||||
status = LoadStatus::INVALID_FILE_PATH;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.read_bytes(logo_loaded, logo_len);
|
|
||||||
if (!tp::memequal(logo_loaded, logo, logo_len)) {
|
|
||||||
status = LoadStatus::INVALID_FILE_FORMAT;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Preload();
|
|
||||||
|
|
||||||
try {
|
|
||||||
readFile(log);
|
|
||||||
status = LoadStatus::DONE;
|
|
||||||
} catch (...) {
|
|
||||||
status = LoadStatus::INTERNAL_ERROR;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void HeaderDrawCallback() override {
|
|
||||||
using namespace ImGui;
|
|
||||||
Button("Analize");
|
|
||||||
Button("Save To Header File");
|
|
||||||
}
|
|
||||||
|
|
||||||
void DrawCallback() {
|
|
||||||
|
|
||||||
auto pos = this->mNativeWindow.mAppearence.mSize / 2;
|
|
||||||
if (status != LoadStatus::DONE) {
|
|
||||||
|
|
||||||
nvgFillColor(mNvg, nvgRGB(250, 250, 250));
|
|
||||||
nvgFontSize(mNvg, 24);
|
|
||||||
nvgTextAlign(mNvg, NVG_ALIGN_MIDDLE | NVG_ALIGN_CENTER);
|
|
||||||
|
|
||||||
switch (status) {
|
|
||||||
case LoadStatus::NONE:
|
|
||||||
nvgText(mNvg, pos.x, pos.y, "Loading...", 0);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LoadStatus::INVALID_FILE_PATH:
|
|
||||||
nvgText(mNvg, pos.x, pos.y, "Invalid File Path", 0);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LoadStatus::INVALID_FILE_FORMAT:
|
|
||||||
nvgText(mNvg, pos.x, pos.y, "Invalid File Format", 0);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LoadStatus::INTERNAL_ERROR:
|
|
||||||
nvgText(mNvg, pos.x, pos.y, "Internal Error", 0);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
drawEditor();
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class GroupSorting {
|
|
||||||
LARGER_SIZE,
|
|
||||||
SMALLER_SIZE,
|
|
||||||
LARGER_TOTAL_SIZE,
|
|
||||||
SMALLER_TOTAL_SIZE,
|
|
||||||
} sorting = GroupSorting::LARGER_SIZE;
|
|
||||||
|
|
||||||
void sortGroups(GroupSorting type) {
|
|
||||||
if (type == sorting) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case Memusage::GroupSorting::LARGER_SIZE:
|
|
||||||
groups_sorted.sort([](Group* const& i1, Group* const& i2) {
|
|
||||||
return i1->group_size > i2->group_size;
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case Memusage::GroupSorting::SMALLER_SIZE:
|
|
||||||
groups_sorted.sort([](Group* const& i1, Group* const& i2) {
|
|
||||||
return i1->group_size < i2->group_size;
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case Memusage::GroupSorting::LARGER_TOTAL_SIZE:
|
|
||||||
break;
|
|
||||||
case Memusage::GroupSorting::SMALLER_TOTAL_SIZE:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
sorting = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
Group* mGroupActive = NULL;
|
|
||||||
|
|
||||||
void groupView() {
|
|
||||||
using namespace ImGui;
|
|
||||||
int tmp;
|
|
||||||
|
|
||||||
const char* names[] = {
|
|
||||||
{"Larger Group Size"},
|
|
||||||
{"Smaller Group Size"},
|
|
||||||
//{"LARGER_TOTAL_SIZE"},
|
|
||||||
//{"SMALER_TOTAL_SIZE"},
|
|
||||||
};
|
|
||||||
|
|
||||||
tmp = (int) sorting;
|
|
||||||
Combo(" ", &tmp, names, 2);
|
|
||||||
Separator();
|
|
||||||
sortGroups((GroupSorting) tmp);
|
|
||||||
|
|
||||||
for (auto grp : groups_sorted) {
|
|
||||||
if (Selectable(tp::string((tp::alni)grp.data()->group_size).cstr())) {
|
|
||||||
mGroupActive = grp.data();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void drawTimeline(const Timeline& timeline) {
|
|
||||||
if (ImPlot::BeginPlot(" Plot ", {-1, -1}, ImPlotFlags_CanvasOnly | ImPlotFlags_AntiAliased)) {
|
|
||||||
ImPlot::PlotLine("Memory Usage", timeline.time.buff(), timeline.size.buff(), (tp::halni) timeline.time.length());
|
|
||||||
ImPlot::EndPlot();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void infoView() {
|
|
||||||
using namespace ImGui;
|
|
||||||
if (!mGroupActive) {
|
|
||||||
Text("No Group Selected");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Text("Total Events : %i", mGroupActive->events.length());
|
|
||||||
|
|
||||||
Separator();
|
|
||||||
|
|
||||||
drawTimeline(mGroupActive->timeline);
|
|
||||||
}
|
|
||||||
|
|
||||||
void drawEditor() {
|
|
||||||
using namespace ImGui;
|
|
||||||
|
|
||||||
Begin("Groups View");
|
|
||||||
groupView();
|
|
||||||
End();
|
|
||||||
|
|
||||||
Begin("Info");
|
|
||||||
infoView();
|
|
||||||
End();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
int main(char argc, char* argv[]) {
|
|
||||||
|
|
||||||
if (argc != 2) {
|
|
||||||
printf("invalid arguments");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
//ImPlot::
|
|
||||||
|
|
||||||
Memusage::InitializeTypes();
|
|
||||||
|
|
||||||
{
|
|
||||||
Memusage app(argv[1]);
|
|
||||||
|
|
||||||
ImPlot::CreateContext();
|
|
||||||
|
|
||||||
app.run();
|
|
||||||
|
|
||||||
ImPlot::DestroyContext();
|
|
||||||
|
|
||||||
printf("internal error");
|
|
||||||
}
|
|
||||||
|
|
||||||
Memusage::UnInitializeTypes();
|
|
||||||
|
|
||||||
tp::terminate();
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue