This commit is contained in:
Ilusha 2023-05-28 01:16:30 +03:00 committed by IlushaShurupov
parent d4c558a59a
commit db05d963be
74 changed files with 4473 additions and 3231 deletions

66
.clang-format Normal file
View 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
View 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
View file

@ -1,6 +1,6 @@
.idea .idea
tmp *tmp*
bin bin
build build*
lib lib
install install

20
Allocators/CMakeLists.txt Normal file
View 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)

View file

@ -30,7 +30,7 @@ If memory leaks were detected it will be loged in the output console.
![image](https://user-images.githubusercontent.com/63184036/222794298-3f238de4-c0b8-41fa-b7ec-c0c675da8f05.png) ![image](https://user-images.githubusercontent.com/63184036/222794298-3f238de4-c0b8-41fa-b7ec-c0c675da8f05.png)
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.
![image](https://user-images.githubusercontent.com/63184036/222793169-a405effe-72be-42fc-b375-bb06dce0a735.png) ![image](https://user-images.githubusercontent.com/63184036/222793169-a405effe-72be-42fc-b375-bb06dce0a735.png)

View file

@ -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); }

View file

@ -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);
} }

View file

@ -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
} }
} }

View file

@ -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
} }
} }

View file

@ -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) {
@ -29,7 +29,7 @@ void tp::PoolAlloc::Chunks::add(Chunk* aChunk) {
// check for buff overflow // check for buff overflow
if (mUsedLen == mLen) { if (mUsedLen == mLen) {
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);
mLen *= 2; mLen *= 2;
HeapAllocGlobal::deallocate(prevBuff); HeapAllocGlobal::deallocate(prevBuff);
@ -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;
}

View file

@ -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

View 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);

View file

@ -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;

View file

@ -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);

View file

@ -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
View 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
View 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)

View 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);
}

View 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;
}

View 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)); }
}

View 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";
}

View 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"); }

View 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;
};

View 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;
}
}

View 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
};

View 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

View 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
View 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
View 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)

View 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);
}
}

View 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; }
};
}

View 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
View 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
View 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(); }
};
}

View 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();
}

View 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();
}

View 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();
}

View 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();
}

View 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
View file

@ -0,0 +1 @@
Modules

24
Utils/CMakeLists.txt Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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();
}

View file

@ -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) {
}

View file

@ -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;
};

View file

@ -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);

View file

@ -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();
}

View file

@ -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();
}

View file

@ -1,3 +0,0 @@
#pragma once
void allocators_test();

View file

@ -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();
}

View file

@ -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);
}

View file

@ -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);
};

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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; }
};

View file

@ -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();
}

View file

@ -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;
}

View file

@ -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);
};
};

View file

@ -1 +0,0 @@

View file

@ -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();
}

View file

@ -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();
}