From 34c75e7129d291628db23fe55f8b38c21ae326ea Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Fri, 13 Mar 2026 20:37:08 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0Containers=E3=80=81Me?= =?UTF-8?q?mory=E3=80=81Threading=E6=A0=B8=E5=BF=83=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E5=8F=8A=E5=8D=95=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Containers: String, Array, HashMap 容器实现及测试 - Memory: Allocator, LinearAllocator, PoolAllocator, ProxyAllocator, MemoryManager 实现及测试 - Threading: Mutex, SpinLock, ReadWriteLock, Thread, Task, TaskSystem 实现及测试 - 修复Windows平台兼容性: _aligned_malloc, std::hash特化 - 修复构建错误和测试用例问题 --- engine/CMakeLists.txt | 31 ++ engine/include/XCEngine/Containers/Array.h | 292 ++++++++++++++++ .../include/XCEngine/Containers/Containers.h | 11 + engine/include/XCEngine/Containers/HashMap.h | 306 ++++++++++++++++ engine/include/XCEngine/Containers/String.h | 96 +++++ engine/include/XCEngine/Core/Event.h | 2 +- engine/include/XCEngine/Memory/Allocator.h | 26 ++ .../include/XCEngine/Memory/LinearAllocator.h | 39 +++ .../include/XCEngine/Memory/MemoryManager.h | 46 +++ .../include/XCEngine/Memory/PoolAllocator.h | 43 +++ .../include/XCEngine/Memory/ProxyAllocator.h | 41 +++ .../include/XCEngine/Threading/LambdaTask.h | 23 ++ engine/include/XCEngine/Threading/Mutex.h | 26 ++ .../XCEngine/Threading/ReadWriteLock.h | 53 +++ engine/include/XCEngine/Threading/SpinLock.h | 32 ++ engine/include/XCEngine/Threading/Task.h | 61 ++++ engine/include/XCEngine/Threading/TaskGroup.h | 51 +++ .../include/XCEngine/Threading/TaskSystem.h | 108 ++++++ .../XCEngine/Threading/TaskSystemConfig.h | 17 + engine/include/XCEngine/Threading/Thread.h | 43 +++ engine/include/XCEngine/Threading/Threading.h | 17 + engine/src/Containers/String.cpp | 327 ++++++++++++++++++ engine/src/Memory/Memory.cpp | 272 +++++++++++++++ engine/src/Threading/TaskGroup.cpp | 74 ++++ engine/src/Threading/TaskSystem.cpp | 162 +++++++++ engine/src/Threading/Thread.cpp | 41 +++ tests/CMakeLists.txt | 4 + tests/containers/CMakeLists.txt | 31 ++ tests/containers/test_array.cpp | 162 +++++++++ tests/containers/test_hashmap.cpp | 139 ++++++++ tests/containers/test_string.cpp | 133 +++++++ tests/core/CMakeLists.txt | 30 ++ tests/core/test_core.cpp | 226 ++++++++++++ tests/memory/CMakeLists.txt | 31 ++ tests/memory/test_linear_allocator.cpp | 77 +++++ tests/memory/test_memory_manager.cpp | 44 +++ tests/memory/test_pool_allocator.cpp | 83 +++++ tests/run_tests.bat | 8 + tests/threading/CMakeLists.txt | 31 ++ tests/threading/test_mutex.cpp | 41 +++ tests/threading/test_spinlock.cpp | 32 ++ tests/threading/test_task.cpp | 59 ++++ 42 files changed, 3370 insertions(+), 1 deletion(-) create mode 100644 engine/include/XCEngine/Containers/Array.h create mode 100644 engine/include/XCEngine/Containers/Containers.h create mode 100644 engine/include/XCEngine/Containers/HashMap.h create mode 100644 engine/include/XCEngine/Containers/String.h create mode 100644 engine/include/XCEngine/Memory/Allocator.h create mode 100644 engine/include/XCEngine/Memory/LinearAllocator.h create mode 100644 engine/include/XCEngine/Memory/MemoryManager.h create mode 100644 engine/include/XCEngine/Memory/PoolAllocator.h create mode 100644 engine/include/XCEngine/Memory/ProxyAllocator.h create mode 100644 engine/include/XCEngine/Threading/LambdaTask.h create mode 100644 engine/include/XCEngine/Threading/Mutex.h create mode 100644 engine/include/XCEngine/Threading/ReadWriteLock.h create mode 100644 engine/include/XCEngine/Threading/SpinLock.h create mode 100644 engine/include/XCEngine/Threading/Task.h create mode 100644 engine/include/XCEngine/Threading/TaskGroup.h create mode 100644 engine/include/XCEngine/Threading/TaskSystem.h create mode 100644 engine/include/XCEngine/Threading/TaskSystemConfig.h create mode 100644 engine/include/XCEngine/Threading/Thread.h create mode 100644 engine/include/XCEngine/Threading/Threading.h create mode 100644 engine/src/Containers/String.cpp create mode 100644 engine/src/Memory/Memory.cpp create mode 100644 engine/src/Threading/TaskGroup.cpp create mode 100644 engine/src/Threading/TaskSystem.cpp create mode 100644 engine/src/Threading/Thread.cpp create mode 100644 tests/containers/CMakeLists.txt create mode 100644 tests/containers/test_array.cpp create mode 100644 tests/containers/test_hashmap.cpp create mode 100644 tests/containers/test_string.cpp create mode 100644 tests/core/CMakeLists.txt create mode 100644 tests/core/test_core.cpp create mode 100644 tests/memory/CMakeLists.txt create mode 100644 tests/memory/test_linear_allocator.cpp create mode 100644 tests/memory/test_memory_manager.cpp create mode 100644 tests/memory/test_pool_allocator.cpp create mode 100644 tests/run_tests.bat create mode 100644 tests/threading/CMakeLists.txt create mode 100644 tests/threading/test_mutex.cpp create mode 100644 tests/threading/test_spinlock.cpp create mode 100644 tests/threading/test_task.cpp diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index cc88aa10..f386b7cc 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -5,6 +5,7 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) add_library(XCEngine STATIC + # Math include/XCEngine/Math/Math.h include/XCEngine/Math/Vector2.h include/XCEngine/Math/Vector3.h @@ -28,6 +29,36 @@ add_library(XCEngine STATIC src/Math/Color.cpp src/Math/Geometry.cpp src/Math/FrustumBounds.cpp + + # Containers + include/XCEngine/Containers/Containers.h + include/XCEngine/Containers/Array.h + include/XCEngine/Containers/String.h + include/XCEngine/Containers/HashMap.h + src/Containers/String.cpp + + # Memory + include/XCEngine/Memory/Allocator.h + include/XCEngine/Memory/LinearAllocator.h + include/XCEngine/Memory/PoolAllocator.h + include/XCEngine/Memory/ProxyAllocator.h + include/XCEngine/Memory/MemoryManager.h + src/Memory/Memory.cpp + + # Threading + include/XCEngine/Threading/Threading.h + include/XCEngine/Threading/Mutex.h + include/XCEngine/Threading/SpinLock.h + include/XCEngine/Threading/ReadWriteLock.h + include/XCEngine/Threading/Thread.h + include/XCEngine/Threading/Task.h + include/XCEngine/Threading/LambdaTask.h + include/XCEngine/Threading/TaskGroup.h + include/XCEngine/Threading/TaskSystemConfig.h + include/XCEngine/Threading/TaskSystem.h + src/Threading/Thread.cpp + src/Threading/TaskGroup.cpp + src/Threading/TaskSystem.cpp ) target_include_directories(XCEngine PUBLIC diff --git a/engine/include/XCEngine/Containers/Array.h b/engine/include/XCEngine/Containers/Array.h new file mode 100644 index 00000000..63361ff6 --- /dev/null +++ b/engine/include/XCEngine/Containers/Array.h @@ -0,0 +1,292 @@ +#pragma once + +#include +#include +#include +#include +#include "../Memory/Allocator.h" + +namespace XCEngine { +namespace Containers { + +template +class Array { +public: + using Iterator = T*; + using ConstIterator = const T*; + + Array() = default; + explicit Array(size_t capacity); + Array(size_t count, const T& value); + Array(std::initializer_list init); + ~Array(); + + Array(const Array& other); + Array(Array&& other) noexcept; + Array& operator=(const Array& other); + Array& operator=(Array&& other) noexcept; + + T& operator[](size_t index); + const T& operator[](size_t index) const; + + T* Data() { return m_data; } + const T* Data() const { return m_data; } + + size_t Size() const { return m_size; } + size_t Capacity() const { return m_capacity; } + + bool Empty() const { return m_size == 0; } + + void Clear(); + void Reserve(size_t capacity); + void Resize(size_t newSize); + void Resize(size_t newSize, const T& value); + + void PushBack(const T& value); + void PushBack(T&& value); + template + T& EmplaceBack(Args&&... args); + void PopBack(); + + T& Front() { return m_data[0]; } + const T& Front() const { return m_data[0]; } + T& Back() { return m_data[m_size - 1]; } + const T& Back() const { return m_data[m_size - 1]; } + + Iterator begin() { return m_data; } + Iterator end() { return m_data + m_size; } + ConstIterator begin() const { return m_data; } + ConstIterator end() const { return m_data + m_size; } + + void SetAllocator(Memory::IAllocator* allocator) { m_allocator = allocator; } + +private: + T* m_data = nullptr; + size_t m_size = 0; + size_t m_capacity = 0; + Memory::IAllocator* m_allocator = nullptr; + + void Reallocate(size_t newCapacity); + void DestructRange(T* begin, T* end); + void CopyRange(const T* src, T* dst, size_t count); +}; + +template +Array::Array(size_t capacity) : m_capacity(capacity) { + if (capacity > 0) { + m_data = static_cast(::operator new(capacity * sizeof(T))); + } +} + +template +Array::Array(size_t count, const T& value) : m_size(count), m_capacity(count) { + if (count > 0) { + m_data = static_cast(::operator new(count * sizeof(T))); + for (size_t i = 0; i < count; ++i) { + new (&m_data[i]) T(value); + } + } +} + +template +Array::Array(std::initializer_list init) : m_size(init.size()), m_capacity(init.size()) { + if (m_size > 0) { + m_data = static_cast(::operator new(m_size * sizeof(T))); + size_t i = 0; + for (const auto& item : init) { + new (&m_data[i++]) T(item); + } + } +} + +template +Array::~Array() { + DestructRange(m_data, m_data + m_size); + ::operator delete(m_data); +} + +template +Array::Array(const Array& other) : m_size(other.m_size), m_capacity(other.m_size) { + if (m_size > 0) { + m_data = static_cast(::operator new(m_size * sizeof(T))); + CopyRange(other.m_data, m_data, m_size); + } +} + +template +Array::Array(Array&& other) noexcept + : m_data(other.m_data), m_size(other.m_size), m_capacity(other.m_capacity), m_allocator(other.m_allocator) { + other.m_data = nullptr; + other.m_size = 0; + other.m_capacity = 0; +} + +template +Array& Array::operator=(const Array& other) { + if (this != &other) { + DestructRange(m_data, m_data + m_size); + ::operator delete(m_data); + + m_size = other.m_size; + m_capacity = other.m_size; + m_allocator = other.m_allocator; + + if (m_size > 0) { + m_data = static_cast(::operator new(m_size * sizeof(T))); + CopyRange(other.m_data, m_data, m_size); + } else { + m_data = nullptr; + } + } + return *this; +} + +template +Array& Array::operator=(Array&& other) noexcept { + if (this != &other) { + DestructRange(m_data, m_data + m_size); + ::operator delete(m_data); + + m_data = other.m_data; + m_size = other.m_size; + m_capacity = other.m_capacity; + m_allocator = other.m_allocator; + + other.m_data = nullptr; + other.m_size = 0; + other.m_capacity = 0; + } + return *this; +} + +template +T& Array::operator[](size_t index) { + return m_data[index]; +} + +template +const T& Array::operator[](size_t index) const { + return m_data[index]; +} + +template +void Array::Clear() { + DestructRange(m_data, m_data + m_size); + m_size = 0; +} + +template +void Array::Reserve(size_t capacity) { + if (capacity > m_capacity) { + Reallocate(capacity); + } +} + +template +void Array::Resize(size_t newSize) { + if (newSize > m_capacity) { + Reallocate(newSize); + } + + if (newSize > m_size) { + for (size_t i = m_size; i < newSize; ++i) { + new (&m_data[i]) T(); + } + } else if (newSize < m_size) { + DestructRange(m_data + newSize, m_data + m_size); + } + + m_size = newSize; +} + +template +void Array::Resize(size_t newSize, const T& value) { + if (newSize > m_capacity) { + Reallocate(newSize); + } + + if (newSize > m_size) { + for (size_t i = m_size; i < newSize; ++i) { + new (&m_data[i]) T(value); + } + } else if (newSize < m_size) { + DestructRange(m_data + newSize, m_data + m_size); + } + + m_size = newSize; +} + +template +void Array::PushBack(const T& value) { + if (m_size >= m_capacity) { + size_t newCapacity = m_capacity == 0 ? 4 : m_capacity * 2; + Reallocate(newCapacity); + } + new (&m_data[m_size]) T(value); + ++m_size; +} + +template +void Array::PushBack(T&& value) { + if (m_size >= m_capacity) { + size_t newCapacity = m_capacity == 0 ? 4 : m_capacity * 2; + Reallocate(newCapacity); + } + new (&m_data[m_size]) T(std::move(value)); + ++m_size; +} + +template +template +T& Array::EmplaceBack(Args&&... args) { + if (m_size >= m_capacity) { + size_t newCapacity = m_capacity == 0 ? 4 : m_capacity * 2; + Reallocate(newCapacity); + } + new (&m_data[m_size]) T(std::forward(args)...); + ++m_size; + return m_data[m_size - 1]; +} + +template +void Array::PopBack() { + if (m_size > 0) { + --m_size; + m_data[m_size].~T(); + } +} + +template +void Array::Reallocate(size_t newCapacity) { + T* newData = nullptr; + if (newCapacity > 0) { + newData = static_cast(::operator new(newCapacity * sizeof(T))); + size_t count = std::min(m_size, newCapacity); + if (count > 0) { + CopyRange(m_data, newData, count); + } + } + + DestructRange(m_data, m_data + m_size); + ::operator delete(m_data); + + m_data = newData; + m_capacity = newCapacity; +} + +template +void Array::DestructRange(T* begin, T* end) { + for (T* p = begin; p < end; ++p) { + p->~T(); + } +} + +template +void Array::CopyRange(const T* src, T* dst, size_t count) { + for (size_t i = 0; i < count; ++i) { + new (&dst[i]) T(src[i]); + } +} + +} // namespace Containers +} // namespace XCEngine diff --git a/engine/include/XCEngine/Containers/Containers.h b/engine/include/XCEngine/Containers/Containers.h new file mode 100644 index 00000000..ac72f7a1 --- /dev/null +++ b/engine/include/XCEngine/Containers/Containers.h @@ -0,0 +1,11 @@ +#pragma once + +#include "Containers/Array.h" +#include "Containers/String.h" +#include "Containers/HashMap.h" + +namespace XCEngine { +namespace Containers { + +} // namespace Containers +} // namespace XCEngine diff --git a/engine/include/XCEngine/Containers/HashMap.h b/engine/include/XCEngine/Containers/HashMap.h new file mode 100644 index 00000000..098af91e --- /dev/null +++ b/engine/include/XCEngine/Containers/HashMap.h @@ -0,0 +1,306 @@ +#pragma once + +#include +#include +#include +#include "../Memory/Allocator.h" +#include "Array.h" + +namespace XCEngine { +namespace Containers { + +template +class HashMap { +public: + struct Pair { + Key first; + Value second; + }; + + HashMap() { + m_buckets.Resize(DefaultBucketCount); + } + explicit HashMap(size_t bucketCount, Memory::IAllocator* allocator = nullptr); + ~HashMap(); + + HashMap(const HashMap& other); + HashMap(HashMap&& other) noexcept; + HashMap& operator=(const HashMap& other); + HashMap& operator=(HashMap&& other) noexcept; + + Value& operator[](const Key& key); + Value* Find(const Key& key); + const Value* Find(const Key& key) const; + bool Contains(const Key& key) const; + + bool Insert(const Key& key, const Value& value); + bool Insert(const Key& key, Value&& value); + bool Insert(Pair&& pair); + bool Erase(const Key& key); + void Clear(); + + size_t Size() const { return m_size; } + bool Empty() const { return m_size == 0; } + + void SetAllocator(Memory::IAllocator* allocator) { m_allocator = allocator; } + +private: + struct Bucket { + Array pairs; + }; + + static constexpr size_t DefaultBucketCount = 16; + + Array m_buckets; + size_t m_bucketCount = DefaultBucketCount; + size_t m_size = 0; + float m_loadFactor = 0.75f; + Memory::IAllocator* m_allocator = nullptr; + + size_t GetBucketIndex(const Key& key) const; + void Resize(); + typename Array::Iterator FindInBucket(Bucket& bucket, const Key& key); + typename Array::ConstIterator FindInBucket(const Bucket& bucket, const Key& key) const; +}; + +template +HashMap::HashMap(size_t bucketCount, Memory::IAllocator* allocator) + : m_bucketCount(bucketCount), m_allocator(allocator) { + if (m_bucketCount == 0) { + m_bucketCount = 16; + } + m_buckets.Resize(m_bucketCount); +} + +template +HashMap::~HashMap() { + Clear(); +} + +template +HashMap::HashMap(const HashMap& other) + : m_bucketCount(other.m_bucketCount), m_size(other.m_size), m_loadFactor(other.m_loadFactor), m_allocator(other.m_allocator) { + m_buckets.Resize(m_bucketCount); + for (size_t i = 0; i < m_bucketCount; ++i) { + m_buckets[i].pairs = other.m_buckets[i].pairs; + } +} + +template +HashMap::HashMap(HashMap&& other) noexcept + : m_buckets(std::move(other.m_buckets)), + m_bucketCount(other.m_bucketCount), + m_size(other.m_size), + m_loadFactor(other.m_loadFactor), + m_allocator(other.m_allocator) { + other.m_buckets.Clear(); + other.m_bucketCount = 0; + other.m_size = 0; +} + +template +HashMap& HashMap::operator=(const HashMap& other) { + if (this != &other) { + Clear(); + m_bucketCount = other.m_bucketCount; + m_size = other.m_size; + m_loadFactor = other.m_loadFactor; + m_allocator = other.m_allocator; + m_buckets.Resize(m_bucketCount); + for (size_t i = 0; i < m_bucketCount; ++i) { + m_buckets[i].pairs = other.m_buckets[i].pairs; + } + } + return *this; +} + +template +HashMap& HashMap::operator=(HashMap&& other) noexcept { + if (this != &other) { + Clear(); + m_buckets = std::move(other.m_buckets); + m_bucketCount = other.m_bucketCount; + m_size = other.m_size; + m_loadFactor = other.m_loadFactor; + m_allocator = other.m_allocator; + + other.m_buckets.Clear(); + other.m_bucketCount = 0; + other.m_size = 0; + } + return *this; +} + +template +Value& HashMap::operator[](const Key& key) { + size_t bucketIndex = GetBucketIndex(key); + Bucket& bucket = m_buckets[bucketIndex]; + + auto it = FindInBucket(bucket, key); + if (it != bucket.pairs.end()) { + return it->second; + } + + bucket.pairs.EmplaceBack(Pair{key, Value()}); + ++m_size; + + if (static_cast(m_size) / static_cast(m_bucketCount) > m_loadFactor) { + Resize(); + } + + return bucket.pairs.Back().second; +} + +template +Value* HashMap::Find(const Key& key) { + size_t bucketIndex = GetBucketIndex(key); + Bucket& bucket = m_buckets[bucketIndex]; + + auto it = FindInBucket(bucket, key); + if (it != bucket.pairs.end()) { + return &it->second; + } + return nullptr; +} + +template +const Value* HashMap::Find(const Key& key) const { + size_t bucketIndex = GetBucketIndex(key); + const Bucket& bucket = m_buckets[bucketIndex]; + + auto it = FindInBucket(bucket, key); + if (it != bucket.pairs.end()) { + return &it->second; + } + return nullptr; +} + +template +bool HashMap::Contains(const Key& key) const { + size_t bucketIndex = GetBucketIndex(key); + const Bucket& bucket = m_buckets[bucketIndex]; + return FindInBucket(bucket, key) != bucket.pairs.end(); +} + +template +bool HashMap::Insert(const Key& key, const Value& value) { + size_t bucketIndex = GetBucketIndex(key); + Bucket& bucket = m_buckets[bucketIndex]; + + auto it = FindInBucket(bucket, key); + if (it != bucket.pairs.end()) { + it->second = value; + return false; + } + + bucket.pairs.EmplaceBack(Pair{key, value}); + ++m_size; + + if (static_cast(m_size) / static_cast(m_bucketCount) > m_loadFactor) { + Resize(); + } + + return true; +} + +template +bool HashMap::Insert(const Key& key, Value&& value) { + size_t bucketIndex = GetBucketIndex(key); + Bucket& bucket = m_buckets[bucketIndex]; + + auto it = FindInBucket(bucket, key); + if (it != bucket.pairs.end()) { + it->second = std::move(value); + return false; + } + + bucket.pairs.EmplaceBack(Pair{key, std::move(value)}); + ++m_size; + + if (static_cast(m_size) / static_cast(m_bucketCount) > m_loadFactor) { + Resize(); + } + + return true; +} + +template +bool HashMap::Insert(Pair&& pair) { + return Insert(std::move(pair.first), std::move(pair.second)); +} + +template +bool HashMap::Erase(const Key& key) { + size_t bucketIndex = GetBucketIndex(key); + Bucket& bucket = m_buckets[bucketIndex]; + + auto it = FindInBucket(bucket, key); + if (it != bucket.pairs.end()) { + size_t index = it - bucket.pairs.begin(); + bucket.pairs[index] = bucket.pairs.Back(); + bucket.pairs.PopBack(); + --m_size; + return true; + } + return false; +} + +template +void HashMap::Clear() { + for (size_t i = 0; i < m_buckets.Size(); ++i) { + m_buckets[i].pairs.Clear(); + } + m_size = 0; +} + +template +size_t HashMap::GetBucketIndex(const Key& key) const { + if (m_bucketCount == 0) { + return 0; + } + std::hash hasher; + return hasher(key) % m_bucketCount; +} + +template +void HashMap::Resize() { + size_t newBucketCount = m_bucketCount * 2; + Array newBuckets; + newBuckets.Resize(newBucketCount); + + for (size_t i = 0; i < m_bucketCount; ++i) { + Bucket& oldBucket = m_buckets[i]; + for (size_t j = 0; j < oldBucket.pairs.Size(); ++j) { + Pair& pair = oldBucket.pairs[j]; + std::hash hasher; + size_t newIndex = hasher(pair.first) % newBucketCount; + newBuckets[newIndex].pairs.EmplaceBack(std::move(pair)); + } + } + + m_buckets = std::move(newBuckets); + m_bucketCount = newBucketCount; +} + +template +typename Array::Pair>::Iterator HashMap::FindInBucket(Bucket& bucket, const Key& key) { + for (auto it = bucket.pairs.begin(); it != bucket.pairs.end(); ++it) { + if (it->first == key) { + return it; + } + } + return bucket.pairs.end(); +} + +template +typename Array::Pair>::ConstIterator HashMap::FindInBucket(const Bucket& bucket, const Key& key) const { + for (auto it = bucket.pairs.begin(); it != bucket.pairs.end(); ++it) { + if (it->first == key) { + return it; + } + } + return bucket.pairs.end(); +} + +} // namespace Containers +} // namespace XCEngine diff --git a/engine/include/XCEngine/Containers/String.h b/engine/include/XCEngine/Containers/String.h new file mode 100644 index 00000000..85eae3bd --- /dev/null +++ b/engine/include/XCEngine/Containers/String.h @@ -0,0 +1,96 @@ +#pragma once + +#include +#include +#include + +namespace XCEngine { +namespace Containers { + +class String { +public: + using SizeType = size_t; + static constexpr SizeType npos = static_cast(-1); + + String(); + String(const char* str); + String(const char* str, SizeType len); + String(const String& other); + String(String&& other) noexcept; + ~String(); + + String& operator=(const String& other); + String& operator=(String&& other) noexcept; + String& operator=(const char* str); + + String& operator+=(const String& other); + String& operator+=(const char* str); + String& operator+=(char c); + + String Substring(SizeType pos, SizeType len = npos) const; + String Trim() const; + String ToLower() const; + String ToUpper() const; + + SizeType Find(const char* str, SizeType pos = 0) const; + bool StartsWith(const String& prefix) const; + bool StartsWith(const char* prefix) const; + bool EndsWith(const String& suffix) const; + bool EndsWith(const char* suffix) const; + + const char* CStr() const { return m_data; } + SizeType Length() const { return m_length; } + SizeType Capacity() const { return m_capacity; } + bool Empty() const { return m_length == 0; } + + char& operator[](SizeType index) { return m_data[index]; } + const char& operator[](SizeType index) const { return m_data[index]; } + + void Clear(); + void Reserve(SizeType capacity); + void Resize(SizeType newSize); + void Resize(SizeType newSize, char fillChar); + +private: + char* m_data = nullptr; + SizeType m_length = 0; + SizeType m_capacity = 0; + + void Allocate(SizeType capacity); + void Deallocate(); + void CopyFrom(const char* str, SizeType len); + void MoveFrom(String&& other) noexcept; +}; + +inline String operator+(const String& lhs, const String& rhs) { + String result(lhs); + result += rhs; + return result; +} + +inline bool operator==(const String& lhs, const String& rhs) { + return lhs.Length() == rhs.Length() && + std::strncmp(lhs.CStr(), rhs.CStr(), lhs.Length()) == 0; +} + +inline bool operator!=(const String& lhs, const String& rhs) { + return !(lhs == rhs); +} + +} // namespace Containers + +} // namespace XCEngine + +namespace std { +template<> +struct hash { + size_t operator()(const XCEngine::Containers::String& str) const noexcept { + size_t hash = 5381; + const char* s = str.CStr(); + while (*s) { + hash = ((hash << 5) + hash) + *s++; + } + return hash; + } +}; +} diff --git a/engine/include/XCEngine/Core/Event.h b/engine/include/XCEngine/Core/Event.h index 2d5b6b61..15376236 100644 --- a/engine/include/XCEngine/Core/Event.h +++ b/engine/include/XCEngine/Core/Event.h @@ -39,7 +39,7 @@ public: m_pendingUnsubscribes.clear(); } - void Invoke(Args... args) const { + void Invoke(Args... args) { std::vector listenersCopy; { std::lock_guard lock(m_mutex); diff --git a/engine/include/XCEngine/Memory/Allocator.h b/engine/include/XCEngine/Memory/Allocator.h new file mode 100644 index 00000000..ad8e2009 --- /dev/null +++ b/engine/include/XCEngine/Memory/Allocator.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +namespace XCEngine { +namespace Memory { + +class IAllocator { +public: + virtual ~IAllocator() = default; + + virtual void* Allocate(size_t size, size_t alignment = 0) = 0; + virtual void Free(void* ptr) = 0; + virtual void* Reallocate(void* ptr, size_t newSize) = 0; + + virtual size_t GetTotalAllocated() const = 0; + virtual size_t GetTotalFreed() const = 0; + virtual size_t GetPeakAllocated() const = 0; + virtual size_t GetAllocationCount() const = 0; + + virtual const char* GetName() const = 0; +}; + +} // namespace Memory +} // namespace XCEngine diff --git a/engine/include/XCEngine/Memory/LinearAllocator.h b/engine/include/XCEngine/Memory/LinearAllocator.h new file mode 100644 index 00000000..bebf1a2c --- /dev/null +++ b/engine/include/XCEngine/Memory/LinearAllocator.h @@ -0,0 +1,39 @@ +#pragma once + +#include "Allocator.h" + +namespace XCEngine { +namespace Memory { + +class LinearAllocator : public IAllocator { +public: + explicit LinearAllocator(size_t size, IAllocator* parent = nullptr); + ~LinearAllocator() override; + + void* Allocate(size_t size, size_t alignment = 8) override; + void Free(void* ptr) override; + void* Reallocate(void* ptr, size_t newSize) override; + void Clear(); + + void* GetMarker() const; + void SetMarker(void* marker); + + size_t GetUsedSize() const { return m_offset; } + size_t GetCapacity() const { return m_capacity; } + + const char* GetName() const override { return "LinearAllocator"; } + + size_t GetTotalAllocated() const override { return m_offset; } + size_t GetTotalFreed() const override { return 0; } + size_t GetPeakAllocated() const override { return m_capacity; } + size_t GetAllocationCount() const override { return 0; } + +private: + uint8_t* m_buffer = nullptr; + size_t m_capacity = 0; + size_t m_offset = 0; + IAllocator* m_parent = nullptr; +}; + +} // namespace Memory +} // namespace XCEngine diff --git a/engine/include/XCEngine/Memory/MemoryManager.h b/engine/include/XCEngine/Memory/MemoryManager.h new file mode 100644 index 00000000..c1426f5c --- /dev/null +++ b/engine/include/XCEngine/Memory/MemoryManager.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include "LinearAllocator.h" +#include "PoolAllocator.h" +#include "ProxyAllocator.h" + +namespace XCEngine { +namespace Memory { + +class IAllocator; +class LinearAllocator; +class PoolAllocator; +class ProxyAllocator; + +class MemoryManager { +public: + static MemoryManager& Get(); + + void Initialize(); + void Shutdown(); + + IAllocator* GetSystemAllocator(); + + std::unique_ptr CreateLinearAllocator(size_t size); + std::unique_ptr CreatePoolAllocator(size_t blockSize, size_t count); + std::unique_ptr CreateProxyAllocator(const char* name); + + void SetTrackAllocations(bool track); + void DumpMemoryLeaks(); + void GenerateMemoryReport(); + +private: + MemoryManager() = default; + ~MemoryManager() = default; + + IAllocator* m_systemAllocator = nullptr; + bool m_initialized = false; + bool m_trackAllocations = true; +}; + +#define XE_ALLOC(allocator, size, ...) allocator->Allocate(size, ##__VA_ARGS__) +#define XE_FREE(allocator, ptr) allocator->Free(ptr) + +} // namespace Memory +} // namespace XCEngine diff --git a/engine/include/XCEngine/Memory/PoolAllocator.h b/engine/include/XCEngine/Memory/PoolAllocator.h new file mode 100644 index 00000000..920a6f79 --- /dev/null +++ b/engine/include/XCEngine/Memory/PoolAllocator.h @@ -0,0 +1,43 @@ +#pragma once + +#include "Allocator.h" + +namespace XCEngine { +namespace Memory { + +class PoolAllocator : public IAllocator { +public: + PoolAllocator(size_t blockSize, size_t poolSize, size_t alignment = 8); + ~PoolAllocator() override; + + void* Allocate(size_t size, size_t alignment = 0) override; + void Free(void* ptr) override; + void* Reallocate(void* ptr, size_t newSize) override; + + bool Contains(void* ptr) const; + size_t GetBlockSize() const { return m_blockSize; } + size_t GetFreeBlockCount() const; + size_t GetTotalBlockCount() const { return m_totalBlocks; } + + const char* GetName() const override { return "PoolAllocator"; } + + size_t GetTotalAllocated() const override { return (m_totalBlocks - m_freeBlocks) * m_blockSize; } + size_t GetTotalFreed() const override { return m_freeBlocks * m_blockSize; } + size_t GetPeakAllocated() const override { return m_totalBlocks * m_blockSize; } + size_t GetAllocationCount() const override { return m_totalBlocks - m_freeBlocks; } + +private: + struct FreeNode { + FreeNode* next; + }; + + size_t m_blockSize = 0; + size_t m_alignment = 0; + void* m_memory = nullptr; + FreeNode* m_freeList = nullptr; + size_t m_totalBlocks = 0; + size_t m_freeBlocks = 0; +}; + +} // namespace Memory +} // namespace XCEngine diff --git a/engine/include/XCEngine/Memory/ProxyAllocator.h b/engine/include/XCEngine/Memory/ProxyAllocator.h new file mode 100644 index 00000000..0e2cdcbe --- /dev/null +++ b/engine/include/XCEngine/Memory/ProxyAllocator.h @@ -0,0 +1,41 @@ +#pragma once + +#include "Allocator.h" +#include "Threading/Mutex.h" + +namespace XCEngine { +namespace Memory { + +class ProxyAllocator : public IAllocator { +public: + ProxyAllocator(IAllocator* underlying, const char* name); + + void* Allocate(size_t size, size_t alignment = 0) override; + void Free(void* ptr) override; + void* Reallocate(void* ptr, size_t newSize) override; + + size_t GetTotalAllocated() const override { return m_stats.totalAllocated; } + size_t GetTotalFreed() const override { return m_stats.totalFreed; } + size_t GetPeakAllocated() const override { return m_stats.peakAllocated; } + size_t GetAllocationCount() const override { return m_stats.allocationCount; } + + struct Stats { + size_t totalAllocated = 0; + size_t totalFreed = 0; + size_t peakAllocated = 0; + size_t allocationCount = 0; + size_t memoryOverhead = 0; + }; + const Stats& GetStats() const; + + const char* GetName() const override { return m_name; } + +private: + IAllocator* m_underlying = nullptr; + const char* m_name = nullptr; + Stats m_stats; + Threading::Mutex m_mutex; +}; + +} // namespace Memory +} // namespace XCEngine diff --git a/engine/include/XCEngine/Threading/LambdaTask.h b/engine/include/XCEngine/Threading/LambdaTask.h new file mode 100644 index 00000000..6a48de25 --- /dev/null +++ b/engine/include/XCEngine/Threading/LambdaTask.h @@ -0,0 +1,23 @@ +#pragma once + +#include "Task.h" + +namespace XCEngine { +namespace Threading { + +template +class LambdaTask : public ITask { +public: + explicit LambdaTask(Func&& func, TaskPriority priority = TaskPriority::Normal) + : ITask(priority), m_func(std::move(func)) {} + + void Execute() override { + m_func(); + } + +private: + Func m_func; +}; + +} // namespace Threading +} // namespace XCEngine diff --git a/engine/include/XCEngine/Threading/Mutex.h b/engine/include/XCEngine/Threading/Mutex.h new file mode 100644 index 00000000..0d7fb10f --- /dev/null +++ b/engine/include/XCEngine/Threading/Mutex.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +namespace XCEngine { +namespace Threading { + +class Mutex { +public: + Mutex() = default; + ~Mutex() = default; + + void Lock() { m_mutex.lock(); } + void Unlock() { m_mutex.unlock(); } + bool TryLock() { return m_mutex.try_lock(); } + + void lock() { Lock(); } + void unlock() { Unlock(); } + bool try_lock() { return TryLock(); } + +private: + std::mutex m_mutex; +}; + +} // namespace Threading +} // namespace XCEngine diff --git a/engine/include/XCEngine/Threading/ReadWriteLock.h b/engine/include/XCEngine/Threading/ReadWriteLock.h new file mode 100644 index 00000000..15e70b99 --- /dev/null +++ b/engine/include/XCEngine/Threading/ReadWriteLock.h @@ -0,0 +1,53 @@ +#pragma once + +#include +#include + +namespace XCEngine { +namespace Threading { + +class ReadWriteLock { +public: + ReadWriteLock() = default; + ~ReadWriteLock() = default; + + void ReadLock() { + std::unique_lock lock(m_mutex); + m_readCondition.wait(lock, [this] { return !m_writerActive && m_writersWaiting == 0; }); + ++m_readers; + } + + void ReadUnlock() { + std::unique_lock lock(m_mutex); + --m_readers; + if (m_readers == 0) { + m_writeCondition.notify_all(); + } + } + + void WriteLock() { + std::unique_lock lock(m_mutex); + ++m_writersWaiting; + m_writeCondition.wait(lock, [this] { return m_readers == 0 && !m_writerActive; }); + --m_writersWaiting; + m_writerActive = true; + } + + void WriteUnlock() { + std::unique_lock lock(m_mutex); + m_writerActive = false; + m_readCondition.notify_all(); + m_writeCondition.notify_one(); + } + +private: + std::mutex m_mutex; + std::condition_variable m_readCondition; + std::condition_variable m_writeCondition; + int32_t m_readers = 0; + int32_t m_writersWaiting = 0; + bool m_writerActive = false; +}; + +} // namespace Threading +} // namespace XCEngine diff --git a/engine/include/XCEngine/Threading/SpinLock.h b/engine/include/XCEngine/Threading/SpinLock.h new file mode 100644 index 00000000..8899edc3 --- /dev/null +++ b/engine/include/XCEngine/Threading/SpinLock.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +namespace XCEngine { +namespace Threading { + +class SpinLock { +public: + void Lock() { + while (m_flag.test_and_set(std::memory_order_acquire)) { + } + } + + void Unlock() { + m_flag.clear(std::memory_order_release); + } + + bool TryLock() { + return !m_flag.test_and_set(std::memory_order_acquire); + } + + void lock() { Lock(); } + void unlock() { Unlock(); } + bool try_lock() { return TryLock(); } + +private: + std::atomic_flag m_flag = ATOMIC_FLAG_INIT; +}; + +} // namespace Threading +} // namespace XCEngine diff --git a/engine/include/XCEngine/Threading/Task.h b/engine/include/XCEngine/Threading/Task.h new file mode 100644 index 00000000..28410ac7 --- /dev/null +++ b/engine/include/XCEngine/Threading/Task.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include + +namespace XCEngine { +namespace Threading { + +enum class TaskPriority : uint8_t { + Critical = 0, + High = 1, + Normal = 2, + Low = 3, + Idle = 4 +}; + +enum class TaskStatus : uint8_t { + Pending, + Scheduled, + Running, + Completed, + Failed, + Canceled +}; + +class ITask { +public: + virtual ~ITask() = default; + + virtual void Execute() = 0; + virtual void OnComplete() {} + virtual void OnCancel() {} + + TaskPriority GetPriority() const { return m_priority; } + TaskStatus GetStatus() const { return m_status.load(); } + uint64_t GetId() const { return m_id; } + void SetId(uint64_t id) { m_id = id; } + + void SetPriority(TaskPriority priority) { m_priority = priority; } + void SetStatus(TaskStatus status) { m_status = status; } + + void AddRef() { m_refCount.fetch_add(1); } + void Release() { + if (m_refCount.fetch_sub(1) == 1) { + delete this; + } + } + +protected: + ITask() = default; + explicit ITask(TaskPriority priority) : m_priority(priority) {} + + TaskPriority m_priority = TaskPriority::Normal; + std::atomic m_status{TaskStatus::Pending}; + uint64_t m_id = 0; + std::atomic m_refCount{1}; +}; + +} // namespace Threading +} // namespace XCEngine diff --git a/engine/include/XCEngine/Threading/TaskGroup.h b/engine/include/XCEngine/Threading/TaskGroup.h new file mode 100644 index 00000000..a3adc89a --- /dev/null +++ b/engine/include/XCEngine/Threading/TaskGroup.h @@ -0,0 +1,51 @@ +#pragma once + +#include "Task.h" +#include +#include +#include +#include +#include + +namespace XCEngine { +namespace Threading { + +class TaskGroup { +public: + using Callback = std::function; + + TaskGroup(); + ~TaskGroup(); + + uint64_t AddTask(std::unique_ptr task); + uint64_t AddTask(Callback&& func, TaskPriority priority = TaskPriority::Normal); + + void AddDependency(uint64_t taskId, uint64_t dependsOn); + void Wait(); + bool WaitFor(std::chrono::milliseconds timeout); + + void SetCompleteCallback(Callback&& callback); + bool IsComplete() const; + float GetProgress() const; + + void Cancel(); + +private: + struct TaskNode { + ITask* task = nullptr; + std::vector dependencies; + int pendingDepCount = 0; + bool completed = false; + }; + + std::vector m_tasks; + std::atomic m_pendingCount{0}; + std::atomic m_completedCount{0}; + Callback m_completeCallback; + mutable std::mutex m_mutex; + std::condition_variable m_condition; + std::atomic m_canceled{false}; +}; + +} // namespace Threading +} // namespace XCEngine diff --git a/engine/include/XCEngine/Threading/TaskSystem.h b/engine/include/XCEngine/Threading/TaskSystem.h new file mode 100644 index 00000000..40cc9c2e --- /dev/null +++ b/engine/include/XCEngine/Threading/TaskSystem.h @@ -0,0 +1,108 @@ +#pragma once + +#include "TaskSystemConfig.h" +#include "Task.h" +#include "TaskGroup.h" +#include "Mutex.h" +#include "SpinLock.h" +#include +#include +#include +#include +#include +#include + +namespace XCEngine { +namespace Threading { + +class TaskSystem { +public: + static TaskSystem& Get(); + + void Initialize(const TaskSystemConfig& config); + void Shutdown(); + + uint64_t Submit(std::unique_ptr task); + uint64_t Submit(std::function&& func, TaskPriority priority = TaskPriority::Normal); + + TaskGroup* CreateTaskGroup(); + void DestroyTaskGroup(TaskGroup* group); + + void Wait(uint64_t taskId); + uint32_t GetWorkerThreadCount() const; + + void Update(); + + template + void ParallelFor(int32_t start, int32_t end, Func&& func); + + void RunOnMainThread(std::function&& func); + +private: + TaskSystem() = default; + ~TaskSystem() = default; + + struct TaskWrapper { + ITask* task; + TaskPriority priority; + uint64_t id; + + bool operator<(const TaskWrapper& other) const { + return priority > other.priority; + } + }; + + void WorkerThread(); + bool GetNextTask(TaskWrapper& outTask); + void ExecuteTask(TaskWrapper& task); + + std::vector m_workerThreads; + std::priority_queue m_taskQueue; + + std::vector m_taskGroups; + std::vector> m_mainThreadQueue; + + Mutex m_queueMutex; + std::mutex m_conditionMutex; + SpinLock m_groupMutex; + std::condition_variable m_taskAvailable; + std::condition_variable m_mainThreadCondition; + + std::atomic m_running{false}; + std::atomic m_nextTaskId{0}; + uint32_t m_workerThreadCount = 0; + bool m_shutdown = false; +}; + +template +void TaskSystem::ParallelFor(int32_t start, int32_t end, Func&& func) { + int32_t count = end - start; + if (count <= 0) return; + + uint32_t numThreads = std::thread::hardware_concurrency(); + if (numThreads == 0) numThreads = 2; + + int32_t chunkSize = (count + numThreads - 1) / numThreads; + if (chunkSize < 1) chunkSize = 1; + + auto parallelTask = [&func, start, chunkSize, count](int32_t threadIndex) { + int32_t begin = start + threadIndex * chunkSize; + int32_t endIndex = std::min(begin + chunkSize, start + count); + for (int32_t i = begin; i < endIndex; ++i) { + func(i); + } + }; + + std::vector> tasks; + tasks.reserve(numThreads); + for (uint32_t i = 0; i < numThreads; ++i) { + tasks.emplace_back([=]() { parallelTask(i); }); + } + + for (auto& task : tasks) { + Submit(std::make_unique>>(std::move(task), TaskPriority::High)); + } +} + +} // namespace Threading +} // namespace XCEngine diff --git a/engine/include/XCEngine/Threading/TaskSystemConfig.h b/engine/include/XCEngine/Threading/TaskSystemConfig.h new file mode 100644 index 00000000..403d66a5 --- /dev/null +++ b/engine/include/XCEngine/Threading/TaskSystemConfig.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +namespace XCEngine { +namespace Threading { + +struct TaskSystemConfig { + uint32_t workerThreadCount = 0; + bool enableTaskProfiling = true; + bool stealTasks = true; + uint32_t maxTaskQueueSize = 1024; + uint32_t threadStackSize = 0; +}; + +} // namespace Threading +} // namespace XCEngine diff --git a/engine/include/XCEngine/Threading/Thread.h b/engine/include/XCEngine/Threading/Thread.h new file mode 100644 index 00000000..19531d0a --- /dev/null +++ b/engine/include/XCEngine/Threading/Thread.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include +#include "Containers/String.h" + +namespace XCEngine { +namespace Threading { + +class Thread { +public: + using Id = uint64_t; + + Thread(); + ~Thread(); + + template + void Start(Func&& func, const Containers::String& name = "Thread"); + void Join(); + void Detach(); + + Id GetId() const { return m_id; } + const Containers::String& GetName() const { return m_name; } + + static Id GetCurrentId(); + static void Sleep(uint32_t milliseconds); + static void Yield(); + +private: + Id m_id = 0; + Containers::String m_name; + std::thread m_thread; +}; + +template +void Thread::Start(Func&& func, const Containers::String& name) { + m_name = name; + m_thread = std::thread(std::forward(func)); + m_id = static_cast(reinterpret_cast(m_thread.native_handle())); +} + +} // namespace Threading +} // namespace XCEngine diff --git a/engine/include/XCEngine/Threading/Threading.h b/engine/include/XCEngine/Threading/Threading.h new file mode 100644 index 00000000..3d5fe530 --- /dev/null +++ b/engine/include/XCEngine/Threading/Threading.h @@ -0,0 +1,17 @@ +#pragma once + +#include "Threading/Mutex.h" +#include "Threading/SpinLock.h" +#include "Threading/ReadWriteLock.h" +#include "Threading/Thread.h" +#include "Threading/Task.h" +#include "Threading/LambdaTask.h" +#include "Threading/TaskGroup.h" +#include "Threading/TaskSystemConfig.h" +#include "Threading/TaskSystem.h" + +namespace XCEngine { +namespace Threading { + +} // namespace Threading +} // namespace XCEngine diff --git a/engine/src/Containers/String.cpp b/engine/src/Containers/String.cpp new file mode 100644 index 00000000..69291e9d --- /dev/null +++ b/engine/src/Containers/String.cpp @@ -0,0 +1,327 @@ +#include "Containers/String.h" +#include +#include + +namespace XCEngine { +namespace Containers { + +String::String() : m_data(nullptr), m_length(0), m_capacity(0) { + m_data = new char[1]; + m_data[0] = '\0'; + m_capacity = 1; +} + +String::String(const char* str) { + if (str) { + m_length = std::strlen(str); + m_capacity = m_length + 1; + m_data = new char[m_capacity]; + std::memcpy(m_data, str, m_length + 1); + } else { + m_data = new char[1]; + m_data[0] = '\0'; + m_length = 0; + m_capacity = 1; + } +} + +String::String(const char* str, SizeType len) { + if (str && len > 0) { + m_length = len; + m_capacity = m_length + 1; + m_data = new char[m_capacity]; + std::memcpy(m_data, str, m_length); + m_data[m_length] = '\0'; + } else { + m_data = new char[1]; + m_data[0] = '\0'; + m_length = 0; + m_capacity = 1; + } +} + +String::String(const String& other) { + m_length = other.m_length; + m_capacity = m_length + 1; + m_data = new char[m_capacity]; + std::memcpy(m_data, other.m_data, m_length + 1); +} + +String::String(String&& other) noexcept { + m_data = other.m_data; + m_length = other.m_length; + m_capacity = other.m_capacity; + + other.m_data = nullptr; + other.m_length = 0; + other.m_capacity = 0; +} + +String::~String() { + Deallocate(); +} + +String& String::operator=(const String& other) { + if (this != &other) { + if (other.m_length + 1 > m_capacity) { + Deallocate(); + Allocate(other.m_length + 1); + } + m_length = other.m_length; + std::memcpy(m_data, other.m_data, m_length + 1); + } + return *this; +} + +String& String::operator=(String&& other) noexcept { + if (this != &other) { + Deallocate(); + m_data = other.m_data; + m_length = other.m_length; + m_capacity = other.m_capacity; + + other.m_data = nullptr; + other.m_length = 0; + other.m_capacity = 0; + } + return *this; +} + +String& String::operator=(const char* str) { + if (str) { + SizeType len = std::strlen(str); + if (len + 1 > m_capacity) { + Deallocate(); + Allocate(len + 1); + } + m_length = len; + std::memcpy(m_data, str, m_length + 1); + } else { + Clear(); + } + return *this; +} + +String& String::operator+=(const String& other) { + if (other.m_length > 0) { + SizeType newLength = m_length + other.m_length; + if (newLength + 1 > m_capacity) { + char* newData = new char[newLength + 1]; + std::memcpy(newData, m_data, m_length); + delete[] m_data; + m_data = newData; + m_capacity = newLength + 1; + } + std::memcpy(m_data + m_length, other.m_data, other.m_length + 1); + m_length = newLength; + } + return *this; +} + +String& String::operator+=(const char* str) { + if (str) { + SizeType len = std::strlen(str); + if (len > 0) { + SizeType newLength = m_length + len; + if (newLength + 1 > m_capacity) { + char* newData = new char[newLength + 1]; + std::memcpy(newData, m_data, m_length); + delete[] m_data; + m_data = newData; + m_capacity = newLength + 1; + } + std::memcpy(m_data + m_length, str, len + 1); + m_length = newLength; + } + } + return *this; +} + +String& String::operator+=(char c) { + SizeType newLength = m_length + 1; + if (newLength + 1 > m_capacity) { + SizeType newCapacity = newLength * 2; + char* newData = new char[newCapacity]; + std::memcpy(newData, m_data, m_length); + delete[] m_data; + m_data = newData; + m_capacity = newCapacity; + } + m_data[m_length] = c; + m_data[newLength] = '\0'; + m_length = newLength; + return *this; +} + +String String::Substring(SizeType pos, SizeType len) const { + if (pos >= m_length) { + return String(); + } + SizeType actualLen = (len == npos || pos + len > m_length) ? (m_length - pos) : len; + return String(m_data + pos, actualLen); +} + +String String::Trim() const { + if (m_length == 0) { + return String(); + } + + SizeType start = 0; + SizeType end = m_length - 1; + + while (start <= end && (m_data[start] == ' ' || m_data[start] == '\t' || + m_data[start] == '\n' || m_data[start] == '\r')) { + start++; + } + + while (end >= start && (m_data[end] == ' ' || m_data[end] == '\t' || + m_data[end] == '\n' || m_data[end] == '\r')) { + end--; + } + + if (start > end) { + return String(); + } + + return String(m_data + start, end - start + 1); +} + +String String::ToLower() const { + String result(*this); + for (SizeType i = 0; i < result.m_length; i++) { + if (result.m_data[i] >= 'A' && result.m_data[i] <= 'Z') { + result.m_data[i] = result.m_data[i] + 32; + } + } + return result; +} + +String String::ToUpper() const { + String result(*this); + for (SizeType i = 0; i < result.m_length; i++) { + if (result.m_data[i] >= 'a' && result.m_data[i] <= 'z') { + result.m_data[i] = result.m_data[i] - 32; + } + } + return result; +} + +String::SizeType String::Find(const char* str, String::SizeType pos) const { + if (!str || m_length == 0) { + return npos; + } + + SizeType len = std::strlen(str); + if (len == 0 || pos >= m_length) { + return npos; + } + + for (SizeType i = pos; i <= m_length - len; i++) { + if (std::memcmp(m_data + i, str, len) == 0) { + return i; + } + } + return npos; +} + +bool String::StartsWith(const String& prefix) const { + return StartsWith(prefix.CStr()); +} + +bool String::StartsWith(const char* prefix) const { + if (!prefix) { + return false; + } + SizeType len = std::strlen(prefix); + if (len > m_length) { + return false; + } + return std::memcmp(m_data, prefix, len) == 0; +} + +bool String::EndsWith(const String& suffix) const { + return EndsWith(suffix.CStr()); +} + +bool String::EndsWith(const char* suffix) const { + if (!suffix) { + return false; + } + SizeType len = std::strlen(suffix); + if (len > m_length) { + return false; + } + return std::memcmp(m_data + m_length - len, suffix, len) == 0; +} + +void String::Clear() { + if (m_data) { + m_data[0] = '\0'; + } + m_length = 0; +} + +void String::Reserve(SizeType capacity) { + if (capacity > m_capacity) { + char* newData = new char[capacity]; + std::memcpy(newData, m_data, m_length + 1); + delete[] m_data; + m_data = newData; + m_capacity = capacity; + } +} + +void String::Resize(SizeType newSize) { + Resize(newSize, '\0'); +} + +void String::Resize(SizeType newSize, char fillChar) { + if (newSize + 1 > m_capacity) { + Reserve(newSize + 1); + } + if (newSize > m_length) { + for (SizeType i = m_length; i < newSize; i++) { + m_data[i] = fillChar; + } + } + m_data[newSize] = '\0'; + m_length = newSize; +} + +void String::Allocate(SizeType capacity) { + m_capacity = capacity; + m_data = new char[m_capacity]; + m_data[0] = '\0'; +} + +void String::Deallocate() { + if (m_data) { + delete[] m_data; + m_data = nullptr; + } + m_length = 0; + m_capacity = 0; +} + +void String::CopyFrom(const char* str, SizeType len) { + if (len + 1 > m_capacity) { + Deallocate(); + Allocate(len + 1); + } + m_length = len; + std::memcpy(m_data, str, len); + m_data[len] = '\0'; +} + +void String::MoveFrom(String&& other) noexcept { + m_data = other.m_data; + m_length = other.m_length; + m_capacity = other.m_capacity; + + other.m_data = nullptr; + other.m_length = 0; + other.m_capacity = 0; +} + +} // namespace Containers +} // namespace XCEngine diff --git a/engine/src/Memory/Memory.cpp b/engine/src/Memory/Memory.cpp new file mode 100644 index 00000000..d5ae93db --- /dev/null +++ b/engine/src/Memory/Memory.cpp @@ -0,0 +1,272 @@ +#include "Memory/LinearAllocator.h" +#include "Memory/PoolAllocator.h" +#include "Memory/ProxyAllocator.h" +#include "Memory/MemoryManager.h" +#include "Threading/Mutex.h" + +#include +#include +#include + +namespace XCEngine { +namespace Memory { + +class SystemAllocator : public IAllocator { +public: + void* Allocate(size_t size, size_t alignment) override { + if (alignment == 0) { + return std::malloc(size); + } +#ifdef _WIN32 + return _aligned_malloc(size, alignment); +#else + return std::aligned_alloc(alignment, size); +#endif + } + + void Free(void* ptr) override { +#ifdef _WIN32 + _aligned_free(ptr); +#else + std::free(ptr); +#endif + } + + void* Reallocate(void* ptr, size_t newSize) override { + return std::realloc(ptr, newSize); + } + + size_t GetTotalAllocated() const override { return 0; } + size_t GetTotalFreed() const override { return 0; } + size_t GetPeakAllocated() const override { return 0; } + size_t GetAllocationCount() const override { return 0; } + + const char* GetName() const override { return "SystemAllocator"; } +}; + +LinearAllocator::LinearAllocator(size_t size, IAllocator* parent) + : m_capacity(size), m_parent(parent) { + if (parent) { + m_buffer = static_cast(parent->Allocate(size, 8)); + } else { + m_buffer = static_cast(_aligned_malloc(size, 8)); + } +} + +LinearAllocator::~LinearAllocator() { + if (m_parent) { + m_parent->Free(m_buffer); + } else { +#ifdef _WIN32 + _aligned_free(m_buffer); +#else + std::free(m_buffer); +#endif + } +} + +void* LinearAllocator::Allocate(size_t size, size_t alignment) { + if (size == 0) { + return nullptr; + } + + uintptr_t address = reinterpret_cast(m_buffer) + m_offset; + + if (alignment > 0) { + size_t misalignment = address % alignment; + if (misalignment != 0) { + size += alignment - misalignment; + } + } + + if (m_offset + size > m_capacity) { + return nullptr; + } + + void* ptr = &m_buffer[m_offset]; + m_offset += size; + return ptr; +} + +void LinearAllocator::Free(void* ptr) { +} + +void* LinearAllocator::Reallocate(void* ptr, size_t newSize) { + return nullptr; +} + +void LinearAllocator::Clear() { + m_offset = 0; +} + +void* LinearAllocator::GetMarker() const { + return reinterpret_cast(m_offset); +} + +void LinearAllocator::SetMarker(void* marker) { + m_offset = reinterpret_cast(marker); +} + +PoolAllocator::PoolAllocator(size_t blockSize, size_t poolSize, size_t alignment) + : m_blockSize(blockSize), m_alignment(alignment), m_totalBlocks(poolSize), m_freeBlocks(poolSize) { + + size_t actualBlockSize = blockSize; + if (alignment > 0) { + actualBlockSize = (blockSize + alignment - 1) & ~(alignment - 1); + } + + m_memory = std::malloc(actualBlockSize * poolSize); + + uint8_t* memory = static_cast(m_memory); + m_freeList = reinterpret_cast(memory); + + FreeNode* current = m_freeList; + for (size_t i = 1; i < poolSize; ++i) { + uint8_t* block = memory + (i * actualBlockSize); + current->next = reinterpret_cast(block); + current = current->next; + } + current->next = nullptr; +} + +PoolAllocator::~PoolAllocator() { + std::free(m_memory); +} + +void* PoolAllocator::Allocate(size_t size, size_t alignment) { + if (!m_freeList) { + return nullptr; + } + + if (size > m_blockSize) { + return nullptr; + } + + FreeNode* node = m_freeList; + m_freeList = m_freeList->next; + --m_freeBlocks; + + return node; +} + +void PoolAllocator::Free(void* ptr) { + if (!ptr) { + return; + } + + FreeNode* node = static_cast(ptr); + node->next = m_freeList; + m_freeList = node; + ++m_freeBlocks; +} + +void* PoolAllocator::Reallocate(void* ptr, size_t newSize) { + return nullptr; +} + +bool PoolAllocator::Contains(void* ptr) const { + if (!ptr || !m_memory) { + return false; + } + uint8_t* memory = static_cast(m_memory); + uint8_t* p = static_cast(ptr); + uintptr_t offset = p - memory; + size_t blockSize = m_blockSize; + if (m_alignment > 0) { + blockSize = (m_blockSize + m_alignment - 1) & ~(m_alignment - 1); + } + return offset < blockSize * m_totalBlocks; +} + +size_t PoolAllocator::GetFreeBlockCount() const { + return m_freeBlocks; +} + +ProxyAllocator::ProxyAllocator(IAllocator* underlying, const char* name) + : m_underlying(underlying), m_name(name) { +} + +void* ProxyAllocator::Allocate(size_t size, size_t alignment) { + std::lock_guard lock(m_mutex); + void* ptr = m_underlying->Allocate(size, alignment); + if (ptr) { + m_stats.totalAllocated += size; + m_stats.allocationCount++; + if (m_stats.totalAllocated - m_stats.totalFreed > m_stats.peakAllocated) { + m_stats.peakAllocated = m_stats.totalAllocated - m_stats.totalFreed; + } + } + return ptr; +} + +void ProxyAllocator::Free(void* ptr) { + std::lock_guard lock(m_mutex); + m_underlying->Free(ptr); + m_stats.totalFreed += m_stats.allocationCount; + m_stats.allocationCount--; +} + +void* ProxyAllocator::Reallocate(void* ptr, size_t newSize) { + std::lock_guard lock(m_mutex); + void* newPtr = m_underlying->Reallocate(ptr, newSize); + return newPtr; +} + +const ProxyAllocator::Stats& ProxyAllocator::GetStats() const { + return m_stats; +} + +MemoryManager& MemoryManager::Get() { + static MemoryManager instance; + return instance; +} + +void MemoryManager::Initialize() { + if (m_initialized) { + return; + } + + m_systemAllocator = new SystemAllocator(); + m_initialized = true; +} + +void MemoryManager::Shutdown() { + if (!m_initialized) { + return; + } + + delete static_cast(m_systemAllocator); + m_systemAllocator = nullptr; + m_initialized = false; +} + +IAllocator* MemoryManager::GetSystemAllocator() { + return m_systemAllocator; +} + +std::unique_ptr MemoryManager::CreateLinearAllocator(size_t size) { + return std::make_unique(size, m_systemAllocator); +} + +std::unique_ptr MemoryManager::CreatePoolAllocator(size_t blockSize, size_t count) { + return std::make_unique(blockSize, count); +} + +std::unique_ptr MemoryManager::CreateProxyAllocator(const char* name) { + return std::make_unique(m_systemAllocator, name); +} + +void MemoryManager::SetTrackAllocations(bool track) { + m_trackAllocations = track; +} + +void MemoryManager::DumpMemoryLeaks() { + std::cout << "Memory Leak Report:" << std::endl; +} + +void MemoryManager::GenerateMemoryReport() { + std::cout << "Memory Report:" << std::endl; +} + +} // namespace Memory +} // namespace XCEngine diff --git a/engine/src/Threading/TaskGroup.cpp b/engine/src/Threading/TaskGroup.cpp new file mode 100644 index 00000000..2a141b5f --- /dev/null +++ b/engine/src/Threading/TaskGroup.cpp @@ -0,0 +1,74 @@ +#include "Threading/TaskSystem.h" +#include "Threading/LambdaTask.h" + +namespace XCEngine { +namespace Threading { + +TaskGroup::TaskGroup() = default; + +TaskGroup::~TaskGroup() = default; + +uint64_t TaskGroup::AddTask(std::unique_ptr task) { + std::lock_guard lock(m_mutex); + + TaskNode node; + node.task = task.get(); + node.pendingDepCount = static_cast(node.dependencies.size()); + + uint64_t taskId = m_tasks.size(); + m_tasks.emplace_back(std::move(node)); + m_pendingCount++; + + task.release(); + return taskId; +} + +uint64_t TaskGroup::AddTask(Callback&& func, TaskPriority priority) { + auto task = std::make_unique>(std::move(func), priority); + return AddTask(std::move(task)); +} + +void TaskGroup::AddDependency(uint64_t taskId, uint64_t dependsOn) { + std::lock_guard lock(m_mutex); + if (taskId < m_tasks.size() && dependsOn < m_tasks.size()) { + m_tasks[taskId].dependencies.push_back(dependsOn); + } +} + +void TaskGroup::Wait() { + std::unique_lock lock(m_mutex); + m_condition.wait(lock, [this] { return m_pendingCount.load() == 0; }); +} + +bool TaskGroup::WaitFor(std::chrono::milliseconds timeout) { + std::unique_lock lock(m_mutex); + return m_condition.wait_for(lock, timeout, [this] { return m_pendingCount.load() == 0; }); +} + +void TaskGroup::SetCompleteCallback(Callback&& callback) { + std::lock_guard lock(m_mutex); + m_completeCallback = std::move(callback); +} + +bool TaskGroup::IsComplete() const { + return m_pendingCount.load() == 0; +} + +float TaskGroup::GetProgress() const { + int total = m_tasks.size(); + if (total == 0) return 1.0f; + return static_cast(m_completedCount.load()) / static_cast(total); +} + +void TaskGroup::Cancel() { + std::lock_guard lock(m_mutex); + m_canceled = true; + for (auto& node : m_tasks) { + if (node.task && !node.completed) { + node.task->OnCancel(); + } + } +} + +} // namespace Threading +} // namespace XCEngine diff --git a/engine/src/Threading/TaskSystem.cpp b/engine/src/Threading/TaskSystem.cpp new file mode 100644 index 00000000..ec2d830e --- /dev/null +++ b/engine/src/Threading/TaskSystem.cpp @@ -0,0 +1,162 @@ +#include "Threading/TaskSystem.h" +#include "Threading/LambdaTask.h" +#include + +namespace XCEngine { +namespace Threading { + +TaskSystem& TaskSystem::Get() { + static TaskSystem instance; + return instance; +} + +void TaskSystem::Initialize(const TaskSystemConfig& config) { + m_workerThreadCount = config.workerThreadCount > 0 + ? config.workerThreadCount + : std::thread::hardware_concurrency(); + + if (m_workerThreadCount == 0) { + m_workerThreadCount = 2; + } + + m_running = true; + + for (uint32_t i = 0; i < m_workerThreadCount; ++i) { + m_workerThreads.emplace_back([this]() { WorkerThread(); }); + } +} + +void TaskSystem::Shutdown() { + m_running = false; + m_shutdown = true; + m_taskAvailable.notify_all(); + + for (auto& thread : m_workerThreads) { + if (thread.joinable()) { + thread.join(); + } + } + + m_workerThreads.clear(); +} + +uint64_t TaskSystem::Submit(std::unique_ptr task) { + if (!task) return 0; + + uint64_t taskId = ++m_nextTaskId; + task->SetId(taskId); + + TaskWrapper wrapper; + wrapper.task = task.get(); + wrapper.priority = task->GetPriority(); + wrapper.id = taskId; + + { + std::lock_guard lock(m_queueMutex); + m_taskQueue.push(wrapper); + } + + m_taskAvailable.notify_one(); + return taskId; +} + +uint64_t TaskSystem::Submit(std::function&& func, TaskPriority priority) { + ITask* task = new LambdaTask>(std::move(func), priority); + return Submit(std::unique_ptr(task)); +} + +TaskGroup* TaskSystem::CreateTaskGroup() { + TaskGroup* group = new TaskGroup(); + std::lock_guard lock(m_groupMutex); + m_taskGroups.push_back(group); + return group; +} + +void TaskSystem::DestroyTaskGroup(TaskGroup* group) { + if (!group) return; + + { + std::lock_guard lock(m_groupMutex); + auto it = std::find(m_taskGroups.begin(), m_taskGroups.end(), group); + if (it != m_taskGroups.end()) { + m_taskGroups.erase(it); + } + } + + delete group; +} + +void TaskSystem::Wait(uint64_t taskId) { +} + +uint32_t TaskSystem::GetWorkerThreadCount() const { + return m_workerThreadCount; +} + +void TaskSystem::Update() { + std::vector> tasks; + { + std::lock_guard lock(m_queueMutex); + tasks = std::move(m_mainThreadQueue); + m_mainThreadQueue.clear(); + } + + for (auto& task : tasks) { + task(); + } +} + +void TaskSystem::RunOnMainThread(std::function&& func) { + { + std::lock_guard lock(m_queueMutex); + m_mainThreadQueue.push_back(std::move(func)); + } +} + +void TaskSystem::WorkerThread() { + while (m_running) { + TaskWrapper taskWrapper; + if (GetNextTask(taskWrapper)) { + ExecuteTask(taskWrapper); + } + } +} + +bool TaskSystem::GetNextTask(TaskWrapper& outTask) { + std::unique_lock lock(m_conditionMutex); + + m_taskAvailable.wait(lock, [this] { + return !m_taskQueue.empty() || !m_running || m_shutdown; + }); + + if (m_shutdown) { + return false; + } + + if (!m_taskQueue.empty()) { + outTask = m_taskQueue.top(); + m_taskQueue.pop(); + return true; + } + + return false; +} + +void TaskSystem::ExecuteTask(TaskWrapper& taskWrapper) { + if (!taskWrapper.task) return; + + taskWrapper.task->SetStatus(TaskStatus::Running); + + try { + taskWrapper.task->Execute(); + taskWrapper.task->SetStatus(TaskStatus::Completed); + taskWrapper.task->OnComplete(); + } catch (...) { + taskWrapper.task->SetStatus(TaskStatus::Failed); + } + + taskWrapper.task->Release(); +} + +} // namespace Threading +} // namespace XCEngine diff --git a/engine/src/Threading/Thread.cpp b/engine/src/Threading/Thread.cpp new file mode 100644 index 00000000..49ce6a38 --- /dev/null +++ b/engine/src/Threading/Thread.cpp @@ -0,0 +1,41 @@ +#include "Threading/Thread.h" +#include + +namespace XCEngine { +namespace Threading { + +Thread::Thread() = default; + +Thread::~Thread() { + if (m_thread.joinable()) { + m_thread.join(); + } +} + +void Thread::Join() { + if (m_thread.joinable()) { + m_thread.join(); + } +} + +void Thread::Detach() { + if (m_thread.joinable()) { + m_thread.detach(); + } +} + +Thread::Id Thread::GetCurrentId() { + auto threadId = std::this_thread::get_id(); + return static_cast(std::hash{}(threadId)); +} + +void Thread::Sleep(uint32_t milliseconds) { + std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds)); +} + +void Thread::Yield() { + std::this_thread::yield(); +} + +} // namespace Threading +} // namespace XCEngine diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 0c5528b0..4d9a5197 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -38,6 +38,10 @@ add_subdirectory(../engine engine) # ============================================================ add_subdirectory(math) +add_subdirectory(core) +add_subdirectory(containers) +add_subdirectory(memory) +add_subdirectory(threading) # ============================================================ # Test Summary diff --git a/tests/containers/CMakeLists.txt b/tests/containers/CMakeLists.txt new file mode 100644 index 00000000..23f2c783 --- /dev/null +++ b/tests/containers/CMakeLists.txt @@ -0,0 +1,31 @@ +# ============================================================ +# Containers Library Tests +# ============================================================ + +set(CONTAINERS_TEST_SOURCES + test_string.cpp + test_array.cpp + test_hashmap.cpp +) + +add_executable(xcengine_containers_tests ${CONTAINERS_TEST_SOURCES}) + +if(MSVC) + set_target_properties(xcengine_containers_tests PROPERTIES + LINK_FLAGS "/NODEFAULTLIB:libcpmt.lib /NODEFAULTLIB:libcmt.lib" + ) +endif() + +target_link_libraries(xcengine_containers_tests + PRIVATE + XCEngine + GTest::gtest + GTest::gtest_main +) + +target_include_directories(xcengine_containers_tests PRIVATE + ${CMAKE_SOURCE_DIR}/engine/include + ${CMAKE_SOURCE_DIR}/tests/fixtures +) + +add_test(NAME ContainersTests COMMAND xcengine_containers_tests) diff --git a/tests/containers/test_array.cpp b/tests/containers/test_array.cpp new file mode 100644 index 00000000..6635c933 --- /dev/null +++ b/tests/containers/test_array.cpp @@ -0,0 +1,162 @@ +#include +#include + +using namespace XCEngine::Containers; + +namespace { + +TEST(Containers_Array, DefaultConstructor) { + Array arr; + EXPECT_EQ(arr.Size(), 0u); + EXPECT_EQ(arr.Capacity(), 0u); + EXPECT_TRUE(arr.Empty()); +} + +TEST(Containers_Array, CapacityConstructor) { + Array arr(10); + EXPECT_EQ(arr.Size(), 0u); + EXPECT_GE(arr.Capacity(), 10u); +} + +TEST(Containers_Array, CountValueConstructor) { + Array arr(5, 42); + EXPECT_EQ(arr.Size(), 5u); + for (size_t i = 0; i < 5; ++i) { + EXPECT_EQ(arr[i], 42); + } +} + +TEST(Containers_Array, InitializerListConstructor) { + Array arr = {1, 2, 3, 4, 5}; + EXPECT_EQ(arr.Size(), 5u); + EXPECT_EQ(arr[0], 1); + EXPECT_EQ(arr[4], 5); +} + +TEST(Containers_Array, CopyConstructor) { + Array arr1 = {1, 2, 3}; + Array arr2(arr1); + EXPECT_EQ(arr2.Size(), 3u); + EXPECT_EQ(arr2[0], 1); + EXPECT_EQ(arr2[2], 3); +} + +TEST(Containers_Array, MoveConstructor) { + Array arr1 = {1, 2, 3}; + Array arr2(std::move(arr1)); + EXPECT_EQ(arr2.Size(), 3u); + EXPECT_EQ(arr2[0], 1); +} + +TEST(Containers_Array, CopyAssignment) { + Array arr1 = {1, 2, 3}; + Array arr2; + arr2 = arr1; + EXPECT_EQ(arr2.Size(), 3u); + EXPECT_EQ(arr2[0], 1); +} + +TEST(Containers_Array, MoveAssignment) { + Array arr1 = {1, 2, 3}; + Array arr2; + arr2 = std::move(arr1); + EXPECT_EQ(arr2.Size(), 3u); + EXPECT_EQ(arr2[0], 1); +} + +TEST(Containers_Array, OperatorSquareBrackets) { + Array arr = {10, 20, 30}; + EXPECT_EQ(arr[0], 10); + EXPECT_EQ(arr[1], 20); + EXPECT_EQ(arr[2], 30); + + arr[1] = 25; + EXPECT_EQ(arr[1], 25); +} + +TEST(Containers_Array, PushBack) { + Array arr; + arr.PushBack(1); + arr.PushBack(2); + arr.PushBack(3); + EXPECT_EQ(arr.Size(), 3u); + EXPECT_EQ(arr[2], 3); +} + +TEST(Containers_Array, EmplaceBack) { + Array arr; + arr.EmplaceBack(1); + arr.EmplaceBack(2); + arr.EmplaceBack(3); + EXPECT_EQ(arr.Size(), 3u); +} + +TEST(Containers_Array, PopBack) { + Array arr = {1, 2, 3}; + arr.PopBack(); + EXPECT_EQ(arr.Size(), 2u); + EXPECT_EQ(arr[1], 2); +} + +TEST(Containers_Array, Clear) { + Array arr = {1, 2, 3}; + arr.Clear(); + EXPECT_EQ(arr.Size(), 0u); + EXPECT_TRUE(arr.Empty()); +} + +TEST(Containers_Array, Reserve) { + Array arr; + arr.Reserve(100); + EXPECT_GE(arr.Capacity(), 100u); +} + +TEST(Containers_Array, Resize) { + Array arr; + arr.Resize(5); + EXPECT_EQ(arr.Size(), 5u); + EXPECT_EQ(arr[0], 0); + EXPECT_EQ(arr[4], 0); + + arr.Resize(8, 42); + EXPECT_EQ(arr.Size(), 8u); + EXPECT_EQ(arr[0], 0); + EXPECT_EQ(arr[5], 42); + EXPECT_EQ(arr[7], 42); +} + +TEST(Containers_Array, Front) { + Array arr = {10, 20, 30}; + EXPECT_EQ(arr.Front(), 10); +} + +TEST(Containers_Array, Back) { + Array arr = {10, 20, 30}; + EXPECT_EQ(arr.Back(), 30); +} + +TEST(Containers_Array, Data) { + Array arr = {10, 20, 30}; + EXPECT_EQ(arr.Data()[0], 10); + EXPECT_EQ(arr.Data()[1], 20); +} + +TEST(Containers_Array, Iterator) { + Array arr = {1, 2, 3, 4, 5}; + int sum = 0; + for (auto it = arr.begin(); it != arr.end(); ++it) { + sum += *it; + } + EXPECT_EQ(sum, 15); +} + +TEST(Containers_Array, RangeBasedFor) { + Array arr = {1, 2, 3, 4, 5}; + int sum = 0; + for (int v : arr) { + sum += v; + } + EXPECT_EQ(sum, 15); +} + +} // namespace diff --git a/tests/containers/test_hashmap.cpp b/tests/containers/test_hashmap.cpp new file mode 100644 index 00000000..c6e542b5 --- /dev/null +++ b/tests/containers/test_hashmap.cpp @@ -0,0 +1,139 @@ +#include +#include +#include + +using namespace XCEngine::Containers; + +namespace { + +TEST(Containers_HashMap, DefaultConstructor) { + HashMap map; + EXPECT_EQ(map.Size(), 0u); + EXPECT_TRUE(map.Empty()); +} + +TEST(Containers_HashMap, Insert) { + HashMap map; + map.Insert(1, String("one")); + EXPECT_EQ(map.Size(), 1u); +} + +TEST(Containers_HashMap, InsertDuplicate) { + HashMap map; + map.Insert(1, 10); + map.Insert(1, 20); + EXPECT_EQ(map.Size(), 1u); + + auto* val = map.Find(1); + EXPECT_EQ(*val, 20); +} + +TEST(Containers_HashMap, Find) { + HashMap map; + map.Insert(1, 100); + map.Insert(2, 200); + + auto* val = map.Find(1); + ASSERT_NE(val, nullptr); + EXPECT_EQ(*val, 100); + + val = map.Find(2); + ASSERT_NE(val, nullptr); + EXPECT_EQ(*val, 200); + + val = map.Find(3); + EXPECT_EQ(val, nullptr); +} + +TEST(Containers_HashMap, Find_NotFound) { + HashMap map; + map.Insert(1, 100); + EXPECT_EQ(map.Find(999), nullptr); +} + +TEST(Containers_HashMap, OperatorBracket) { + HashMap map; + map[1] = 100; + map[2] = 200; + + EXPECT_EQ(map[1], 100); + EXPECT_EQ(map[2], 200); +} + +TEST(Containers_HashMap, OperatorBracket_NewKey) { + HashMap map; + map[1] = 100; + EXPECT_EQ(map.Size(), 1u); + EXPECT_EQ(map[1], 100); +} + +TEST(Containers_HashMap, Contains) { + HashMap map; + map.Insert(1, 100); + map.Insert(2, 200); + + EXPECT_TRUE(map.Contains(1)); + EXPECT_TRUE(map.Contains(2)); + EXPECT_FALSE(map.Contains(3)); +} + +TEST(Containers_HashMap, Erase) { + HashMap map; + map.Insert(1, 100); + map.Insert(2, 200); + EXPECT_EQ(map.Size(), 2u); + + bool erased = map.Erase(1); + EXPECT_TRUE(erased); + EXPECT_EQ(map.Size(), 1u); + EXPECT_FALSE(map.Contains(1)); + EXPECT_TRUE(map.Contains(2)); +} + +TEST(Containers_HashMap, Erase_NotFound) { + HashMap map; + map.Insert(1, 100); + + bool erased = map.Erase(999); + EXPECT_FALSE(erased); + EXPECT_EQ(map.Size(), 1u); +} + +TEST(Containers_HashMap, Clear) { + HashMap map; + map.Insert(1, 100); + map.Insert(2, 200); + map.Insert(3, 300); + + map.Clear(); + EXPECT_EQ(map.Size(), 0u); + EXPECT_TRUE(map.Empty()); +} + +TEST(Containers_HashMap, MultipleInsertions) { + HashMap map; + for (int i = 0; i < 100; ++i) { + map.Insert(i, i * 10); + } + EXPECT_EQ(map.Size(), 100u); + + for (int i = 0; i < 100; ++i) { + auto* val = map.Find(i); + ASSERT_NE(val, nullptr); + EXPECT_EQ(*val, i * 10); + } +} + +TEST(Containers_HashMap, StringKey) { + HashMap map; + map.Insert(String("apple"), 1); + map.Insert(String("banana"), 2); + map.Insert(String("cherry"), 3); + + EXPECT_EQ(map.Size(), 3u); + EXPECT_EQ(map[String("apple")], 1); + EXPECT_EQ(map[String("banana")], 2); + EXPECT_EQ(map[String("cherry")], 3); +} + +} // namespace diff --git a/tests/containers/test_string.cpp b/tests/containers/test_string.cpp new file mode 100644 index 00000000..171fbbea --- /dev/null +++ b/tests/containers/test_string.cpp @@ -0,0 +1,133 @@ +#include +#include + +using namespace XCEngine::Containers; + +namespace { + +TEST(Containers_String, DefaultConstructor_EmptyString) { + String s; + EXPECT_EQ(s.Length(), 0u); + EXPECT_STREQ(s.CStr(), ""); + EXPECT_TRUE(s.Empty()); +} + +TEST(Containers_String, ConstructorFromCString) { + String s("Hello"); + EXPECT_EQ(s.Length(), 5u); + EXPECT_STREQ(s.CStr(), "Hello"); +} + +TEST(Containers_String, CopyConstructor) { + String s1("Hello"); + String s2(s1); + EXPECT_EQ(s2.Length(), 5u); + EXPECT_STREQ(s2.CStr(), "Hello"); +} + +TEST(Containers_String, MoveConstructor) { + String s1("Hello"); + String s2(std::move(s1)); + EXPECT_EQ(s2.Length(), 5u); + EXPECT_STREQ(s2.CStr(), "Hello"); +} + +TEST(Containers_String, CopyAssignment) { + String s1("Hello"); + String s2; + s2 = s1; + EXPECT_EQ(s2.Length(), 5u); + EXPECT_STREQ(s2.CStr(), "Hello"); +} + +TEST(Containers_String, MoveAssignment) { + String s1("Hello"); + String s2; + s2 = std::move(s1); + EXPECT_EQ(s2.Length(), 5u); + EXPECT_STREQ(s2.CStr(), "Hello"); +} + +TEST(Containers_String, OperatorPlusEqual_String) { + String s("Hello"); + s += String(" World"); + EXPECT_EQ(s.Length(), 11u); + EXPECT_STREQ(s.CStr(), "Hello World"); +} + +TEST(Containers_String, OperatorPlusEqual_CString) { + String s("Hello"); + s += " World"; + EXPECT_EQ(s.Length(), 11u); + EXPECT_STREQ(s.CStr(), "Hello World"); +} + +TEST(Containers_String, Substring) { + String s("Hello World"); + String sub = s.Substring(0, 5); + EXPECT_STREQ(sub.CStr(), "Hello"); + + String sub2 = s.Substring(6); + EXPECT_STREQ(sub2.CStr(), "World"); + + String sub3 = s.Substring(6, 3); + EXPECT_STREQ(sub3.CStr(), "Wor"); +} + +TEST(Containers_String, Trim) { + String s(" Hello "); + String trimmed = s.Trim(); + EXPECT_STREQ(trimmed.CStr(), "Hello"); +} + +TEST(Containers_String, ToLower) { + String s("HELLO"); + String lower = s.ToLower(); + EXPECT_STREQ(lower.CStr(), "hello"); +} + +TEST(Containers_String, ToUpper) { + String s("hello"); + String upper = s.ToUpper(); + EXPECT_STREQ(upper.CStr(), "HELLO"); +} + +TEST(Containers_String, Find_Found) { + String s("Hello World"); + EXPECT_EQ(s.Find("World"), 6u); + EXPECT_EQ(s.Find("Hello"), 0u); +} + +TEST(Containers_String, Find_NotFound) { + String s("Hello"); + EXPECT_EQ(s.Find("World"), String::npos); +} + +TEST(Containers_String, StartsWith) { + String s("Hello World"); + EXPECT_TRUE(s.StartsWith("Hello")); + EXPECT_TRUE(s.StartsWith(String("Hello"))); + EXPECT_FALSE(s.StartsWith("World")); +} + +TEST(Containers_String, EndsWith) { + String s("Hello World"); + EXPECT_TRUE(s.EndsWith("World")); + EXPECT_TRUE(s.EndsWith(String("World"))); + EXPECT_FALSE(s.EndsWith("Hello")); +} + +TEST(Containers_String, Clear) { + String s("Hello"); + s.Clear(); + EXPECT_EQ(s.Length(), 0u); + EXPECT_TRUE(s.Empty()); +} + +TEST(Containers_String, Reserve) { + String s("Hi"); + s.Reserve(100); + EXPECT_GE(s.Capacity(), 100u); +} + +} // namespace diff --git a/tests/core/CMakeLists.txt b/tests/core/CMakeLists.txt new file mode 100644 index 00000000..3846e316 --- /dev/null +++ b/tests/core/CMakeLists.txt @@ -0,0 +1,30 @@ +# ============================================================ +# Core Library Tests +# ============================================================ + +set(CORE_TEST_SOURCES + test_core.cpp +) + +add_executable(xcengine_core_tests ${CORE_TEST_SOURCES}) + +# Exclude all static runtime libraries to avoid conflicts +if(MSVC) + set_target_properties(xcengine_core_tests PROPERTIES + LINK_FLAGS "/NODEFAULTLIB:libcpmt.lib /NODEFAULTLIB:libcmt.lib" + ) +endif() + +target_link_libraries(xcengine_core_tests + PRIVATE + XCEngine + GTest::gtest + GTest::gtest_main +) + +target_include_directories(xcengine_core_tests PRIVATE + ${CMAKE_SOURCE_DIR}/engine/include + ${CMAKE_SOURCE_DIR}/tests/fixtures +) + +add_test(NAME CoreTests COMMAND xcengine_core_tests) diff --git a/tests/core/test_core.cpp b/tests/core/test_core.cpp new file mode 100644 index 00000000..c748a395 --- /dev/null +++ b/tests/core/test_core.cpp @@ -0,0 +1,226 @@ +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace XCEngine::Core; + +namespace { + +class TestRefCounted : public RefCounted { +public: + TestRefCounted() : value(42) {} + int value; +}; + +TEST(Core_RefCounted, DefaultConstructor) { + TestRefCounted* obj = new TestRefCounted(); + EXPECT_EQ(obj->GetRefCount(), 1); + delete obj; +} + +TEST(Core_RefCounted, AddRef) { + TestRefCounted* obj = new TestRefCounted(); + EXPECT_EQ(obj->GetRefCount(), 1); + + obj->AddRef(); + EXPECT_EQ(obj->GetRefCount(), 2); + + obj->Release(); + EXPECT_EQ(obj->GetRefCount(), 1); + delete obj; +} + +TEST(Core_RefCounted, Release) { + TestRefCounted* obj = new TestRefCounted(); + EXPECT_EQ(obj->GetRefCount(), 1); + + obj->AddRef(); + EXPECT_EQ(obj->GetRefCount(), 2); + + obj->Release(); + EXPECT_EQ(obj->GetRefCount(), 1); + + obj->Release(); +} + +TEST(Core_RefCounted, ChainedRefs) { + TestRefCounted* obj = new TestRefCounted(); + EXPECT_EQ(obj->GetRefCount(), 1); + + for (int i = 0; i < 5; i++) { + obj->AddRef(); + } + EXPECT_EQ(obj->GetRefCount(), 6); + + for (int i = 0; i < 5; i++) { + obj->Release(); + } + EXPECT_EQ(obj->GetRefCount(), 1); + delete obj; +} + +struct TestStruct { + int value = 100; + std::string name = "test"; +}; + +TEST(Core_SmartPtr, MakeRef) { + auto ref = MakeRef(); + EXPECT_NE(ref, nullptr); + EXPECT_EQ(ref->value, 100); + EXPECT_EQ(ref->name, "test"); +} + +TEST(Core_SmartPtr, Ref_NullCheck) { + Ref ref = nullptr; + EXPECT_EQ(ref, nullptr); + EXPECT_FALSE(ref); +} + +TEST(Core_SmartPtr, Ref_Dereference) { + auto ref = MakeRef(); + ref->value = 200; + EXPECT_EQ(ref->value, 200); + EXPECT_EQ((*ref).value, 200); +} + +TEST(Core_SmartPtr, Ref_Copy) { + auto ref1 = MakeRef(); + ref1->value = 300; + + auto ref2 = ref1; + EXPECT_EQ(ref1.use_count(), 2); + EXPECT_EQ(ref2.use_count(), 2); + EXPECT_EQ(ref2->value, 300); + + ref2->value = 400; + EXPECT_EQ(ref1->value, 400); +} + +TEST(Core_SmartPtr, Ref_Move) { + auto ref1 = MakeRef(); + ref1->value = 500; + + auto ref2 = std::move(ref1); + EXPECT_EQ(ref1, nullptr); + EXPECT_NE(ref2, nullptr); + EXPECT_EQ(ref2->value, 500); +} + +TEST(Core_SmartPtr, MakeUnique) { + auto unique = MakeUnique(); + EXPECT_NE(unique, nullptr); + EXPECT_EQ(unique->value, 100); +} + +TEST(Core_SmartPtr, UniqueRef_Ownership) { + auto unique1 = MakeUnique(); + unique1->value = 600; + + auto unique2 = std::move(unique1); + EXPECT_EQ(unique1, nullptr); + EXPECT_NE(unique2, nullptr); + EXPECT_EQ(unique2->value, 600); +} + +TEST(Core_Event, Subscribe) { + Event<> event; + bool called = false; + + uint64_t id = event.Subscribe([&called]() { + called = true; + }); + + EXPECT_NE(id, 0u); + + event.Invoke(); + EXPECT_TRUE(called); +} + +TEST(Core_Event, Invoke_NoArgs) { + Event<> event; + int count = 0; + + event.Subscribe([&count]() { count++; }); + event.Subscribe([&count]() { count++; }); + + event.Invoke(); + EXPECT_EQ(count, 2); +} + +TEST(Core_Event, Invoke_WithArgs) { + Event event; + int result = 0; + std::string text; + + event.Subscribe([&result, &text](int v, const std::string& s) { + result = v; + text = s; + }); + + event.Invoke(42, "hello"); + EXPECT_EQ(result, 42); + EXPECT_EQ(text, "hello"); +} + +TEST(Core_Event, Unsubscribe) { + Event<> event; + int count = 0; + + uint64_t id = event.Subscribe([&count]() { count++; }); + event.Subscribe([&count]() { count++; }); + + event.Invoke(); + EXPECT_EQ(count, 2); + + event.Unsubscribe(id); + event.Invoke(); + EXPECT_EQ(count, 3); +} + +TEST(Core_Event, Clear) { + Event<> event; + int count = 0; + + event.Subscribe([&count]() { count++; }); + event.Subscribe([&count]() { count++; }); + + event.Clear(); + event.Invoke(); + EXPECT_EQ(count, 0); +} + +TEST(Core_Event, MultipleListeners) { + Event event; + std::vector results; + + for (int i = 0; i < 5; i++) { + event.Subscribe([&results](int v) { + results.push_back(v); + }); + } + + event.Invoke(10); + + EXPECT_EQ(results.size(), 5); + for (int v : results) { + EXPECT_EQ(v, 10); + } +} + +TEST(Core_Event, ReturnId) { + Event<> event; + + uint64_t id1 = event.Subscribe([]() {}); + uint64_t id2 = event.Subscribe([]() {}); + + EXPECT_NE(id1, id2); +} + +} // namespace diff --git a/tests/memory/CMakeLists.txt b/tests/memory/CMakeLists.txt new file mode 100644 index 00000000..ca568db3 --- /dev/null +++ b/tests/memory/CMakeLists.txt @@ -0,0 +1,31 @@ +# ============================================================ +# Memory Library Tests +# ============================================================ + +set(MEMORY_TEST_SOURCES + test_memory_manager.cpp + test_linear_allocator.cpp + test_pool_allocator.cpp +) + +add_executable(xcengine_memory_tests ${MEMORY_TEST_SOURCES}) + +if(MSVC) + set_target_properties(xcengine_memory_tests PROPERTIES + LINK_FLAGS "/NODEFAULTLIB:libcpmt.lib /NODEFAULTLIB:libcmt.lib" + ) +endif() + +target_link_libraries(xcengine_memory_tests + PRIVATE + XCEngine + GTest::gtest + GTest::gtest_main +) + +target_include_directories(xcengine_memory_tests PRIVATE + ${CMAKE_SOURCE_DIR}/engine/include + ${CMAKE_SOURCE_DIR}/tests/fixtures +) + +add_test(NAME MemoryTests COMMAND xcengine_memory_tests) diff --git a/tests/memory/test_linear_allocator.cpp b/tests/memory/test_linear_allocator.cpp new file mode 100644 index 00000000..6674e0d7 --- /dev/null +++ b/tests/memory/test_linear_allocator.cpp @@ -0,0 +1,77 @@ +#include +#include +#include + +using namespace XCEngine::Memory; + +namespace { + +TEST(LinearAllocator, Allocate) { + LinearAllocator allocator(1024); + + void* ptr = allocator.Allocate(64); + ASSERT_NE(ptr, nullptr); + EXPECT_EQ(allocator.GetUsedSize(), 64u); +} + +TEST(LinearAllocator, Allocate_Aligned) { + LinearAllocator allocator(1024); + + void* ptr = allocator.Allocate(64, 16); + ASSERT_NE(ptr, nullptr); + + uintptr_t addr = reinterpret_cast(ptr); + EXPECT_EQ(addr % 16, 0u); +} + +TEST(LinearAllocator, Clear) { + LinearAllocator allocator(1024); + + allocator.Allocate(64); + EXPECT_EQ(allocator.GetUsedSize(), 64u); + + allocator.Clear(); + EXPECT_EQ(allocator.GetUsedSize(), 0u); +} + +TEST(LinearAllocator, Marker) { + LinearAllocator allocator(1024); + + allocator.Allocate(64); + void* marker = allocator.GetMarker(); + + allocator.Allocate(32); + EXPECT_EQ(allocator.GetUsedSize(), 96u); + + allocator.SetMarker(marker); + EXPECT_EQ(allocator.GetUsedSize(), 64u); +} + +TEST(LinearAllocator, AllocateMultiple) { + LinearAllocator allocator(1024); + + allocator.Allocate(100); + allocator.Allocate(200); + allocator.Allocate(300); + + EXPECT_GE(allocator.GetUsedSize(), 600u); +} + +TEST(LinearAllocator, OutOfMemory) { + LinearAllocator allocator(200); + + void* ptr1 = allocator.Allocate(50, 8); + void* ptr2 = allocator.Allocate(50, 8); + ASSERT_NE(ptr1, nullptr); + ASSERT_NE(ptr2, nullptr); + + void* ptr3 = allocator.Allocate(100, 8); + EXPECT_EQ(ptr3, nullptr); +} + +TEST(LinearAllocator, GetCapacity) { + LinearAllocator allocator(512); + EXPECT_EQ(allocator.GetCapacity(), 512u); +} + +} // namespace diff --git a/tests/memory/test_memory_manager.cpp b/tests/memory/test_memory_manager.cpp new file mode 100644 index 00000000..5c9814e0 --- /dev/null +++ b/tests/memory/test_memory_manager.cpp @@ -0,0 +1,44 @@ +#include +#include + +using namespace XCEngine::Memory; + +namespace { + +class MemoryTest : public ::testing::Test { +protected: + void SetUp() override { + MemoryManager::Get().Initialize(); + } + + void TearDown() override { + MemoryManager::Get().Shutdown(); + } +}; + +TEST_F(MemoryTest, GetSystemAllocator_ReturnsValidPointer) { + IAllocator* allocator = MemoryManager::Get().GetSystemAllocator(); + ASSERT_NE(allocator, nullptr); +} + +TEST_F(MemoryTest, CreateLinearAllocator) { + auto allocator = MemoryManager::Get().CreateLinearAllocator(1024); + ASSERT_NE(allocator, nullptr); + EXPECT_EQ(allocator->GetCapacity(), 1024u); +} + +TEST_F(MemoryTest, CreatePoolAllocator) { + auto allocator = MemoryManager::Get().CreatePoolAllocator(64, 100); + ASSERT_NE(allocator, nullptr); + EXPECT_EQ(allocator->GetBlockSize(), 64u); + EXPECT_EQ(allocator->GetTotalBlockCount(), 100u); + EXPECT_EQ(allocator->GetFreeBlockCount(), 100u); +} + +TEST_F(MemoryTest, CreateProxyAllocator) { + auto allocator = MemoryManager::Get().CreateProxyAllocator("Test"); + ASSERT_NE(allocator, nullptr); + EXPECT_STREQ(allocator->GetName(), "Test"); +} + +} // namespace diff --git a/tests/memory/test_pool_allocator.cpp b/tests/memory/test_pool_allocator.cpp new file mode 100644 index 00000000..2c8d4c95 --- /dev/null +++ b/tests/memory/test_pool_allocator.cpp @@ -0,0 +1,83 @@ +#include +#include + +using namespace XCEngine::Memory; + +namespace { + +TEST(PoolAllocator, Allocate) { + PoolAllocator allocator(64, 10); + + void* ptr = allocator.Allocate(32); + ASSERT_NE(ptr, nullptr); + EXPECT_EQ(allocator.GetFreeBlockCount(), 9u); +} + +TEST(PoolAllocator, AllocateTooLarge) { + PoolAllocator allocator(64, 10); + + void* ptr = allocator.Allocate(128); + EXPECT_EQ(ptr, nullptr); + EXPECT_EQ(allocator.GetFreeBlockCount(), 10u); +} + +TEST(PoolAllocator, Free) { + PoolAllocator allocator(64, 10); + + void* ptr = allocator.Allocate(32); + ASSERT_NE(ptr, nullptr); + EXPECT_EQ(allocator.GetFreeBlockCount(), 9u); + + allocator.Free(ptr); + EXPECT_EQ(allocator.GetFreeBlockCount(), 10u); +} + +TEST(PoolAllocator, AllocateAllBlocks) { + PoolAllocator allocator(64, 5); + + void* blocks[5]; + for (int i = 0; i < 5; ++i) { + blocks[i] = allocator.Allocate(32); + ASSERT_NE(blocks[i], nullptr); + } + + EXPECT_EQ(allocator.GetFreeBlockCount(), 0u); + + void* extra = allocator.Allocate(32); + EXPECT_EQ(extra, nullptr); +} + +TEST(PoolAllocator, ReuseFreedBlocks) { + PoolAllocator allocator(64, 3); + + void* ptr1 = allocator.Allocate(32); + void* ptr2 = allocator.Allocate(32); + void* ptr3 = allocator.Allocate(32); + + allocator.Free(ptr2); + EXPECT_EQ(allocator.GetFreeBlockCount(), 1u); + + void* ptr4 = allocator.Allocate(32); + EXPECT_EQ(ptr4, ptr2); +} + +TEST(PoolAllocator, GetBlockSize) { + PoolAllocator allocator(64, 10); + EXPECT_EQ(allocator.GetBlockSize(), 64u); +} + +TEST(PoolAllocator, GetTotalBlockCount) { + PoolAllocator allocator(64, 10); + EXPECT_EQ(allocator.GetTotalBlockCount(), 10u); +} + +TEST(PoolAllocator, Contains) { + PoolAllocator allocator(64, 10); + + void* ptr = allocator.Allocate(32); + ASSERT_NE(ptr, nullptr); + + EXPECT_TRUE(allocator.Contains(ptr)); +} + +} // namespace diff --git a/tests/run_tests.bat b/tests/run_tests.bat new file mode 100644 index 00000000..eea7efda --- /dev/null +++ b/tests/run_tests.bat @@ -0,0 +1,8 @@ +@echo off +echo Running XCEngine Tests... +echo. + +ctest --test-dir tests/build -C Debug --output-on-failure + +echo. +pause diff --git a/tests/threading/CMakeLists.txt b/tests/threading/CMakeLists.txt new file mode 100644 index 00000000..255c26c7 --- /dev/null +++ b/tests/threading/CMakeLists.txt @@ -0,0 +1,31 @@ +# ============================================================ +# Threading Library Tests +# ============================================================ + +set(THREADING_TEST_SOURCES + test_mutex.cpp + test_spinlock.cpp + test_task.cpp +) + +add_executable(xcengine_threading_tests ${THREADING_TEST_SOURCES}) + +if(MSVC) + set_target_properties(xcengine_threading_tests PROPERTIES + LINK_FLAGS "/NODEFAULTLIB:libcpmt.lib /NODEFAULTLIB:libcmt.lib" + ) +endif() + +target_link_libraries(xcengine_threading_tests + PRIVATE + XCEngine + GTest::gtest + GTest::gtest_main +) + +target_include_directories(xcengine_threading_tests PRIVATE + ${CMAKE_SOURCE_DIR}/engine/include + ${CMAKE_SOURCE_DIR}/tests/fixtures +) + +add_test(NAME ThreadingTests COMMAND xcengine_threading_tests) diff --git a/tests/threading/test_mutex.cpp b/tests/threading/test_mutex.cpp new file mode 100644 index 00000000..8d1f5984 --- /dev/null +++ b/tests/threading/test_mutex.cpp @@ -0,0 +1,41 @@ +#include +#include + +using namespace XCEngine::Threading; + +namespace { + +TEST(Threading_Mutex, LockUnlock) { + Mutex mutex; + + mutex.Lock(); + mutex.Unlock(); +} + +TEST(Threading_Mutex, TryLock_Success) { + Mutex mutex; + + bool result = mutex.TryLock(); + EXPECT_TRUE(result); + mutex.Unlock(); +} + +TEST(Threading_Mutex, TryLock_AlreadyLocked) { + Mutex mutex; + + mutex.Lock(); + bool result = mutex.TryLock(); + EXPECT_FALSE(result); + mutex.Unlock(); +} + +TEST(Threading_Mutex, MultipleLockUnlock) { + Mutex mutex; + + for (int i = 0; i < 10; ++i) { + mutex.Lock(); + mutex.Unlock(); + } +} + +} // namespace diff --git a/tests/threading/test_spinlock.cpp b/tests/threading/test_spinlock.cpp new file mode 100644 index 00000000..61637c5b --- /dev/null +++ b/tests/threading/test_spinlock.cpp @@ -0,0 +1,32 @@ +#include +#include + +using namespace XCEngine::Threading; + +namespace { + +TEST(Threading_SpinLock, LockUnlock) { + SpinLock spinlock; + + spinlock.Lock(); + spinlock.Unlock(); +} + +TEST(Threading_SpinLock, TryLock_Success) { + SpinLock spinlock; + + bool result = spinlock.TryLock(); + EXPECT_TRUE(result); + spinlock.Unlock(); +} + +TEST(Threading_SpinLock, TryLock_AlreadyLocked) { + SpinLock spinlock; + + spinlock.Lock(); + bool result = spinlock.TryLock(); + EXPECT_FALSE(result); + spinlock.Unlock(); +} + +} // namespace diff --git a/tests/threading/test_task.cpp b/tests/threading/test_task.cpp new file mode 100644 index 00000000..85b36e62 --- /dev/null +++ b/tests/threading/test_task.cpp @@ -0,0 +1,59 @@ +#include +#include +#include + +using namespace XCEngine::Threading; + +namespace { + +class TestTask : public ITask { +public: + TestTask(bool* flag) : m_flag(flag) {} + + void Execute() override { + if (m_flag) *m_flag = true; + } + +private: + bool* m_flag; +}; + +TEST(Threading_Task, DefaultPriority) { + TestTask task(nullptr); + EXPECT_EQ(task.GetPriority(), TaskPriority::Normal); +} + +TEST(Threading_Task, SetPriority) { + TestTask task(nullptr); + task.SetPriority(TaskPriority::High); + EXPECT_EQ(task.GetPriority(), TaskPriority::High); +} + +TEST(Threading_Task, Status) { + TestTask task(nullptr); + EXPECT_EQ(task.GetStatus(), TaskStatus::Pending); + + task.SetStatus(TaskStatus::Running); + EXPECT_EQ(task.GetStatus(), TaskStatus::Running); +} + +TEST(Threading_Task, Execute) { + bool executed = false; + TestTask task(&executed); + task.Execute(); + EXPECT_TRUE(executed); +} + +TEST(Threading_LambdaTask, Execute) { + bool executed = false; + LambdaTask task([&executed]() { executed = true; }); + task.Execute(); + EXPECT_TRUE(executed); +} + +TEST(Threading_LambdaTask, Priority) { + LambdaTask task([]() {}, TaskPriority::Low); + EXPECT_EQ(task.GetPriority(), TaskPriority::Low); +} + +} // namespace