diff --git a/.clang-format b/.clang-format index 697d44e..2ede1e2 100644 --- a/.clang-format +++ b/.clang-format @@ -35,7 +35,7 @@ BraceWrapping: BreakAfterAttributes: Always BreakBeforeTernaryOperators: true BreakBeforeConceptDeclarations: Always -BreakConstructorInitializers: BeforeColon +BreakConstructorInitializers: BeforeComma BreakInheritanceList: BeforeColon ColumnLimit: 120 diff --git a/.gitignore b/.gitignore index ef5bbe8..e10868d 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,7 @@ # CMake cmake-build-*/ +builds/ # IDE files .idea \ No newline at end of file diff --git a/app/include/Core/Delegates/Delegate.h b/app/include/Core/Delegates/Delegate.h new file mode 100644 index 0000000..692e97a --- /dev/null +++ b/app/include/Core/Delegates/Delegate.h @@ -0,0 +1,175 @@ +#ifndef TGENGINE_DELEGATE_H +#define TGENGINE_DELEGATE_H + +#include "Core/Memory/StackStorage.h" +#include "Core/Types/Callable.h" +#include "DelegateAllocator.h" +#include "ErasedFunctions.h" + +#include + +namespace TGEngine::Core +{ + + +template +class Delegate; + +template +class Delegate +{ +private: + + using ErasedDelegate = _DelegateInternals::IDelegate; + + using Executer = FunctionPtr; + using Constructor = FunctionPtr; + + static constexpr size_t storageSize = 64; + static constexpr size_t storageAlign = 8; + using Storage = StackStorage; + using DefaultAllocator = _DelegateInternals::DelegateAllocator; + +public: + + Delegate() = default; + + Delegate(const Delegate& delegate) + : defaultAllocator(storage) + , executer(delegate.executer) + , constructor(delegate.constructor) + , erasedDelegate(makeCopy(delegate)) + {} + + Delegate& operator= (const Delegate& delegate) + { + if (this == &delegate) return *this; + unbind(); + + executer = delegate.executer; + constructor = delegate.constructor; + erasedDelegate = makeCopy(delegate); + + return *this; + } + + ~Delegate() { unbind(); } + +private: + + ErasedDelegate* makeCopy(const Delegate& delegate) + { + ErasedDelegate* copy = defaultAllocator.allocateCopy(delegate.storage); + return constructor(copy, delegate.erasedDelegate); + } + +public: + + [[nodiscard]] + bool isBound() const + { + return erasedDelegate != nullptr; + } + + void unbind() + { + using AllocTraits = std::allocator_traits; + + AllocTraits::destroy(defaultAllocator, erasedDelegate); + AllocTraits::deallocate(defaultAllocator, erasedDelegate, 1); + } + +public: + + [[nodiscard]] + RetVal execute(Args... args) const + { + return executer(erasedDelegate, std::forward(args)...); + } + + [[nodiscard]] + std::optional executeIfBound(Args... args) const + { + if (!isBound()) return std::nullopt; + return executer(erasedDelegate, std::forward(args)...); + } + +public: + + template + void bindStatic(FunctionPtr function, std::decay_t... payload) + { + using namespace _DelegateInternals; + using DelegateType = StaticDelegate; + bind(function, std::move(payload)...); + } + + template + void bindLambda(Lambda&& lambda, Payload... payload) + requires std::invocable + { + using namespace _DelegateInternals; + using DelegateType = LambdaDelegate; + bind(std::forward(lambda), std::move(payload)...); + } + + template + void bindMethod(NotConstMethodPtr method, T* object, + std::decay_t... payload) + { + constexpr bool isConst = false; + using namespace _DelegateInternals; + using DelegateType = MethodDelegate; + bind(method, object, std::move(payload)...); + } + + template + void bindMethod(ConstMethodPtr method, const T* object, + std::decay_t... payload) + { + constexpr bool isConst = true; + using namespace _DelegateInternals; + using DelegateType = MethodDelegate; + bind(method, object, std::move(payload)...); + } + +private: + + template<_DelegateInternals::DelegateType DelegateType, typename... ConstructionArgs> + void bind(ConstructionArgs&&... args) + { + if (isBound()) unbind(); + + erasedDelegate = makeDelegate(std::forward(args)...); + executer = _DelegateInternals::execute; + constructor = _DelegateInternals::construct; + } + + template + ErasedDelegate* makeDelegate(ConstructionArgs&&... args) + { + using Allocator = typename std::allocator_traits::template rebind_alloc; + using AllocTraits = std::allocator_traits; + + Allocator allocator(storage); + DelegateType* delegate = AllocTraits::allocate(allocator, 1); + AllocTraits::construct(allocator, delegate, std::forward(args)...); + + return delegate; + } + +private: + + Storage storage; + DefaultAllocator defaultAllocator {storage}; + + Executer executer {}; + Constructor constructor {}; + ErasedDelegate* erasedDelegate {}; +}; + + +} // namespace TGEngine::Core + + +#endif // TGENGINE_DELEGATE_H diff --git a/app/include/Core/Delegates/DelegateAllocator.h b/app/include/Core/Delegates/DelegateAllocator.h new file mode 100644 index 0000000..3e5e9ed --- /dev/null +++ b/app/include/Core/Delegates/DelegateAllocator.h @@ -0,0 +1,79 @@ +#ifndef TGENGINE_DELEGATE_ALLOCATOR_H +#define TGENGINE_DELEGATE_ALLOCATOR_H + +#include "DelegateTypes.h" + +namespace TGEngine::Core::_DelegateInternals +{ + +template +class DelegateAllocator +{ +public: + + using value_type = Delegate; + using ErasedDelegate = Delegate::Erased; + + constexpr explicit DelegateAllocator(Storage& storage) + : storage(storage) + {} + + constexpr DelegateAllocator(const DelegateAllocator& other) + : storage(other.storage) + {} + + template + constexpr explicit DelegateAllocator(const DelegateAllocator& other) + : storage(other.storage) + {} + + template + friend class DelegateAllocator; + + template + bool operator== (const DelegateAllocator& allocator) const + { + return &storage == &allocator.storage && std::is_base_of_v; + } + +public: + + [[nodiscard]] + Delegate* allocate(size_t) + { + const size_t bytes = sizeof(Delegate); + return static_cast(reserve(bytes)); + } + + [[nodiscard]] + ErasedDelegate* allocateCopy(const Storage& copyStorage) + { + const size_t bytes = copyStorage.used(); + return static_cast(reserve(bytes)); + } + + void deallocate(Delegate* delegate, size_t) + { + const size_t used = storage.used(); + hasStorageAllocation(delegate) ? storage.deallocate(delegate, used) : std::free(delegate); + } + +private: + + void* reserve(std::size_t bytes) + { + return canAllocateInStorage(bytes) ? storage.allocate(bytes) : std::aligned_alloc(alignof(Delegate), bytes); + } + + bool canAllocateInStorage(size_t bytes) const { return storage.isEmpty() && storage.canAllocate(bytes); } + + bool hasStorageAllocation(Delegate* delegate) const { return storage.contains(delegate); } + +private: + + Storage& storage; +}; + +} // namespace TGEngine::Core::_DelegateInternals + +#endif // TGENGINE_DELEGATE_ALLOCATOR_H diff --git a/app/include/Core/Delegates/DelegateTypes.h b/app/include/Core/Delegates/DelegateTypes.h new file mode 100644 index 0000000..523f774 --- /dev/null +++ b/app/include/Core/Delegates/DelegateTypes.h @@ -0,0 +1,155 @@ +#ifndef TGENGINE_DELEGATE_TYPES_H +#define TGENGINE_DELEGATE_TYPES_H + +#include +#include + +namespace TGEngine::Core::_DelegateInternals +{ + +template +class IDelegate; + +template +class IDelegate +{ +public: + + using RetVal = R; + using Signature = RetVal(Args...); + using Erased = IDelegate; + + virtual ~IDelegate() = default; +}; + +template +concept DelegateType = requires { + typename T::RetVal; + typename T::Signature; + typename T::Erased; + + static_cast>(std::declval()); +} && std::same_as>; + + +template +class StaticDelegate; + +template +class StaticDelegate: public IDelegate +{ +public: + + constexpr explicit StaticDelegate(Function function, Payload&&... payload) + : function(function) + , payload(std::forward(payload)...) + {} + + constexpr StaticDelegate(const StaticDelegate& delegate) = default; + + [[nodiscard]] + constexpr RetVal execute(Args&&... args) + { + return executeWithPayload(std::forward(args)..., std::index_sequence_for {}); + } + +private: + + template + constexpr RetVal executeWithPayload(Args&&... args, std::index_sequence) + { + return function(std::forward(args)..., std::get(payload)...); + } + +private: + + Function function; + + [[no_unique_address]] + std::tuple payload; +}; + + +template +class LambdaDelegate; + +template +class LambdaDelegate: public IDelegate +{ +public: + + constexpr explicit LambdaDelegate(Lambda&& lambda, Payload&&... payload) + : lambda(std::forward(lambda)) + , payload(std::forward(payload)...) + {} + + constexpr LambdaDelegate(const LambdaDelegate& delegate) = default; + + [[nodiscard]] + constexpr RetVal execute(Args&&... args) + { + return executeWithPayload(std::forward(args)..., std::index_sequence_for {}); + } + +private: + + template + constexpr RetVal executeWithPayload(Args&&... args, std::index_sequence) + { + return lambda(std::forward(args)..., std::get(payload)...); + } + +private: + + [[no_unique_address]] + std::remove_reference_t lambda; + + [[no_unique_address]] + std::tuple payload; +}; + + +template +class MethodDelegate; + +template +class MethodDelegate: public IDelegate +{ +public: + + using Object = std::conditional_t; + + constexpr explicit MethodDelegate(Method method, Object object, Payload&&... payload) + : method(method) + , object(object) + , payload(std::forward(payload)...) + {} + + constexpr MethodDelegate(const MethodDelegate& delegate) = default; + + [[nodiscard]] + constexpr RetVal execute(Args&&... args) + { + return executeWithPayload(std::forward(args)..., std::index_sequence_for {}); + } + +private: + + template + constexpr RetVal executeWithPayload(Args&&... args, std::index_sequence) + { + return (object->*method)(std::forward(args)..., std::get(payload)...); + } + +private: + + Method method; + Object object; + + [[no_unique_address]] + std::tuple payload; +}; + +} // namespace TGEngine::Core::_DelegateInternals + +#endif // TGENGINE_DELEGATE_TYPES_H diff --git a/app/include/Core/Delegates/ErasedFunctions.h b/app/include/Core/Delegates/ErasedFunctions.h new file mode 100644 index 0000000..6fc8e63 --- /dev/null +++ b/app/include/Core/Delegates/ErasedFunctions.h @@ -0,0 +1,28 @@ +#ifndef TGENGINE_DELEGATE_INTERNALS_H +#define TGENGINE_DELEGATE_INTERNALS_H + +#include "DelegateTypes.h" + +#include + +namespace TGEngine::Core::_DelegateInternals +{ + +template +constexpr DelegateType::RetVal execute(typename DelegateType::Erased* delegate, Args&&... args) +{ + return static_cast(delegate)->execute(std::forward(args)...); +} + +template +constexpr DelegateType::Erased* construct(typename DelegateType::Erased* at, const typename DelegateType::Erased* from) +{ + auto p = static_cast(at); + auto arg = static_cast(from); + + return std::construct_at(p, *arg); +} + +} // namespace TGEngine::Core::_DelegateInternals + +#endif // TGENGINE_DELEGATE_INTERNALS_H diff --git a/app/include/Core/Memory/StackStorage.h b/app/include/Core/Memory/StackStorage.h new file mode 100644 index 0000000..82be381 --- /dev/null +++ b/app/include/Core/Memory/StackStorage.h @@ -0,0 +1,81 @@ +#ifndef TGENGINE_STACK_STORAGE_H +#define TGENGINE_STACK_STORAGE_H + +#include +#include + +namespace TGEngine::Core +{ + +template +class StackStorage +{ +public: + + constexpr StackStorage() + : buffer() + , ptr(buffer) + {} + + StackStorage(const StackStorage&) = delete; + StackStorage& operator= (const StackStorage&) = delete; + + static constexpr size_t size = N; + static constexpr size_t align = Align; + +public: + + [[nodiscard]] + constexpr void* allocate(size_t bytes) + { + if (!canAllocate(bytes)) return nullptr; + + void* currentPtr = ptr; + ptr += alignUp(bytes); + return currentPtr; + } + + constexpr void deallocate([[maybe_unused]] const void* p, size_t bytes) + { + bytes = alignUp(bytes); + assert(p == ptr - bytes); + ptr -= bytes; + } + + [[nodiscard]] + constexpr bool canAllocate(size_t bytes) const + { + return bytes > 0 && used() + bytes <= size; + } + + [[nodiscard]] + constexpr bool contains(const void* p) const + { + return p && p >= buffer && p < buffer + N; + } + + [[nodiscard]] + constexpr size_t used() const + { + return static_cast(ptr - buffer); + } + + [[nodiscard]] + constexpr bool isEmpty() const + { + return ptr == buffer; + } + +private: + + constexpr static size_t alignUp(std::size_t n) noexcept { return (n + (align - 1)) & ~(align - 1); } + +private: + + alignas(align) std::byte buffer[size]; + std::byte* ptr; +}; + +} // namespace TGEngine::Core + +#endif // TGENGINE_STACK_STORAGE_H diff --git a/app/include/Core/Types/Callable.h b/app/include/Core/Types/Callable.h new file mode 100644 index 0000000..3899a0c --- /dev/null +++ b/app/include/Core/Types/Callable.h @@ -0,0 +1,39 @@ +#ifndef TGENGINE_CALLABLE_H +#define TGENGINE_CALLABLE_H + +namespace TGEngine::Core +{ + +namespace internal +{ + +template +struct MethodDeductionType +{ + using Type = RetVal (T::*)(Args...); +}; + +template +struct MethodDeductionType +{ + using Type = RetVal (T::*)(Args...) const; +}; + +} // namespace internal + +template +using MethodPtr = typename internal::MethodDeductionType::Type; + +template +using NotConstMethodPtr = RetVal (T::*)(Args...); + +template +using ConstMethodPtr = RetVal (T::*)(Args...) const; + +template +using FunctionPtr = RetVal (*)(Args...); + +} // namespace TGEngine::Core + + +#endif // TGENGINE_CALLABLE_H diff --git a/app/src/main.cpp b/app/src/main.cpp index 5bc5493..2c315f0 100644 --- a/app/src/main.cpp +++ b/app/src/main.cpp @@ -1,4 +1,89 @@ +#include "Core/Delegates/Delegate.h" + +#include + +struct A +{ + A() = default; + + A(const A&) { std::cout << "Copy constructor A\n"; } + + A(A&&) noexcept { std::cout << "Move constructor A\n"; } + + A& operator= (const A&) + { + std::cout << "Copy assigment A\n"; + return *this; + } + + A& operator= (A&&) noexcept + { + std::cout << "Move assigment A\n"; + return *this; + } +}; + +using namespace TGEngine::Core; +using namespace TGEngine::Core; + +int f(int) +{ + return 2; +} + +int g(int n, const A&) +{ + return n * n; +} + +struct S +{ + int foo(int, A&) { return 1; } + + int bar(int, A) const { return -1; } + + int hul(int, double) { return 0; } +}; + +template +struct BB +{ + bool b = B; +}; + +template +void showT(T&) +{ + constexpr bool isConst = std::is_const_v; + std::cout << BB {}.b; +}; + int main() { + S s; + const S cs; + + const A ca; + A a; + + Delegate delegate; + + auto l = [](int, double) { return 1; }; + delegate.bindLambda(l, 3.14); + delegate.bindLambda([](int) { return 1; }); + + delegate.bindStatic(&g, A()); + delegate.bindStatic(&f); + + delegate.bindMethod(&S::foo, &s, ca); + delegate.bindMethod(&S::hul, &s, 1.0); + delegate.bindMethod(&S::bar, &cs, a); + + Delegate delegate1 = delegate; + std::cout << delegate1.execute(1) << " \n"; + + const int r {}; + showT(r); + return 0; } \ No newline at end of file diff --git a/build/.cmake/api/v1/query/client-vscode/query.json b/build/.cmake/api/v1/query/client-vscode/query.json new file mode 100644 index 0000000..82bb964 --- /dev/null +++ b/build/.cmake/api/v1/query/client-vscode/query.json @@ -0,0 +1 @@ +{"requests":[{"kind":"cache","version":2},{"kind":"codemodel","version":2},{"kind":"toolchains","version":1},{"kind":"cmakeFiles","version":1}]} \ No newline at end of file diff --git a/build/CMakeCache.txt b/build/CMakeCache.txt new file mode 100644 index 0000000..6a6bf4a --- /dev/null +++ b/build/CMakeCache.txt @@ -0,0 +1,65 @@ +# This is the CMakeCache file. +# For build in directory: /home/ilia/programming/tg-engine/build +# It was generated by CMake: /usr/bin/cmake +# You can edit this file to change values found and used by cmake. +# If you do not want to change any of the values, simply exit the editor. +# If you do want to change a value, simply edit, save, and exit the editor. +# The syntax for the file is as follows: +# KEY:TYPE=VALUE +# KEY is the name of a variable in the cache. +# TYPE is a hint to GUIs for the type of VALUE, DO NOT EDIT TYPE!. +# VALUE is the current value for the KEY. + +######################## +# EXTERNAL cache entries +######################## + +//No help, variable specified on the command line. +CMAKE_BUILD_TYPE:STRING=Debug + +//No help, variable specified on the command line. +CMAKE_CXX_COMPILER:FILEPATH=/usr/bin/g++ + +//No help, variable specified on the command line. +CMAKE_C_COMPILER:FILEPATH=/usr/bin/gcc + +//No help, variable specified on the command line. +CMAKE_EXPORT_COMPILE_COMMANDS:BOOL=TRUE + + +######################## +# INTERNAL cache entries +######################## + +//This is the directory where this CMakeCache.txt was created +CMAKE_CACHEFILE_DIR:INTERNAL=/home/ilia/programming/tg-engine/build +//Major version of cmake used to create the current loaded cache +CMAKE_CACHE_MAJOR_VERSION:INTERNAL=3 +//Minor version of cmake used to create the current loaded cache +CMAKE_CACHE_MINOR_VERSION:INTERNAL=22 +//Patch version of cmake used to create the current loaded cache +CMAKE_CACHE_PATCH_VERSION:INTERNAL=1 +//Path to CMake executable. +CMAKE_COMMAND:INTERNAL=/usr/bin/cmake +//Path to cpack program executable. +CMAKE_CPACK_COMMAND:INTERNAL=/usr/bin/cpack +//Path to ctest program executable. +CMAKE_CTEST_COMMAND:INTERNAL=/usr/bin/ctest +//Name of external makefile project generator. +CMAKE_EXTRA_GENERATOR:INTERNAL= +//Name of generator. +CMAKE_GENERATOR:INTERNAL=Unix Makefiles +//Generator instance identifier. +CMAKE_GENERATOR_INSTANCE:INTERNAL= +//Name of generator platform. +CMAKE_GENERATOR_PLATFORM:INTERNAL= +//Name of generator toolset. +CMAKE_GENERATOR_TOOLSET:INTERNAL= +//Source directory with the top level CMakeLists.txt file for this +// project +CMAKE_HOME_DIRECTORY:INTERNAL=/home/ilia/programming/tg-engine +//number of local generators +CMAKE_NUMBER_OF_MAKEFILES:INTERNAL=1 +//Path to CMake installation. +CMAKE_ROOT:INTERNAL=/usr/share/cmake-3.22 + diff --git a/build/CMakeFiles/cmake.check_cache b/build/CMakeFiles/cmake.check_cache new file mode 100644 index 0000000..3dccd73 --- /dev/null +++ b/build/CMakeFiles/cmake.check_cache @@ -0,0 +1 @@ +# This file is generated by cmake for dependency checking of the CMakeCache.txt file diff --git a/docs/Doxyfile b/docs/Doxyfile index 5021e14..3b2f98c 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -216,9 +216,9 @@ JAVADOC_BANNER = YES QT_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a -# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# multi-line C++ special comment controlBlock (i.e. a controlBlock of //! or /// comments) as # a brief description. This used to be the default behavior. The new default is -# to treat a multi-line C++ comment block as a detailed description. Set this +# to treat a multi-line C++ comment controlBlock as a detailed description. Set this # tag to YES if you prefer the old behavior instead. # # Note that setting this tag to YES also means that rational rose comments are @@ -568,7 +568,7 @@ HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any # documentation blocks found inside the body of a function. If set to NO, these -# blocks will be appended to the function's detailed documentation block. +# blocks will be appended to the function's detailed documentation controlBlock. # The default value is: NO. HIDE_IN_BODY_DOCS = NO @@ -1245,7 +1245,7 @@ HTML_FILE_EXTENSION = .html # uses. # Note: The header is subject to change so you typically have to regenerate the # default header when upgrading to a newer version of doxygen. For a description -# of the possible markers and block names see the documentation. +# of the possible markers and controlBlock names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_HEADER = @@ -2595,7 +2595,7 @@ PLANTUML_JAR_PATH = PLANTUML_CFG_FILE = # When using plantuml, the specified paths are searched for files specified by -# the !include statement in a plantuml block. +# the !include statement in a plantuml controlBlock. PLANTUML_INCLUDE_PATH = diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 210633e..6201f65 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -17,5 +17,5 @@ gtest_discover_tests(${TEST_NAME}) add_test(NAME ${TEST_NAME} WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} - COMMAND ${TEST_NAME} + COMMAND ${TEST_NAME} "--gtest_color=yes" ) \ No newline at end of file diff --git a/tests/Delegates/DelegateAllocator/AlocationDeffender.h b/tests/Delegates/DelegateAllocator/AlocationDeffender.h new file mode 100644 index 0000000..99a755a --- /dev/null +++ b/tests/Delegates/DelegateAllocator/AlocationDeffender.h @@ -0,0 +1,40 @@ +#ifndef TGENGINE_ALOCATION_DEFFENDER_H +#define TGENGINE_ALOCATION_DEFFENDER_H + +#include + +template +class AllocationDeffender +{ +public: + + using DelegateType = typename Allocator::value_type; + + explicit AllocationDeffender(Allocator& allocator) + : allocator(allocator) + , allocations() + {} + + const DelegateType* allocate(std::size_t n) + { + DelegateType* delegate = allocator.allocate(n); + allocations.push_back({delegate, n}); + return delegate; + } + + ~AllocationDeffender() + { + for (auto iter = allocations.rbegin(); iter != allocations.rend(); ++iter) + { + auto& [delegate, n] = *iter; + allocator.deallocate(delegate, n); + } + } + +private: + + Allocator& allocator; + std::vector> allocations; +}; + +#endif //TGENGINE_ALOCATION_DEFFENDER_H diff --git a/tests/Delegates/DelegateAllocator/DelegateAllocatorTest.cpp b/tests/Delegates/DelegateAllocator/DelegateAllocatorTest.cpp new file mode 100644 index 0000000..41a16e1 --- /dev/null +++ b/tests/Delegates/DelegateAllocator/DelegateAllocatorTest.cpp @@ -0,0 +1,218 @@ +#include "Core/Delegates/DelegateAllocator.h" + +#include "AlocationDeffender.h" +#include "Core/Memory/StackStorage.h" +#include "Core/Types/Callable.h" + +#include + +using namespace TGEngine::Core::_DelegateInternals; +using namespace TGEngine::Core; + +using Storage = StackStorage<64, 8>; +using SmallStorage = StackStorage<8, 8>; + +TEST(DelegateAllocatorTest, CopyIsEqualToOriginalAllocator) +{ + Storage storage; + DelegateAllocator, Storage> allocator1(storage); + DelegateAllocator allocator2 = allocator1; + + EXPECT_EQ(allocator1, allocator2); +} + +TEST(DelegateAllocatorTest, AnyAllocatorsWithDifferentDelegateTypeSignatureArentEquals) +{ + Storage storage; + DelegateAllocator, Storage> allocator1(storage); + DelegateAllocator, Storage> allocator2(storage); + + EXPECT_NE(allocator1, allocator2); +} + +TEST(DelegateAllocatorTest, AnyAllocatorsWithTheDifferentStoragesArentEquals) +{ + Storage storage1; + DelegateAllocator, Storage> allocator1(storage1); + + Storage storage2; + DelegateAllocator, Storage> allocator2(storage2); + + EXPECT_NE(allocator1, allocator2); +} + +TEST(DelegateAllocatorTest, AllocatorsWithSameStorageAndCommonBaseDelegateTypeAreEquals) +{ + Storage storage; + DelegateAllocator, Storage> allocator1(storage); + DelegateAllocator, void()>, Storage> allocator2(storage); + DelegateAllocator, void(), double, int>, Storage> allocator3(storage); + + EXPECT_EQ(allocator1, allocator2); + EXPECT_EQ(allocator2, allocator3); +} + +TEST(DelegateAllocatorTest, AlocatesInStorageIfEnoughFreeSpace) +{ + using DelegateType = StaticDelegate, void()>; + + Storage storage; + DelegateAllocator allocator(storage); + AllocationDeffender allocDeffender(allocator); + + const DelegateType* delegate = allocDeffender.allocate(1); + + EXPECT_NE(delegate, nullptr); + EXPECT_TRUE(sizeof(DelegateType) <= storage.size); + EXPECT_TRUE(storage.contains(delegate)); +} + +TEST(DelegateAllocatorTest, AlocatesInHeapIfNotEnoughFreeSpace) +{ + using DelegateType = StaticDelegate, void(), double>; + + SmallStorage storage; + DelegateAllocator allocator(storage); + AllocationDeffender allocDeffender(allocator); + + const DelegateType* delegate = allocDeffender.allocate(1); + + EXPECT_NE(delegate, nullptr); + EXPECT_TRUE(sizeof(DelegateType) > storage.size); + EXPECT_FALSE(storage.contains(delegate)); +} + +TEST(DelegateAllocatorTest, AllocatesInStorageOnlyOneObject) +{ + using DelegateType = StaticDelegate, void()>; + + Storage storage; + DelegateAllocator allocator(storage); + AllocationDeffender allocDeffender(allocator); + + const DelegateType* delegate1 = allocDeffender.allocate(1); + const DelegateType* delegate2 = allocDeffender.allocate(1); + + EXPECT_NE(delegate1, nullptr); + EXPECT_NE(delegate2, nullptr); + EXPECT_TRUE(2 * sizeof(DelegateType) <= storage.size); + EXPECT_TRUE(storage.contains(delegate1)); + EXPECT_FALSE(storage.contains(delegate2)); +} + +TEST(DelegateAllocatorTest, AllocateReservesSizeofValueTypeBytesWhenNIsLarge) +{ + using DelegateType = StaticDelegate, void()>; + + Storage storage; + DelegateAllocator allocator(storage); + AllocationDeffender allocDeffender(allocator); + + const DelegateType* delegate = allocDeffender.allocate(100); + + EXPECT_NE(delegate, nullptr); + EXPECT_EQ(storage.used(), sizeof(DelegateType)); +} + +TEST(DelegateAllocatorTest, AllocateReservesSizeofValueTypeBytesWhenNIsSmall) +{ + using DelegateType = StaticDelegate, void()>; + + Storage storage; + DelegateAllocator allocator(storage); + AllocationDeffender allocDeffender(allocator); + + const DelegateType* delegate = allocDeffender.allocate(0); + + EXPECT_NE(delegate, nullptr); + EXPECT_EQ(storage.used(), sizeof(DelegateType)); +} + +TEST(DelegateAllocatorTest, AfterDeallocationStorageIsEmpty) +{ + Storage storage; + using DelegateType = StaticDelegate, void()>; + DelegateAllocator allocator(storage); + + DelegateType* delegate = allocator.allocate(1); + const size_t usedAfterAlloc = storage.used(); + + allocator.deallocate(delegate, 1); + const size_t usedAfterDealloc = storage.used(); + + EXPECT_NE(usedAfterAlloc, 0); + EXPECT_EQ(usedAfterDealloc, 0); +} + +TEST(DelegateAllocatorTest, DeallocateErasesWholeStorageWhenNIsLarge) +{ + Storage storage; + using DelegateType = StaticDelegate, void()>; + DelegateAllocator allocator(storage); + + DelegateType* delegate = allocator.allocate(1); + const size_t usedAfterAlloc = storage.used(); + + allocator.deallocate(delegate, 100); + const size_t usedAfterDealloc = storage.used(); + + EXPECT_NE(usedAfterAlloc, 0); + EXPECT_EQ(usedAfterDealloc, 0); +} + +TEST(DelegateAllocatorTest, DeallocateErasesWholeStorageWhenNIsSmall) +{ + Storage storage; + using DelegateType = StaticDelegate, void()>; + DelegateAllocator allocator(storage); + + DelegateType* delegate = allocator.allocate(1); + const size_t usedAfterAlloc = storage.used(); + + allocator.deallocate(delegate, 0); + const size_t usedAfterDealloc = storage.used(); + + EXPECT_NE(usedAfterAlloc, 0); + EXPECT_EQ(usedAfterDealloc, 0); +} + +TEST(DelegateAllocatorTest, AllocateCopyReservesTheSameAmountOfMemoryAsAllocatedInOtherStorage) +{ + Storage storage; + using DelegateType = StaticDelegate, void()>; + DelegateAllocator allocator(storage); + + Storage copyStorage; + DelegateAllocator erasedAllocator(copyStorage); + + DelegateType* srcP = allocator.allocate(1); + DelegateType::Erased* dstP = erasedAllocator.allocateCopy(storage); + + EXPECT_NE(srcP, nullptr); + EXPECT_NE(dstP, nullptr); + EXPECT_EQ(storage.used(), copyStorage.used()); + + allocator.deallocate(srcP, 1); + erasedAllocator.deallocate(dstP, 1); +} + +TEST(DelegateAllocatorTest, CantAllocateCopyInTheSameStorage) +{ + Storage storage; + using DelegateType = StaticDelegate, void()>; + DelegateAllocator allocator(storage); + DelegateAllocator erasedAllocator(storage); + + DelegateType* srcP = allocator.allocate(1); + const size_t used = storage.used(); + + DelegateType::Erased* dstP = erasedAllocator.allocateCopy(storage); + + EXPECT_NE(srcP, nullptr); + EXPECT_NE(dstP, nullptr); + EXPECT_EQ(storage.used(), used); + EXPECT_FALSE(storage.contains(dstP)); + + allocator.deallocate(srcP, 1); + erasedAllocator.deallocate(dstP, 1); +} \ No newline at end of file diff --git a/tests/Delegates/DelegateTest.cpp b/tests/Delegates/DelegateTest.cpp new file mode 100644 index 0000000..ed3c19b --- /dev/null +++ b/tests/Delegates/DelegateTest.cpp @@ -0,0 +1,6 @@ +#include + +TEST(DelegateTest, SomeName) +{ + ASSERT_EQ(1, 1); +} \ No newline at end of file diff --git a/tests/Memory/StackStorageTest.cpp b/tests/Memory/StackStorageTest.cpp new file mode 100644 index 0000000..efeb570 --- /dev/null +++ b/tests/Memory/StackStorageTest.cpp @@ -0,0 +1,276 @@ +#include "Core/Memory/StackStorage.h" + +#include +#include +#include + +using namespace TGEngine::Core; + +TEST(StackStorageTest, StorageIsEmptyByDefault) +{ + constexpr size_t size = 64; + const StackStorage storage; + + const bool isEmpty = storage.isEmpty(); + + EXPECT_TRUE(isEmpty); +} + +TEST(StackStorageTest, CantAllocateMoreThanSize) +{ + constexpr size_t size = 64; + const StackStorage storage; + + const bool canAllocate = storage.canAllocate(size + 1); + + EXPECT_FALSE(canAllocate); +} + +TEST(StackStorageTest, CantAllocateZeroBytes) +{ + constexpr size_t size = 64; + const StackStorage storage; + + constexpr bool canAllocate = storage.canAllocate(0); + + EXPECT_FALSE(canAllocate); +} + +TEST(StackStorageTest, CanAllocateBetweenZeroAndSizeBytes) +{ + constexpr size_t size = 64; + StackStorage storage; + + bool canAllocateForAllSizes = true; + for (size_t bytes = 1; bytes < size; ++bytes) + { + canAllocateForAllSizes &= storage.canAllocate(bytes); + } + + EXPECT_TRUE(canAllocateForAllSizes); +} + +TEST(StackStorageTest, PushReturnsNullptrIfCantAllocate) +{ + constexpr size_t size = 64; + StackStorage storage; + + void* zeroBytesBlock = storage.allocate(0); + void* largeBlock = storage.allocate(size + 1); + + EXPECT_EQ(zeroBytesBlock, nullptr); + EXPECT_EQ(largeBlock, nullptr); +} + +TEST(StackStorageTest, StorageIsntEmptyAfterPush) +{ + constexpr size_t size = 64; + StackStorage storage; + + std::ignore = storage.allocate(size); + const bool isEmpty = storage.isEmpty(); + + EXPECT_FALSE(isEmpty); +} + +TEST(StackStorageTest, StorageIsEmptyWhenUsedEqualsZero) +{ + constexpr size_t size = 64; + StackStorage storage; + + const bool isEmpty = storage.isEmpty(); + const size_t used = storage.used(); + + EXPECT_TRUE(isEmpty); + EXPECT_EQ(used, 0); +} + +TEST(StackStorageTest, StorageIsntEmptyWhenUsedDoesntEqualZero) +{ + constexpr size_t size = 64; + StackStorage storage; + + std::ignore = storage.allocate(1); + const bool isEmpty = storage.isEmpty(); + const size_t used = storage.used(); + + EXPECT_FALSE(isEmpty); + EXPECT_NE(used, 0); +} + +TEST(StackStorageTest, StorageAlignsData) +{ + constexpr size_t size = 64; + constexpr size_t align = 8; + StackStorage storage; + + std::array addresses {}; + addresses[0] = reinterpret_cast(storage.allocate(align)); + addresses[1] = reinterpret_cast(storage.allocate(1)); + addresses[2] = reinterpret_cast(storage.allocate(2 * align - 5)); + + for (auto address: addresses) + { + EXPECT_TRUE(address % align == 0); + } + EXPECT_EQ(storage.used(), 32); +} + +TEST(StackStorageTest, AligmentEqualsMaxAlignByDefault) +{ + constexpr size_t size = 64; + StackStorage storage; + + const auto adress = reinterpret_cast(storage.allocate(1)); + + EXPECT_TRUE(adress % alignof(std::max_align_t) == 0); +} + +TEST(StackStorageTest, CanPushWhileHasFreeSpace) +{ + constexpr size_t size = 64; + constexpr size_t align = 8; + StackStorage storage; + + constexpr size_t n = size / align + 1; + std::array addresses {}; + std::ranges::generate(addresses, [&storage] { return storage.allocate(1); }); + + for (auto iter = addresses.begin(); iter < std::prev(addresses.end()); ++iter) + { + EXPECT_NE(*iter, nullptr); + } + EXPECT_EQ(addresses.back(), nullptr); +} + +TEST(StackStorageTest, ConstainsReturnsTrueForPointersFromStorage) +{ + constexpr size_t size = 64; + StackStorage storage; + + const void* p1 = storage.allocate(16); + const void* p2 = storage.allocate(32); + + const bool p1InStorage = storage.contains(p1); + const bool p2InStorage = storage.contains(p2); + + EXPECT_TRUE(p1InStorage); + EXPECT_TRUE(p2InStorage); +} + +TEST(StackStorageTest, ContainsReturnsFalseForNullptr) +{ + constexpr size_t size = 64; + StackStorage storage; + + void* p = storage.allocate(size + 1); + const bool inStorage = storage.contains(p); + + EXPECT_EQ(p, nullptr); + EXPECT_FALSE(inStorage); +} + +TEST(StackStorageTest, ContainsReturnsFalseForPointersOutsideStorage) +{ + constexpr size_t size = 64; + const StackStorage storage; + + int a = 0; + const bool aInStorage = storage.contains(&a); + + const void* p = new int(0); + const bool pInStorage = storage.contains(p); + + EXPECT_FALSE(aInStorage); + EXPECT_FALSE(pInStorage); +} + +TEST(StackStorageTest, PopErasesStorage) +{ + constexpr size_t size = 64; + StackStorage storage; + + const void* p = storage.allocate(size); + const bool storageWasFull = storage.used() == size; + + storage.deallocate(p, size); + const bool storageIsEmpty = storage.isEmpty(); + + EXPECT_TRUE(storageWasFull); + EXPECT_TRUE(storageIsEmpty); +} + +TEST(StackStorageTest, CanAllocateAfterPop) +{ + constexpr size_t size = 64; + StackStorage storage; + + const void* p = storage.allocate(1); + const bool canAllocateBeforePop = storage.canAllocate(size); + + storage.deallocate(p, 1); + const bool canAllocateAfterPop = storage.canAllocate(size); + + EXPECT_FALSE(canAllocateBeforePop); + EXPECT_TRUE(canAllocateAfterPop); +} + +TEST(StackStorageTest, PopSuportsAlign) +{ + constexpr size_t size = 64; + constexpr size_t align = 8; + StackStorage storage; + + const void* p = storage.allocate(1); + storage.deallocate(p, align); + const bool storageIsEmpty = storage.isEmpty(); + + EXPECT_TRUE(storageIsEmpty); +} + +TEST(StackStorageTest, PopInReverseOrder) +{ + constexpr size_t size = 64; + constexpr size_t align = 8; + StackStorage storage; + + const void* p1 = storage.allocate(1); + const void* p2 = storage.allocate(1); + + storage.deallocate(p2, 1); + storage.deallocate(p1, 1); + + const bool storageIsEmpty = storage.isEmpty(); + + EXPECT_TRUE(storageIsEmpty); +} + +TEST(StackStorageTest, CantPopPointerOutsideStorage) +{ + constexpr size_t size = 64; + StackStorage storage; + + EXPECT_DEBUG_DEATH(storage.deallocate(new int(0), sizeof(int)), ""); +} + +TEST(StackStorageTest, CantPopWrongSize) +{ + constexpr size_t size = 64; + StackStorage storage; + + const void* p = storage.allocate(3); + + EXPECT_DEBUG_DEATH(storage.deallocate(p, 2), ""); + EXPECT_DEBUG_DEATH(storage.deallocate(p, 5), ""); +} + +TEST(StackStorageTest, CantPopInWrongOrder) +{ + constexpr size_t size = 64; + StackStorage storage; + + const void* p1 = storage.allocate(4); + std::ignore = storage.allocate(4); + + EXPECT_DEBUG_DEATH(storage.deallocate(p1, 4), ""); +} \ No newline at end of file diff --git a/tests/TestExample.cpp b/tests/TestExample.cpp deleted file mode 100644 index 0de2fc9..0000000 --- a/tests/TestExample.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include - -TEST(ExampleTest, 2Plus2Equals4) -{ - EXPECT_EQ(2 + 2, 4); -} \ No newline at end of file