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
|
||||
Reference in New Issue
Block a user