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