feat: 实现Containers、Memory、Threading核心模块及单元测试
- Containers: String, Array, HashMap 容器实现及测试 - Memory: Allocator, LinearAllocator, PoolAllocator, ProxyAllocator, MemoryManager 实现及测试 - Threading: Mutex, SpinLock, ReadWriteLock, Thread, Task, TaskSystem 实现及测试 - 修复Windows平台兼容性: _aligned_malloc, std::hash特化 - 修复构建错误和测试用例问题
This commit is contained in:
@@ -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
|
||||
|
||||
292
engine/include/XCEngine/Containers/Array.h
Normal file
292
engine/include/XCEngine/Containers/Array.h
Normal file
@@ -0,0 +1,292 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <initializer_list>
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
#include "../Memory/Allocator.h"
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Containers {
|
||||
|
||||
template<typename T>
|
||||
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<T> 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<typename... Args>
|
||||
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<typename T>
|
||||
Array<T>::Array(size_t capacity) : m_capacity(capacity) {
|
||||
if (capacity > 0) {
|
||||
m_data = static_cast<T*>(::operator new(capacity * sizeof(T)));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Array<T>::Array(size_t count, const T& value) : m_size(count), m_capacity(count) {
|
||||
if (count > 0) {
|
||||
m_data = static_cast<T*>(::operator new(count * sizeof(T)));
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
new (&m_data[i]) T(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Array<T>::Array(std::initializer_list<T> init) : m_size(init.size()), m_capacity(init.size()) {
|
||||
if (m_size > 0) {
|
||||
m_data = static_cast<T*>(::operator new(m_size * sizeof(T)));
|
||||
size_t i = 0;
|
||||
for (const auto& item : init) {
|
||||
new (&m_data[i++]) T(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Array<T>::~Array() {
|
||||
DestructRange(m_data, m_data + m_size);
|
||||
::operator delete(m_data);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Array<T>::Array(const Array& other) : m_size(other.m_size), m_capacity(other.m_size) {
|
||||
if (m_size > 0) {
|
||||
m_data = static_cast<T*>(::operator new(m_size * sizeof(T)));
|
||||
CopyRange(other.m_data, m_data, m_size);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Array<T>::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<typename T>
|
||||
Array<T>& Array<T>::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<T*>(::operator new(m_size * sizeof(T)));
|
||||
CopyRange(other.m_data, m_data, m_size);
|
||||
} else {
|
||||
m_data = nullptr;
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Array<T>& Array<T>::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<typename T>
|
||||
T& Array<T>::operator[](size_t index) {
|
||||
return m_data[index];
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
const T& Array<T>::operator[](size_t index) const {
|
||||
return m_data[index];
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void Array<T>::Clear() {
|
||||
DestructRange(m_data, m_data + m_size);
|
||||
m_size = 0;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void Array<T>::Reserve(size_t capacity) {
|
||||
if (capacity > m_capacity) {
|
||||
Reallocate(capacity);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void Array<T>::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<typename T>
|
||||
void Array<T>::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<typename T>
|
||||
void Array<T>::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<typename T>
|
||||
void Array<T>::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<typename T>
|
||||
template<typename... Args>
|
||||
T& Array<T>::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>(args)...);
|
||||
++m_size;
|
||||
return m_data[m_size - 1];
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void Array<T>::PopBack() {
|
||||
if (m_size > 0) {
|
||||
--m_size;
|
||||
m_data[m_size].~T();
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void Array<T>::Reallocate(size_t newCapacity) {
|
||||
T* newData = nullptr;
|
||||
if (newCapacity > 0) {
|
||||
newData = static_cast<T*>(::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<typename T>
|
||||
void Array<T>::DestructRange(T* begin, T* end) {
|
||||
for (T* p = begin; p < end; ++p) {
|
||||
p->~T();
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void Array<T>::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
|
||||
11
engine/include/XCEngine/Containers/Containers.h
Normal file
11
engine/include/XCEngine/Containers/Containers.h
Normal file
@@ -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
|
||||
306
engine/include/XCEngine/Containers/HashMap.h
Normal file
306
engine/include/XCEngine/Containers/HashMap.h
Normal file
@@ -0,0 +1,306 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
#include <functional>
|
||||
#include "../Memory/Allocator.h"
|
||||
#include "Array.h"
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Containers {
|
||||
|
||||
template<typename Key, typename Value>
|
||||
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<Pair> pairs;
|
||||
};
|
||||
|
||||
static constexpr size_t DefaultBucketCount = 16;
|
||||
|
||||
Array<Bucket> 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<Pair>::Iterator FindInBucket(Bucket& bucket, const Key& key);
|
||||
typename Array<Pair>::ConstIterator FindInBucket(const Bucket& bucket, const Key& key) const;
|
||||
};
|
||||
|
||||
template<typename Key, typename Value>
|
||||
HashMap<Key, Value>::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<typename Key, typename Value>
|
||||
HashMap<Key, Value>::~HashMap() {
|
||||
Clear();
|
||||
}
|
||||
|
||||
template<typename Key, typename Value>
|
||||
HashMap<Key, Value>::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<typename Key, typename Value>
|
||||
HashMap<Key, Value>::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<typename Key, typename Value>
|
||||
HashMap<Key, Value>& HashMap<Key, Value>::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<typename Key, typename Value>
|
||||
HashMap<Key, Value>& HashMap<Key, Value>::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<typename Key, typename Value>
|
||||
Value& HashMap<Key, Value>::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<float>(m_size) / static_cast<float>(m_bucketCount) > m_loadFactor) {
|
||||
Resize();
|
||||
}
|
||||
|
||||
return bucket.pairs.Back().second;
|
||||
}
|
||||
|
||||
template<typename Key, typename Value>
|
||||
Value* HashMap<Key, Value>::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<typename Key, typename Value>
|
||||
const Value* HashMap<Key, Value>::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<typename Key, typename Value>
|
||||
bool HashMap<Key, Value>::Contains(const Key& key) const {
|
||||
size_t bucketIndex = GetBucketIndex(key);
|
||||
const Bucket& bucket = m_buckets[bucketIndex];
|
||||
return FindInBucket(bucket, key) != bucket.pairs.end();
|
||||
}
|
||||
|
||||
template<typename Key, typename Value>
|
||||
bool HashMap<Key, Value>::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<float>(m_size) / static_cast<float>(m_bucketCount) > m_loadFactor) {
|
||||
Resize();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename Key, typename Value>
|
||||
bool HashMap<Key, Value>::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<float>(m_size) / static_cast<float>(m_bucketCount) > m_loadFactor) {
|
||||
Resize();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename Key, typename Value>
|
||||
bool HashMap<Key, Value>::Insert(Pair&& pair) {
|
||||
return Insert(std::move(pair.first), std::move(pair.second));
|
||||
}
|
||||
|
||||
template<typename Key, typename Value>
|
||||
bool HashMap<Key, Value>::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<typename Key, typename Value>
|
||||
void HashMap<Key, Value>::Clear() {
|
||||
for (size_t i = 0; i < m_buckets.Size(); ++i) {
|
||||
m_buckets[i].pairs.Clear();
|
||||
}
|
||||
m_size = 0;
|
||||
}
|
||||
|
||||
template<typename Key, typename Value>
|
||||
size_t HashMap<Key, Value>::GetBucketIndex(const Key& key) const {
|
||||
if (m_bucketCount == 0) {
|
||||
return 0;
|
||||
}
|
||||
std::hash<Key> hasher;
|
||||
return hasher(key) % m_bucketCount;
|
||||
}
|
||||
|
||||
template<typename Key, typename Value>
|
||||
void HashMap<Key, Value>::Resize() {
|
||||
size_t newBucketCount = m_bucketCount * 2;
|
||||
Array<Bucket> 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<Key> 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 Key, typename Value>
|
||||
typename Array<typename HashMap<Key, Value>::Pair>::Iterator HashMap<Key, Value>::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 Key, typename Value>
|
||||
typename Array<typename HashMap<Key, Value>::Pair>::ConstIterator HashMap<Key, Value>::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
|
||||
96
engine/include/XCEngine/Containers/String.h
Normal file
96
engine/include/XCEngine/Containers/String.h
Normal file
@@ -0,0 +1,96 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
#include <initializer_list>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Containers {
|
||||
|
||||
class String {
|
||||
public:
|
||||
using SizeType = size_t;
|
||||
static constexpr SizeType npos = static_cast<SizeType>(-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<XCEngine::Containers::String> {
|
||||
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;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -39,7 +39,7 @@ public:
|
||||
m_pendingUnsubscribes.clear();
|
||||
}
|
||||
|
||||
void Invoke(Args... args) const {
|
||||
void Invoke(Args... args) {
|
||||
std::vector<Listener> listenersCopy;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
26
engine/include/XCEngine/Memory/Allocator.h
Normal file
26
engine/include/XCEngine/Memory/Allocator.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
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
|
||||
39
engine/include/XCEngine/Memory/LinearAllocator.h
Normal file
39
engine/include/XCEngine/Memory/LinearAllocator.h
Normal file
@@ -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
|
||||
46
engine/include/XCEngine/Memory/MemoryManager.h
Normal file
46
engine/include/XCEngine/Memory/MemoryManager.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#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<LinearAllocator> CreateLinearAllocator(size_t size);
|
||||
std::unique_ptr<PoolAllocator> CreatePoolAllocator(size_t blockSize, size_t count);
|
||||
std::unique_ptr<ProxyAllocator> 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
|
||||
43
engine/include/XCEngine/Memory/PoolAllocator.h
Normal file
43
engine/include/XCEngine/Memory/PoolAllocator.h
Normal file
@@ -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
|
||||
41
engine/include/XCEngine/Memory/ProxyAllocator.h
Normal file
41
engine/include/XCEngine/Memory/ProxyAllocator.h
Normal file
@@ -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
|
||||
23
engine/include/XCEngine/Threading/LambdaTask.h
Normal file
23
engine/include/XCEngine/Threading/LambdaTask.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "Task.h"
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Threading {
|
||||
|
||||
template<typename Func>
|
||||
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
|
||||
26
engine/include/XCEngine/Threading/Mutex.h
Normal file
26
engine/include/XCEngine/Threading/Mutex.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
|
||||
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
|
||||
53
engine/include/XCEngine/Threading/ReadWriteLock.h
Normal file
53
engine/include/XCEngine/Threading/ReadWriteLock.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Threading {
|
||||
|
||||
class ReadWriteLock {
|
||||
public:
|
||||
ReadWriteLock() = default;
|
||||
~ReadWriteLock() = default;
|
||||
|
||||
void ReadLock() {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
m_readCondition.wait(lock, [this] { return !m_writerActive && m_writersWaiting == 0; });
|
||||
++m_readers;
|
||||
}
|
||||
|
||||
void ReadUnlock() {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
--m_readers;
|
||||
if (m_readers == 0) {
|
||||
m_writeCondition.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
void WriteLock() {
|
||||
std::unique_lock<std::mutex> 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<std::mutex> 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
|
||||
32
engine/include/XCEngine/Threading/SpinLock.h
Normal file
32
engine/include/XCEngine/Threading/SpinLock.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
|
||||
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
|
||||
61
engine/include/XCEngine/Threading/Task.h
Normal file
61
engine/include/XCEngine/Threading/Task.h
Normal file
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <cstdint>
|
||||
|
||||
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<TaskStatus> m_status{TaskStatus::Pending};
|
||||
uint64_t m_id = 0;
|
||||
std::atomic<uint32_t> m_refCount{1};
|
||||
};
|
||||
|
||||
} // namespace Threading
|
||||
} // namespace XCEngine
|
||||
51
engine/include/XCEngine/Threading/TaskGroup.h
Normal file
51
engine/include/XCEngine/Threading/TaskGroup.h
Normal file
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include "Task.h"
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Threading {
|
||||
|
||||
class TaskGroup {
|
||||
public:
|
||||
using Callback = std::function<void()>;
|
||||
|
||||
TaskGroup();
|
||||
~TaskGroup();
|
||||
|
||||
uint64_t AddTask(std::unique_ptr<ITask> 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<uint64_t> dependencies;
|
||||
int pendingDepCount = 0;
|
||||
bool completed = false;
|
||||
};
|
||||
|
||||
std::vector<TaskNode> m_tasks;
|
||||
std::atomic<int> m_pendingCount{0};
|
||||
std::atomic<int> m_completedCount{0};
|
||||
Callback m_completeCallback;
|
||||
mutable std::mutex m_mutex;
|
||||
std::condition_variable m_condition;
|
||||
std::atomic<bool> m_canceled{false};
|
||||
};
|
||||
|
||||
} // namespace Threading
|
||||
} // namespace XCEngine
|
||||
108
engine/include/XCEngine/Threading/TaskSystem.h
Normal file
108
engine/include/XCEngine/Threading/TaskSystem.h
Normal file
@@ -0,0 +1,108 @@
|
||||
#pragma once
|
||||
|
||||
#include "TaskSystemConfig.h"
|
||||
#include "Task.h"
|
||||
#include "TaskGroup.h"
|
||||
#include "Mutex.h"
|
||||
#include "SpinLock.h"
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Threading {
|
||||
|
||||
class TaskSystem {
|
||||
public:
|
||||
static TaskSystem& Get();
|
||||
|
||||
void Initialize(const TaskSystemConfig& config);
|
||||
void Shutdown();
|
||||
|
||||
uint64_t Submit(std::unique_ptr<ITask> task);
|
||||
uint64_t Submit(std::function<void()>&& func, TaskPriority priority = TaskPriority::Normal);
|
||||
|
||||
TaskGroup* CreateTaskGroup();
|
||||
void DestroyTaskGroup(TaskGroup* group);
|
||||
|
||||
void Wait(uint64_t taskId);
|
||||
uint32_t GetWorkerThreadCount() const;
|
||||
|
||||
void Update();
|
||||
|
||||
template<typename Func>
|
||||
void ParallelFor(int32_t start, int32_t end, Func&& func);
|
||||
|
||||
void RunOnMainThread(std::function<void()>&& 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<std::thread> m_workerThreads;
|
||||
std::priority_queue<TaskWrapper> m_taskQueue;
|
||||
|
||||
std::vector<TaskGroup*> m_taskGroups;
|
||||
std::vector<std::function<void()>> m_mainThreadQueue;
|
||||
|
||||
Mutex m_queueMutex;
|
||||
std::mutex m_conditionMutex;
|
||||
SpinLock m_groupMutex;
|
||||
std::condition_variable m_taskAvailable;
|
||||
std::condition_variable m_mainThreadCondition;
|
||||
|
||||
std::atomic<bool> m_running{false};
|
||||
std::atomic<uint64_t> m_nextTaskId{0};
|
||||
uint32_t m_workerThreadCount = 0;
|
||||
bool m_shutdown = false;
|
||||
};
|
||||
|
||||
template<typename Func>
|
||||
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<std::function<void()>> 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<LambdaTask<std::function<void()>>>(std::move(task), TaskPriority::High));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Threading
|
||||
} // namespace XCEngine
|
||||
17
engine/include/XCEngine/Threading/TaskSystemConfig.h
Normal file
17
engine/include/XCEngine/Threading/TaskSystemConfig.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
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
|
||||
43
engine/include/XCEngine/Threading/Thread.h
Normal file
43
engine/include/XCEngine/Threading/Thread.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include "Containers/String.h"
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Threading {
|
||||
|
||||
class Thread {
|
||||
public:
|
||||
using Id = uint64_t;
|
||||
|
||||
Thread();
|
||||
~Thread();
|
||||
|
||||
template<typename Func>
|
||||
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<typename Func>
|
||||
void Thread::Start(Func&& func, const Containers::String& name) {
|
||||
m_name = name;
|
||||
m_thread = std::thread(std::forward<Func>(func));
|
||||
m_id = static_cast<Id>(reinterpret_cast<uintptr_t>(m_thread.native_handle()));
|
||||
}
|
||||
|
||||
} // namespace Threading
|
||||
} // namespace XCEngine
|
||||
17
engine/include/XCEngine/Threading/Threading.h
Normal file
17
engine/include/XCEngine/Threading/Threading.h
Normal file
@@ -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
|
||||
327
engine/src/Containers/String.cpp
Normal file
327
engine/src/Containers/String.cpp
Normal file
@@ -0,0 +1,327 @@
|
||||
#include "Containers/String.h"
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
|
||||
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
|
||||
272
engine/src/Memory/Memory.cpp
Normal file
272
engine/src/Memory/Memory.cpp
Normal file
@@ -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 <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
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<uint8_t*>(parent->Allocate(size, 8));
|
||||
} else {
|
||||
m_buffer = static_cast<uint8_t*>(_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<uintptr_t>(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<void*>(m_offset);
|
||||
}
|
||||
|
||||
void LinearAllocator::SetMarker(void* marker) {
|
||||
m_offset = reinterpret_cast<size_t>(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<uint8_t*>(m_memory);
|
||||
m_freeList = reinterpret_cast<FreeNode*>(memory);
|
||||
|
||||
FreeNode* current = m_freeList;
|
||||
for (size_t i = 1; i < poolSize; ++i) {
|
||||
uint8_t* block = memory + (i * actualBlockSize);
|
||||
current->next = reinterpret_cast<FreeNode*>(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<FreeNode*>(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<uint8_t*>(m_memory);
|
||||
uint8_t* p = static_cast<uint8_t*>(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<Threading::Mutex> 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<Threading::Mutex> 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<Threading::Mutex> 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<SystemAllocator*>(m_systemAllocator);
|
||||
m_systemAllocator = nullptr;
|
||||
m_initialized = false;
|
||||
}
|
||||
|
||||
IAllocator* MemoryManager::GetSystemAllocator() {
|
||||
return m_systemAllocator;
|
||||
}
|
||||
|
||||
std::unique_ptr<LinearAllocator> MemoryManager::CreateLinearAllocator(size_t size) {
|
||||
return std::make_unique<LinearAllocator>(size, m_systemAllocator);
|
||||
}
|
||||
|
||||
std::unique_ptr<PoolAllocator> MemoryManager::CreatePoolAllocator(size_t blockSize, size_t count) {
|
||||
return std::make_unique<PoolAllocator>(blockSize, count);
|
||||
}
|
||||
|
||||
std::unique_ptr<ProxyAllocator> MemoryManager::CreateProxyAllocator(const char* name) {
|
||||
return std::make_unique<ProxyAllocator>(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
|
||||
74
engine/src/Threading/TaskGroup.cpp
Normal file
74
engine/src/Threading/TaskGroup.cpp
Normal file
@@ -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<ITask> task) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
TaskNode node;
|
||||
node.task = task.get();
|
||||
node.pendingDepCount = static_cast<int>(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<LambdaTask<Callback>>(std::move(func), priority);
|
||||
return AddTask(std::move(task));
|
||||
}
|
||||
|
||||
void TaskGroup::AddDependency(uint64_t taskId, uint64_t dependsOn) {
|
||||
std::lock_guard<std::mutex> 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<std::mutex> lock(m_mutex);
|
||||
m_condition.wait(lock, [this] { return m_pendingCount.load() == 0; });
|
||||
}
|
||||
|
||||
bool TaskGroup::WaitFor(std::chrono::milliseconds timeout) {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
return m_condition.wait_for(lock, timeout, [this] { return m_pendingCount.load() == 0; });
|
||||
}
|
||||
|
||||
void TaskGroup::SetCompleteCallback(Callback&& callback) {
|
||||
std::lock_guard<std::mutex> 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<float>(m_completedCount.load()) / static_cast<float>(total);
|
||||
}
|
||||
|
||||
void TaskGroup::Cancel() {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_canceled = true;
|
||||
for (auto& node : m_tasks) {
|
||||
if (node.task && !node.completed) {
|
||||
node.task->OnCancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Threading
|
||||
} // namespace XCEngine
|
||||
162
engine/src/Threading/TaskSystem.cpp
Normal file
162
engine/src/Threading/TaskSystem.cpp
Normal file
@@ -0,0 +1,162 @@
|
||||
#include "Threading/TaskSystem.h"
|
||||
#include "Threading/LambdaTask.h"
|
||||
#include <algorithm>
|
||||
|
||||
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<ITask> 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<Mutex> lock(m_queueMutex);
|
||||
m_taskQueue.push(wrapper);
|
||||
}
|
||||
|
||||
m_taskAvailable.notify_one();
|
||||
return taskId;
|
||||
}
|
||||
|
||||
uint64_t TaskSystem::Submit(std::function<void()>&& func, TaskPriority priority) {
|
||||
ITask* task = new LambdaTask<std::function<void()>>(std::move(func), priority);
|
||||
return Submit(std::unique_ptr<ITask>(task));
|
||||
}
|
||||
|
||||
TaskGroup* TaskSystem::CreateTaskGroup() {
|
||||
TaskGroup* group = new TaskGroup();
|
||||
std::lock_guard<SpinLock> lock(m_groupMutex);
|
||||
m_taskGroups.push_back(group);
|
||||
return group;
|
||||
}
|
||||
|
||||
void TaskSystem::DestroyTaskGroup(TaskGroup* group) {
|
||||
if (!group) return;
|
||||
|
||||
{
|
||||
std::lock_guard<SpinLock> 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<std::function<void()>> tasks;
|
||||
{
|
||||
std::lock_guard<Mutex> lock(m_queueMutex);
|
||||
tasks = std::move(m_mainThreadQueue);
|
||||
m_mainThreadQueue.clear();
|
||||
}
|
||||
|
||||
for (auto& task : tasks) {
|
||||
task();
|
||||
}
|
||||
}
|
||||
|
||||
void TaskSystem::RunOnMainThread(std::function<void()>&& func) {
|
||||
{
|
||||
std::lock_guard<Mutex> 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<std::mutex> 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
|
||||
41
engine/src/Threading/Thread.cpp
Normal file
41
engine/src/Threading/Thread.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
#include "Threading/Thread.h"
|
||||
#include <thread>
|
||||
|
||||
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<Id>(std::hash<std::thread::id>{}(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
|
||||
@@ -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
|
||||
|
||||
31
tests/containers/CMakeLists.txt
Normal file
31
tests/containers/CMakeLists.txt
Normal file
@@ -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)
|
||||
162
tests/containers/test_array.cpp
Normal file
162
tests/containers/test_array.cpp
Normal file
@@ -0,0 +1,162 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <XCEngine/Containers/Array.h>
|
||||
|
||||
using namespace XCEngine::Containers;
|
||||
|
||||
namespace {
|
||||
|
||||
TEST(Containers_Array, DefaultConstructor) {
|
||||
Array<int> arr;
|
||||
EXPECT_EQ(arr.Size(), 0u);
|
||||
EXPECT_EQ(arr.Capacity(), 0u);
|
||||
EXPECT_TRUE(arr.Empty());
|
||||
}
|
||||
|
||||
TEST(Containers_Array, CapacityConstructor) {
|
||||
Array<int> arr(10);
|
||||
EXPECT_EQ(arr.Size(), 0u);
|
||||
EXPECT_GE(arr.Capacity(), 10u);
|
||||
}
|
||||
|
||||
TEST(Containers_Array, CountValueConstructor) {
|
||||
Array<int> 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<int> 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<int> arr1 = {1, 2, 3};
|
||||
Array<int> arr2(arr1);
|
||||
EXPECT_EQ(arr2.Size(), 3u);
|
||||
EXPECT_EQ(arr2[0], 1);
|
||||
EXPECT_EQ(arr2[2], 3);
|
||||
}
|
||||
|
||||
TEST(Containers_Array, MoveConstructor) {
|
||||
Array<int> arr1 = {1, 2, 3};
|
||||
Array<int> arr2(std::move(arr1));
|
||||
EXPECT_EQ(arr2.Size(), 3u);
|
||||
EXPECT_EQ(arr2[0], 1);
|
||||
}
|
||||
|
||||
TEST(Containers_Array, CopyAssignment) {
|
||||
Array<int> arr1 = {1, 2, 3};
|
||||
Array<int> arr2;
|
||||
arr2 = arr1;
|
||||
EXPECT_EQ(arr2.Size(), 3u);
|
||||
EXPECT_EQ(arr2[0], 1);
|
||||
}
|
||||
|
||||
TEST(Containers_Array, MoveAssignment) {
|
||||
Array<int> arr1 = {1, 2, 3};
|
||||
Array<int> arr2;
|
||||
arr2 = std::move(arr1);
|
||||
EXPECT_EQ(arr2.Size(), 3u);
|
||||
EXPECT_EQ(arr2[0], 1);
|
||||
}
|
||||
|
||||
TEST(Containers_Array, OperatorSquareBrackets) {
|
||||
Array<int> 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<int> 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<int> arr;
|
||||
arr.EmplaceBack(1);
|
||||
arr.EmplaceBack(2);
|
||||
arr.EmplaceBack(3);
|
||||
EXPECT_EQ(arr.Size(), 3u);
|
||||
}
|
||||
|
||||
TEST(Containers_Array, PopBack) {
|
||||
Array<int> arr = {1, 2, 3};
|
||||
arr.PopBack();
|
||||
EXPECT_EQ(arr.Size(), 2u);
|
||||
EXPECT_EQ(arr[1], 2);
|
||||
}
|
||||
|
||||
TEST(Containers_Array, Clear) {
|
||||
Array<int> arr = {1, 2, 3};
|
||||
arr.Clear();
|
||||
EXPECT_EQ(arr.Size(), 0u);
|
||||
EXPECT_TRUE(arr.Empty());
|
||||
}
|
||||
|
||||
TEST(Containers_Array, Reserve) {
|
||||
Array<int> arr;
|
||||
arr.Reserve(100);
|
||||
EXPECT_GE(arr.Capacity(), 100u);
|
||||
}
|
||||
|
||||
TEST(Containers_Array, Resize) {
|
||||
Array<int> 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<int> arr = {10, 20, 30};
|
||||
EXPECT_EQ(arr.Front(), 10);
|
||||
}
|
||||
|
||||
TEST(Containers_Array, Back) {
|
||||
Array<int> arr = {10, 20, 30};
|
||||
EXPECT_EQ(arr.Back(), 30);
|
||||
}
|
||||
|
||||
TEST(Containers_Array, Data) {
|
||||
Array<int> arr = {10, 20, 30};
|
||||
EXPECT_EQ(arr.Data()[0], 10);
|
||||
EXPECT_EQ(arr.Data()[1], 20);
|
||||
}
|
||||
|
||||
TEST(Containers_Array, Iterator) {
|
||||
Array<int> 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<int> arr = {1, 2, 3, 4, 5};
|
||||
int sum = 0;
|
||||
for (int v : arr) {
|
||||
sum += v;
|
||||
}
|
||||
EXPECT_EQ(sum, 15);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
139
tests/containers/test_hashmap.cpp
Normal file
139
tests/containers/test_hashmap.cpp
Normal file
@@ -0,0 +1,139 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <XCEngine/Containers/HashMap.h>
|
||||
#include <XCEngine/Containers/String.h>
|
||||
|
||||
using namespace XCEngine::Containers;
|
||||
|
||||
namespace {
|
||||
|
||||
TEST(Containers_HashMap, DefaultConstructor) {
|
||||
HashMap<int, int> map;
|
||||
EXPECT_EQ(map.Size(), 0u);
|
||||
EXPECT_TRUE(map.Empty());
|
||||
}
|
||||
|
||||
TEST(Containers_HashMap, Insert) {
|
||||
HashMap<int, String> map;
|
||||
map.Insert(1, String("one"));
|
||||
EXPECT_EQ(map.Size(), 1u);
|
||||
}
|
||||
|
||||
TEST(Containers_HashMap, InsertDuplicate) {
|
||||
HashMap<int, int> 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<int, int> 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<int, int> map;
|
||||
map.Insert(1, 100);
|
||||
EXPECT_EQ(map.Find(999), nullptr);
|
||||
}
|
||||
|
||||
TEST(Containers_HashMap, OperatorBracket) {
|
||||
HashMap<int, int> map;
|
||||
map[1] = 100;
|
||||
map[2] = 200;
|
||||
|
||||
EXPECT_EQ(map[1], 100);
|
||||
EXPECT_EQ(map[2], 200);
|
||||
}
|
||||
|
||||
TEST(Containers_HashMap, OperatorBracket_NewKey) {
|
||||
HashMap<int, int> map;
|
||||
map[1] = 100;
|
||||
EXPECT_EQ(map.Size(), 1u);
|
||||
EXPECT_EQ(map[1], 100);
|
||||
}
|
||||
|
||||
TEST(Containers_HashMap, Contains) {
|
||||
HashMap<int, int> 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<int, int> 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<int, int> map;
|
||||
map.Insert(1, 100);
|
||||
|
||||
bool erased = map.Erase(999);
|
||||
EXPECT_FALSE(erased);
|
||||
EXPECT_EQ(map.Size(), 1u);
|
||||
}
|
||||
|
||||
TEST(Containers_HashMap, Clear) {
|
||||
HashMap<int, int> 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<int, int> 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<String, int> 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
|
||||
133
tests/containers/test_string.cpp
Normal file
133
tests/containers/test_string.cpp
Normal file
@@ -0,0 +1,133 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <XCEngine/Containers/String.h>
|
||||
|
||||
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
|
||||
30
tests/core/CMakeLists.txt
Normal file
30
tests/core/CMakeLists.txt
Normal file
@@ -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)
|
||||
226
tests/core/test_core.cpp
Normal file
226
tests/core/test_core.cpp
Normal file
@@ -0,0 +1,226 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <XCEngine/Core/RefCounted.h>
|
||||
#include <XCEngine/Core/SmartPtr.h>
|
||||
#include <XCEngine/Core/Event.h>
|
||||
#include <XCEngine/Core/Types.h>
|
||||
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
|
||||
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<TestStruct>();
|
||||
EXPECT_NE(ref, nullptr);
|
||||
EXPECT_EQ(ref->value, 100);
|
||||
EXPECT_EQ(ref->name, "test");
|
||||
}
|
||||
|
||||
TEST(Core_SmartPtr, Ref_NullCheck) {
|
||||
Ref<TestStruct> ref = nullptr;
|
||||
EXPECT_EQ(ref, nullptr);
|
||||
EXPECT_FALSE(ref);
|
||||
}
|
||||
|
||||
TEST(Core_SmartPtr, Ref_Dereference) {
|
||||
auto ref = MakeRef<TestStruct>();
|
||||
ref->value = 200;
|
||||
EXPECT_EQ(ref->value, 200);
|
||||
EXPECT_EQ((*ref).value, 200);
|
||||
}
|
||||
|
||||
TEST(Core_SmartPtr, Ref_Copy) {
|
||||
auto ref1 = MakeRef<TestStruct>();
|
||||
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<TestStruct>();
|
||||
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<TestStruct>();
|
||||
EXPECT_NE(unique, nullptr);
|
||||
EXPECT_EQ(unique->value, 100);
|
||||
}
|
||||
|
||||
TEST(Core_SmartPtr, UniqueRef_Ownership) {
|
||||
auto unique1 = MakeUnique<TestStruct>();
|
||||
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<int, std::string> 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<int> event;
|
||||
std::vector<int> 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
|
||||
31
tests/memory/CMakeLists.txt
Normal file
31
tests/memory/CMakeLists.txt
Normal file
@@ -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)
|
||||
77
tests/memory/test_linear_allocator.cpp
Normal file
77
tests/memory/test_linear_allocator.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <XCEngine/Memory/LinearAllocator.h>
|
||||
#include <cstring>
|
||||
|
||||
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<uintptr_t>(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
|
||||
44
tests/memory/test_memory_manager.cpp
Normal file
44
tests/memory/test_memory_manager.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <XCEngine/Memory/MemoryManager.h>
|
||||
|
||||
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
|
||||
83
tests/memory/test_pool_allocator.cpp
Normal file
83
tests/memory/test_pool_allocator.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <XCEngine/Memory/PoolAllocator.h>
|
||||
|
||||
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
|
||||
8
tests/run_tests.bat
Normal file
8
tests/run_tests.bat
Normal file
@@ -0,0 +1,8 @@
|
||||
@echo off
|
||||
echo Running XCEngine Tests...
|
||||
echo.
|
||||
|
||||
ctest --test-dir tests/build -C Debug --output-on-failure
|
||||
|
||||
echo.
|
||||
pause
|
||||
31
tests/threading/CMakeLists.txt
Normal file
31
tests/threading/CMakeLists.txt
Normal file
@@ -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)
|
||||
41
tests/threading/test_mutex.cpp
Normal file
41
tests/threading/test_mutex.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <XCEngine/Threading/Mutex.h>
|
||||
|
||||
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
|
||||
32
tests/threading/test_spinlock.cpp
Normal file
32
tests/threading/test_spinlock.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <XCEngine/Threading/SpinLock.h>
|
||||
|
||||
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
|
||||
59
tests/threading/test_task.cpp
Normal file
59
tests/threading/test_task.cpp
Normal file
@@ -0,0 +1,59 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <XCEngine/Threading/Task.h>
|
||||
#include <XCEngine/Threading/LambdaTask.h>
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user