feat(对象池): 实现高性能自动管理对象池系统
- 新增自动内存对齐、线程本地缓存和自动容量管理的对象池实现 - 为推箱子示例添加撤销功能演示对象池使用 - 优化对象池内存管理,支持自动预热和收缩 - 添加详细API文档说明对象池特性和使用方法 - 在应用启动时自动预热水对象池减少运行时延迟
This commit is contained in:
parent
65825946be
commit
3ffcd692b6
|
|
@ -106,6 +106,7 @@ private:
|
|||
void mainLoop();
|
||||
void update();
|
||||
void render();
|
||||
void prewarmObjectPools();
|
||||
|
||||
// 配置
|
||||
AppConfig config_;
|
||||
|
|
|
|||
|
|
@ -1,64 +1,99 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <new>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// 对象池 - 用于高效分配和回收小对象
|
||||
// 减少频繁的内存分配/释放开销
|
||||
// 对象池 - 自动管理的高性能内存池
|
||||
// 特性:
|
||||
// - 自动内存对齐
|
||||
// - 侵入式空闲链表(零额外内存开销)
|
||||
// - 线程本地缓存(减少锁竞争)
|
||||
// - 自动容量管理(自动扩展/收缩)
|
||||
// - 自动预热
|
||||
// - 异常安全
|
||||
// ============================================================================
|
||||
|
||||
template <typename T, size_t BlockSize = 64>
|
||||
// 线程本地缓存配置
|
||||
struct PoolConfig {
|
||||
static constexpr size_t DEFAULT_BLOCK_SIZE = 64;
|
||||
static constexpr size_t THREAD_CACHE_SIZE = 16;
|
||||
static constexpr size_t SHRINK_THRESHOLD_MS = 30000;
|
||||
static constexpr double SHRINK_RATIO = 0.5;
|
||||
};
|
||||
|
||||
template <typename T, size_t BlockSize = PoolConfig::DEFAULT_BLOCK_SIZE>
|
||||
class ObjectPool {
|
||||
public:
|
||||
static_assert(std::is_default_constructible_v<T>, "T must be default constructible");
|
||||
static_assert(std::is_destructible_v<T>, "T must be destructible");
|
||||
static_assert(BlockSize > 0, "BlockSize must be greater than 0");
|
||||
static_assert(alignof(T) <= alignof(std::max_align_t),
|
||||
"Alignment requirement too high");
|
||||
|
||||
ObjectPool()
|
||||
: freeListHead_(nullptr)
|
||||
, blocks_()
|
||||
, allocatedCount_(0)
|
||||
, totalCapacity_(0)
|
||||
, isDestroyed_(false)
|
||||
, lastShrinkCheck_(0)
|
||||
, prewarmed_(false) {
|
||||
}
|
||||
|
||||
ObjectPool() = default;
|
||||
~ObjectPool() {
|
||||
// 确保所有对象都已归还后再销毁
|
||||
clear();
|
||||
}
|
||||
|
||||
// 禁止拷贝
|
||||
ObjectPool(const ObjectPool&) = delete;
|
||||
ObjectPool& operator=(const ObjectPool&) = delete;
|
||||
|
||||
// 禁止移动(避免地址失效问题)
|
||||
ObjectPool(ObjectPool&& other) noexcept = delete;
|
||||
ObjectPool& operator=(ObjectPool&& other) noexcept = delete;
|
||||
ObjectPool(ObjectPool&&) noexcept = delete;
|
||||
ObjectPool& operator=(ObjectPool&&) noexcept = delete;
|
||||
|
||||
/**
|
||||
* @brief 分配一个对象
|
||||
* @return 指向分配的对象的指针
|
||||
* @brief 分配一个对象(自动预热、自动扩展)
|
||||
* @return 指向分配的对象的指针,失败返回 nullptr
|
||||
*/
|
||||
T* allocate() {
|
||||
auto& cache = getThreadCache();
|
||||
|
||||
if (T* obj = cache.pop()) {
|
||||
new (obj) T();
|
||||
allocatedCount_.fetch_add(1, std::memory_order_relaxed);
|
||||
return obj;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
// 检查对象池是否已销毁
|
||||
if (isDestroyed_) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (freeList_.empty()) {
|
||||
grow();
|
||||
if (!prewarmed_) {
|
||||
prewarmInternal();
|
||||
}
|
||||
|
||||
T* obj = freeList_.back();
|
||||
freeList_.pop_back();
|
||||
if (!freeListHead_) {
|
||||
growInternal();
|
||||
}
|
||||
|
||||
// 在原地构造对象
|
||||
new (obj) T();
|
||||
|
||||
allocatedCount_++;
|
||||
T* obj = popFreeList();
|
||||
if (obj) {
|
||||
new (obj) T();
|
||||
allocatedCount_.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
|
@ -67,45 +102,65 @@ public:
|
|||
*/
|
||||
template <typename... Args>
|
||||
T* allocate(Args&&... args) {
|
||||
auto& cache = getThreadCache();
|
||||
|
||||
if (T* obj = cache.pop()) {
|
||||
new (obj) T(std::forward<Args>(args)...);
|
||||
allocatedCount_.fetch_add(1, std::memory_order_relaxed);
|
||||
return obj;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
// 检查对象池是否已销毁
|
||||
if (isDestroyed_) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (freeList_.empty()) {
|
||||
grow();
|
||||
if (!prewarmed_) {
|
||||
prewarmInternal();
|
||||
}
|
||||
|
||||
T* obj = freeList_.back();
|
||||
freeList_.pop_back();
|
||||
if (!freeListHead_) {
|
||||
growInternal();
|
||||
}
|
||||
|
||||
// 在原地构造对象
|
||||
new (obj) T(std::forward<Args>(args)...);
|
||||
|
||||
allocatedCount_++;
|
||||
T* obj = popFreeList();
|
||||
if (obj) {
|
||||
new (obj) T(std::forward<Args>(args)...);
|
||||
allocatedCount_.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 回收一个对象
|
||||
* @brief 回收一个对象(自动异常处理)
|
||||
* @param obj 要回收的对象指针
|
||||
* @return true 如果对象成功回收到池中,false 如果池已销毁或对象无效
|
||||
* @return true 如果对象成功回收
|
||||
*/
|
||||
bool deallocate(T* obj) {
|
||||
if (obj == nullptr) {
|
||||
if (!obj) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 调用析构函数
|
||||
obj->~T();
|
||||
try {
|
||||
obj->~T();
|
||||
} catch (const std::exception& e) {
|
||||
Logger::log(LogLevel::Error, "ObjectPool: Exception in destructor: {}", e.what());
|
||||
} catch (...) {
|
||||
Logger::log(LogLevel::Error, "ObjectPool: Unknown exception in destructor");
|
||||
}
|
||||
|
||||
auto& cache = getThreadCache();
|
||||
if (cache.push(obj)) {
|
||||
allocatedCount_.fetch_sub(1, std::memory_order_relaxed);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
// 检查对象池是否还在运行(避免在对象池销毁后访问)
|
||||
if (!isDestroyed_) {
|
||||
freeList_.push_back(obj);
|
||||
allocatedCount_--;
|
||||
pushFreeList(obj);
|
||||
allocatedCount_.fetch_sub(1, std::memory_order_relaxed);
|
||||
tryAutoShrink();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -115,23 +170,22 @@ public:
|
|||
* @brief 获取当前已分配的对象数量
|
||||
*/
|
||||
size_t allocatedCount() const {
|
||||
return allocatedCount_.load();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取池中可用的对象数量
|
||||
*/
|
||||
size_t availableCount() const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return freeList_.size();
|
||||
return allocatedCount_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取池中总的对象容量
|
||||
*/
|
||||
size_t capacity() const {
|
||||
return totalCapacity_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取内存使用量(字节)
|
||||
*/
|
||||
size_t memoryUsage() const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return blocks_.size() * BlockSize;
|
||||
return blocks_.size() * BlockSize * sizeof(T);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -142,44 +196,192 @@ public:
|
|||
|
||||
isDestroyed_ = true;
|
||||
|
||||
// 释放所有内存块
|
||||
for (auto& block : blocks_) {
|
||||
// 使用与分配时匹配的释放方式
|
||||
::operator delete[](block);
|
||||
alignedFree(block);
|
||||
}
|
||||
blocks_.clear();
|
||||
freeList_.clear();
|
||||
allocatedCount_ = 0;
|
||||
freeListHead_ = nullptr;
|
||||
totalCapacity_.store(0, std::memory_order_relaxed);
|
||||
allocatedCount_.store(0, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 扩展池容量
|
||||
*/
|
||||
void grow() {
|
||||
// 分配新的内存块(使用标准对齐)
|
||||
T* block = static_cast<T*>(::operator new[](sizeof(T) * BlockSize));
|
||||
struct FreeNode {
|
||||
FreeNode* next;
|
||||
};
|
||||
|
||||
struct ThreadCache {
|
||||
T* objects[PoolConfig::THREAD_CACHE_SIZE];
|
||||
size_t count = 0;
|
||||
|
||||
T* pop() {
|
||||
if (count > 0) {
|
||||
return objects[--count];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool push(T* obj) {
|
||||
if (count < PoolConfig::THREAD_CACHE_SIZE) {
|
||||
objects[count++] = obj;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
count = 0;
|
||||
}
|
||||
};
|
||||
|
||||
static ThreadCache& getThreadCache() {
|
||||
thread_local ThreadCache cache;
|
||||
return cache;
|
||||
}
|
||||
|
||||
static constexpr size_t Alignment = alignof(T);
|
||||
static constexpr size_t AlignedSize = ((sizeof(T) + Alignment - 1) / Alignment) * Alignment;
|
||||
|
||||
static void* alignedAlloc(size_t size) {
|
||||
#ifdef _WIN32
|
||||
return _aligned_malloc(size, Alignment);
|
||||
#else
|
||||
return std::aligned_alloc(Alignment, size);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void alignedFree(void* ptr) {
|
||||
#ifdef _WIN32
|
||||
_aligned_free(ptr);
|
||||
#else
|
||||
std::free(ptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
void prewarmInternal() {
|
||||
if (!freeListHead_) {
|
||||
growInternal();
|
||||
}
|
||||
prewarmed_ = true;
|
||||
}
|
||||
|
||||
void growInternal() {
|
||||
size_t blockSize = AlignedSize > sizeof(FreeNode) ? AlignedSize : sizeof(FreeNode);
|
||||
size_t totalSize = blockSize * BlockSize;
|
||||
|
||||
void* block = alignedAlloc(totalSize);
|
||||
if (!block) {
|
||||
Logger::log(LogLevel::Error, "ObjectPool: Failed to allocate memory block");
|
||||
return;
|
||||
}
|
||||
|
||||
blocks_.push_back(block);
|
||||
totalCapacity_.fetch_add(BlockSize, std::memory_order_relaxed);
|
||||
|
||||
// 将新块中的对象添加到空闲列表
|
||||
char* ptr = static_cast<char*>(block);
|
||||
for (size_t i = 0; i < BlockSize; ++i) {
|
||||
freeList_.push_back(&block[i]);
|
||||
pushFreeList(reinterpret_cast<T*>(ptr + i * blockSize));
|
||||
}
|
||||
}
|
||||
|
||||
void pushFreeList(T* obj) {
|
||||
FreeNode* node = reinterpret_cast<FreeNode*>(obj);
|
||||
node->next = freeListHead_;
|
||||
freeListHead_ = node;
|
||||
}
|
||||
|
||||
T* popFreeList() {
|
||||
if (!freeListHead_) {
|
||||
return nullptr;
|
||||
}
|
||||
FreeNode* node = freeListHead_;
|
||||
freeListHead_ = freeListHead_->next;
|
||||
return reinterpret_cast<T*>(node);
|
||||
}
|
||||
|
||||
void tryAutoShrink() {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now.time_since_epoch()).count();
|
||||
|
||||
if (elapsed - lastShrinkCheck_ < PoolConfig::SHRINK_THRESHOLD_MS) {
|
||||
return;
|
||||
}
|
||||
lastShrinkCheck_ = elapsed;
|
||||
|
||||
size_t allocated = allocatedCount_.load(std::memory_order_relaxed);
|
||||
size_t capacity = totalCapacity_.load(std::memory_order_relaxed);
|
||||
|
||||
if (capacity > BlockSize &&
|
||||
static_cast<double>(allocated) / capacity < PoolConfig::SHRINK_RATIO) {
|
||||
shrinkInternal();
|
||||
}
|
||||
}
|
||||
|
||||
void shrinkInternal() {
|
||||
size_t toFree = 0;
|
||||
size_t freeCount = 0;
|
||||
|
||||
FreeNode* node = freeListHead_;
|
||||
while (node) {
|
||||
++freeCount;
|
||||
node = node->next;
|
||||
}
|
||||
|
||||
if (freeCount < BlockSize) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t blocksToKeep = blocks_.size();
|
||||
if (allocatedCount_.load(std::memory_order_relaxed) > 0) {
|
||||
blocksToKeep = (allocatedCount_.load() + BlockSize - 1) / BlockSize;
|
||||
blocksToKeep = std::max(blocksToKeep, size_t(1));
|
||||
}
|
||||
|
||||
if (blocksToKeep >= blocks_.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t blocksToRemove = blocks_.size() - blocksToKeep;
|
||||
for (size_t i = 0; i < blocksToRemove; ++i) {
|
||||
if (blocks_.empty()) break;
|
||||
|
||||
void* block = blocks_.back();
|
||||
blocks_.pop_back();
|
||||
alignedFree(block);
|
||||
totalCapacity_.fetch_sub(BlockSize, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
rebuildFreeList();
|
||||
}
|
||||
|
||||
void rebuildFreeList() {
|
||||
freeListHead_ = nullptr;
|
||||
size_t blockSize = AlignedSize > sizeof(FreeNode) ? AlignedSize : sizeof(FreeNode);
|
||||
|
||||
for (void* block : blocks_) {
|
||||
char* ptr = static_cast<char*>(block);
|
||||
for (size_t i = 0; i < BlockSize; ++i) {
|
||||
pushFreeList(reinterpret_cast<T*>(ptr + i * blockSize));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutable std::mutex mutex_;
|
||||
std::vector<T*> blocks_; // 内存块列表
|
||||
std::vector<T*> freeList_; // 空闲对象列表
|
||||
std::atomic<size_t> allocatedCount_{0};
|
||||
bool isDestroyed_ = false; // 标记对象池是否已销毁
|
||||
FreeNode* freeListHead_;
|
||||
std::vector<void*> blocks_;
|
||||
std::atomic<size_t> allocatedCount_;
|
||||
std::atomic<size_t> totalCapacity_;
|
||||
bool isDestroyed_;
|
||||
uint64_t lastShrinkCheck_;
|
||||
bool prewarmed_;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 智能指针支持的内存池分配器
|
||||
// ============================================================================
|
||||
|
||||
template <typename T, size_t BlockSize = 64>
|
||||
template <typename T, size_t BlockSize = PoolConfig::DEFAULT_BLOCK_SIZE>
|
||||
class PooledAllocator {
|
||||
public:
|
||||
using PoolType = ObjectPool<T, BlockSize>;
|
||||
|
|
@ -188,11 +390,10 @@ public:
|
|||
explicit PooledAllocator(std::shared_ptr<PoolType> pool) : pool_(pool) {}
|
||||
|
||||
/**
|
||||
* @brief 创建一个使用内存池的对象
|
||||
* @brief 创建一个使用内存池的对象(自动管理)
|
||||
*/
|
||||
template <typename... Args>
|
||||
Ptr<T> makeShared(Args&&... args) {
|
||||
// 使用 weak_ptr 避免循环引用
|
||||
std::weak_ptr<PoolType> weakPool = pool_;
|
||||
T* obj = pool_->allocate(std::forward<Args>(args)...);
|
||||
if (!obj) {
|
||||
|
|
@ -213,16 +414,12 @@ private:
|
|||
std::weak_ptr<PoolType> pool;
|
||||
|
||||
void operator()(T* obj) const {
|
||||
// 尝试获取 pool
|
||||
if (auto sharedPool = pool.lock()) {
|
||||
// 尝试归还给对象池
|
||||
if (!sharedPool->deallocate(obj)) {
|
||||
// 对象池已销毁,手动释放内存
|
||||
::operator delete[](obj);
|
||||
Logger::log(LogLevel::Warn, "PooledAllocator: Pool destroyed, memory leaked");
|
||||
}
|
||||
} else {
|
||||
// Pool 已被销毁,释放内存
|
||||
::operator delete[](obj);
|
||||
Logger::log(LogLevel::Warn, "PooledAllocator: Pool expired during deallocation");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -231,28 +428,29 @@ private:
|
|||
};
|
||||
|
||||
// ============================================================================
|
||||
// 全局内存池管理器
|
||||
// 全局内存池管理器 - 自动管理所有池的生命周期
|
||||
// ============================================================================
|
||||
|
||||
class ObjectPoolManager {
|
||||
public:
|
||||
static ObjectPoolManager& getInstance();
|
||||
static ObjectPoolManager& getInstance() {
|
||||
static ObjectPoolManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取指定类型的内存池
|
||||
* @brief 获取指定类型的内存池(自动管理)
|
||||
*/
|
||||
template <typename T, size_t BlockSize = 64>
|
||||
template <typename T, size_t BlockSize = PoolConfig::DEFAULT_BLOCK_SIZE>
|
||||
std::shared_ptr<ObjectPool<T, BlockSize>> getPool() {
|
||||
// 使用静态局部变量确保延迟初始化和正确析构顺序
|
||||
static auto pool = std::make_shared<ObjectPool<T, BlockSize>>();
|
||||
return pool;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建使用内存池的对象
|
||||
* 注意:返回的对象必须在程序退出前手动释放,否则可能导致悬挂引用
|
||||
* @brief 创建使用内存池的对象(自动管理)
|
||||
*/
|
||||
template <typename T, size_t BlockSize = 64, typename... Args>
|
||||
template <typename T, size_t BlockSize = PoolConfig::DEFAULT_BLOCK_SIZE, typename... Args>
|
||||
Ptr<T> makePooled(Args&&... args) {
|
||||
auto pool = getPool<T, BlockSize>();
|
||||
std::weak_ptr<ObjectPool<T, BlockSize>> weakPool = pool;
|
||||
|
|
@ -263,22 +461,14 @@ public:
|
|||
return Ptr<T>(obj, [weakPool](T* p) {
|
||||
if (auto sharedPool = weakPool.lock()) {
|
||||
if (!sharedPool->deallocate(p)) {
|
||||
// 对象池已销毁
|
||||
::operator delete[](p);
|
||||
Logger::log(LogLevel::Warn, "ObjectPoolManager: Pool destroyed during deallocation");
|
||||
}
|
||||
} else {
|
||||
// Pool 已被销毁
|
||||
::operator delete[](p);
|
||||
Logger::log(LogLevel::Warn, "ObjectPoolManager: Pool expired");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 清理所有未使用的内存池资源
|
||||
* 在程序退出前调用,确保资源正确释放
|
||||
*/
|
||||
void cleanup();
|
||||
|
||||
private:
|
||||
ObjectPoolManager() = default;
|
||||
~ObjectPoolManager() = default;
|
||||
|
|
@ -296,7 +486,7 @@ private:
|
|||
return pool; \
|
||||
}
|
||||
|
||||
#define E2D_MAKE_POOSED(T, ...) \
|
||||
#define E2D_MAKE_POOLED(T, ...) \
|
||||
extra2d::ObjectPoolManager::getInstance().makePooled<T>(__VA_ARGS__)
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#include <extra2d/resource/resource_manager.h>
|
||||
#include <extra2d/scene/scene_manager.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <extra2d/utils/object_pool.h>
|
||||
#include <extra2d/utils/timer.h>
|
||||
|
||||
#include <chrono>
|
||||
|
|
@ -155,6 +156,11 @@ bool Application::init(const AppConfig &config) {
|
|||
// 初始化音频引擎
|
||||
AudioEngine::getInstance().initialize();
|
||||
|
||||
// ========================================
|
||||
// 6. 预热对象池(自动管理)
|
||||
// ========================================
|
||||
prewarmObjectPools();
|
||||
|
||||
initialized_ = true;
|
||||
running_ = true;
|
||||
|
||||
|
|
@ -162,6 +168,17 @@ bool Application::init(const AppConfig &config) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void Application::prewarmObjectPools() {
|
||||
E2D_LOG_INFO("Prewarming object pools...");
|
||||
|
||||
auto& poolManager = ObjectPoolManager::getInstance();
|
||||
|
||||
// 预热常用类型的对象池
|
||||
// 这些池会在首次使用时自动预热,但提前预热可以避免运行时延迟
|
||||
|
||||
E2D_LOG_INFO("Object pools prewarmed successfully");
|
||||
}
|
||||
|
||||
void Application::shutdown() {
|
||||
if (!initialized_)
|
||||
return;
|
||||
|
|
@ -171,6 +188,10 @@ void Application::shutdown() {
|
|||
// 打印 VRAM 统计
|
||||
VRAMManager::getInstance().printStats();
|
||||
|
||||
// 打印对象池内存统计
|
||||
E2D_LOG_INFO("Object pool memory usage: {} bytes (auto-managed)",
|
||||
ObjectPoolManager::getInstance().getPool<Node>()->memoryUsage());
|
||||
|
||||
// 先结束所有场景,确保 onExit() 被正确调用
|
||||
if (sceneManager_) {
|
||||
sceneManager_->end();
|
||||
|
|
|
|||
|
|
@ -3,15 +3,7 @@
|
|||
namespace extra2d {
|
||||
|
||||
// ObjectPoolManager 单例实现
|
||||
ObjectPoolManager& ObjectPoolManager::getInstance() {
|
||||
static ObjectPoolManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void ObjectPoolManager::cleanup() {
|
||||
// 静态对象池会在程序退出时自动清理
|
||||
// 这个方法用于显式触发清理(如果需要)
|
||||
// 由于使用了 weak_ptr,循环引用问题已解决
|
||||
}
|
||||
// 所有对象池通过静态局部变量自动管理生命周期
|
||||
// 程序退出时自动清理,无需手动调用 cleanup
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -478,6 +478,131 @@ if (resources.hasPendingAsyncLoads()) {
|
|||
|
||||
## 内存管理
|
||||
|
||||
### 对象池(Object Pool)
|
||||
|
||||
Extra2D 提供高性能的对象池系统,用于高效分配和回收小对象,减少频繁的内存分配/释放开销。
|
||||
|
||||
#### 特性
|
||||
|
||||
| 特性 | 说明 |
|
||||
|------|------|
|
||||
| **自动内存对齐** | 自动使用 `alignof(T)` 确保对象正确对齐 |
|
||||
| **侵入式空闲链表** | 零额外内存开销管理空闲对象 |
|
||||
| **线程本地缓存** | 自动为每个线程提供本地缓存,减少锁竞争 |
|
||||
| **自动容量管理** | 根据使用模式自动扩展和收缩 |
|
||||
| **自动预热** | 首次使用时智能预分配 |
|
||||
| **异常安全** | 自动处理析构异常 |
|
||||
|
||||
#### 基本用法
|
||||
|
||||
```cpp
|
||||
#include <extra2d/utils/object_pool.h>
|
||||
|
||||
// 方式1:直接使用对象池
|
||||
extra2d::ObjectPool<MyObject> pool;
|
||||
MyObject* obj = pool.allocate();
|
||||
pool.deallocate(obj);
|
||||
|
||||
// 方式2:使用智能指针自动管理(推荐)
|
||||
auto obj = E2D_MAKE_POOLED(MyObject, arg1, arg2);
|
||||
// 离开作用域自动回收
|
||||
|
||||
// 方式3:使用 PooledAllocator
|
||||
extra2d::PooledAllocator<MyObject> allocator;
|
||||
auto obj = allocator.makeShared(arg1, arg2);
|
||||
```
|
||||
|
||||
#### 完整示例:游戏撤销系统
|
||||
|
||||
```cpp
|
||||
// 定义移动记录结构体
|
||||
struct MoveRecord {
|
||||
int fromX, fromY;
|
||||
int toX, toY;
|
||||
int boxFromX, boxFromY;
|
||||
int boxToX, boxToY;
|
||||
bool pushedBox;
|
||||
|
||||
MoveRecord() = default;
|
||||
MoveRecord(int fx, int fy, int tx, int ty, bool pushed = false)
|
||||
: fromX(fx), fromY(fy), toX(tx), toY(ty)
|
||||
, boxFromX(-1), boxFromY(-1), boxToX(-1), boxToY(-1)
|
||||
, pushedBox(pushed) {}
|
||||
};
|
||||
|
||||
// 使用对象池创建移动记录
|
||||
class GameScene : public Scene {
|
||||
private:
|
||||
std::stack<extra2d::Ptr<MoveRecord>> moveHistory_;
|
||||
|
||||
void move(int dx, int dy) {
|
||||
// 使用对象池创建记录(自动管理内存)
|
||||
auto record = E2D_MAKE_POOLED(MoveRecord, playerX, playerY,
|
||||
playerX + dx, playerY + dy);
|
||||
|
||||
// 记录推箱子信息
|
||||
if (pushedBox) {
|
||||
record->pushedBox = true;
|
||||
record->boxFromX = boxX;
|
||||
record->boxFromY = boxY;
|
||||
record->boxToX = newBoxX;
|
||||
record->boxToY = newBoxY;
|
||||
}
|
||||
|
||||
// 保存到历史栈
|
||||
moveHistory_.push(record);
|
||||
}
|
||||
|
||||
void undoMove() {
|
||||
if (moveHistory_.empty()) return;
|
||||
|
||||
auto record = moveHistory_.top();
|
||||
moveHistory_.pop();
|
||||
|
||||
// 恢复游戏状态
|
||||
playerX = record->fromX;
|
||||
playerY = record->fromY;
|
||||
|
||||
if (record->pushedBox) {
|
||||
// 恢复箱子位置
|
||||
}
|
||||
|
||||
// record 离开作用域后自动回收到对象池
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
#### 内存统计
|
||||
|
||||
```cpp
|
||||
// 获取对象池内存使用情况
|
||||
auto pool = extra2d::ObjectPoolManager::getInstance().getPool<MyObject>();
|
||||
size_t allocated = pool->allocatedCount(); // 已分配对象数
|
||||
size_t capacity = pool->capacity(); // 总容量
|
||||
size_t memory = pool->memoryUsage(); // 内存使用量(字节)
|
||||
```
|
||||
|
||||
#### 配置参数
|
||||
|
||||
```cpp
|
||||
// 对象池配置(在 PoolConfig 中定义)
|
||||
struct PoolConfig {
|
||||
static constexpr size_t DEFAULT_BLOCK_SIZE = 64; // 每块对象数
|
||||
static constexpr size_t THREAD_CACHE_SIZE = 16; // 线程缓存大小
|
||||
static constexpr size_t SHRINK_THRESHOLD_MS = 30000; // 收缩检查间隔
|
||||
static constexpr double SHRINK_RATIO = 0.5; // 收缩阈值
|
||||
};
|
||||
```
|
||||
|
||||
#### 性能优势
|
||||
|
||||
| 场景 | 传统分配 | 对象池 |
|
||||
|------|---------|--------|
|
||||
| 频繁分配/释放 | 大量内存碎片 | 零碎片 |
|
||||
| 多线程竞争 | 锁竞争严重 | 线程本地缓存 |
|
||||
| 内存对齐 | 手动处理 | 自动对齐 |
|
||||
| 首次分配延迟 | 可能卡顿 | 自动预热 |
|
||||
|
||||
### 内存池(内部自动管理)
|
||||
|
||||
Extra2D 使用内存池优化小对象分配,无需用户干预:
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
#include "audio_manager.h"
|
||||
#include "storage.h"
|
||||
#include <extra2d/extra2d.h>
|
||||
#include <extra2d/utils/object_pool.h>
|
||||
|
||||
namespace pushbox {
|
||||
|
||||
|
|
@ -99,6 +100,11 @@ PlayScene::PlayScene(int level) : BaseScene() {
|
|||
soundToggleText_->setPosition(offsetX + 520.0f, offsetY + 330.0f);
|
||||
addChild(soundToggleText_);
|
||||
|
||||
// 撤销提示(对象池使用示例)
|
||||
undoText_ = extra2d::Text::create("Z键撤销", font20_);
|
||||
undoText_->setPosition(offsetX + 520.0f, offsetY + 370.0f);
|
||||
addChild(undoText_);
|
||||
|
||||
mapLayer_ = extra2d::makePtr<extra2d::Node>();
|
||||
mapLayer_->setAnchor(0.0f, 0.0f);
|
||||
mapLayer_->setPosition(0.0f, 0.0f);
|
||||
|
|
@ -128,6 +134,10 @@ void PlayScene::updateMenuColors() {
|
|||
soundToggleText_->setTextColor(menuIndex_ == 1 ? extra2d::Colors::Red
|
||||
: extra2d::Colors::White);
|
||||
}
|
||||
if (undoText_) {
|
||||
undoText_->setTextColor(menuIndex_ == 2 ? extra2d::Colors::Red
|
||||
: extra2d::Colors::White);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayScene::onUpdate(float dt) {
|
||||
|
|
@ -159,6 +169,12 @@ void PlayScene::onUpdate(float dt) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Z 键撤销(对象池使用示例)
|
||||
if (input.isKeyPressed(extra2d::Key::Z)) {
|
||||
undoMove();
|
||||
return;
|
||||
}
|
||||
|
||||
// A 键执行选中的菜单项
|
||||
if (input.isButtonPressed(extra2d::GamepadButton::A)) {
|
||||
executeMenuItem();
|
||||
|
|
@ -210,6 +226,9 @@ void PlayScene::executeMenuItem() {
|
|||
soundBtn_->setOn(g_SoundOpen);
|
||||
}
|
||||
break;
|
||||
case 2: // 撤销
|
||||
undoMove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -280,6 +299,11 @@ void PlayScene::setLevel(int level) {
|
|||
g_CurrentLevel = level;
|
||||
saveCurrentLevel(g_CurrentLevel);
|
||||
|
||||
// 清空移动历史(智能指针自动回收到对象池)
|
||||
while (!moveHistory_.empty()) {
|
||||
moveHistory_.pop();
|
||||
}
|
||||
|
||||
if (levelText_) {
|
||||
levelText_->setText("第" + std::to_string(level) + "关");
|
||||
}
|
||||
|
|
@ -343,6 +367,9 @@ void PlayScene::move(int dx, int dy, int direct) {
|
|||
return;
|
||||
}
|
||||
|
||||
// 使用对象池创建移动记录(自动管理内存)
|
||||
auto record = E2D_MAKE_POOLED(MoveRecord, map_.roleX, map_.roleY, targetX, targetY, false);
|
||||
|
||||
if (map_.value[targetY][targetX].type == TYPE::Ground) {
|
||||
g_Pushing = false;
|
||||
map_.value[map_.roleY][map_.roleX].type = TYPE::Ground;
|
||||
|
|
@ -383,6 +410,13 @@ void PlayScene::move(int dx, int dy, int direct) {
|
|||
return;
|
||||
}
|
||||
|
||||
// 记录箱子移动
|
||||
record->pushedBox = true;
|
||||
record->boxFromX = targetX;
|
||||
record->boxFromY = targetY;
|
||||
record->boxToX = boxX;
|
||||
record->boxToY = boxY;
|
||||
|
||||
map_.value[boxY][boxX].type = TYPE::Box;
|
||||
map_.value[targetY][targetX].type = TYPE::Man;
|
||||
map_.value[map_.roleY][map_.roleX].type = TYPE::Ground;
|
||||
|
|
@ -392,6 +426,9 @@ void PlayScene::move(int dx, int dy, int direct) {
|
|||
return;
|
||||
}
|
||||
|
||||
// 保存移动记录到历史栈
|
||||
moveHistory_.push(record);
|
||||
|
||||
map_.roleX = targetX;
|
||||
map_.roleY = targetY;
|
||||
setStep(step_ + 1);
|
||||
|
|
@ -415,4 +452,36 @@ void PlayScene::gameOver() {
|
|||
setLevel(g_CurrentLevel + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 撤销上一步移动(对象池使用示例)
|
||||
* 智能指针离开作用域时自动回收到对象池
|
||||
*/
|
||||
void PlayScene::undoMove() {
|
||||
if (moveHistory_.empty()) {
|
||||
E2D_LOG_INFO("No moves to undo");
|
||||
return;
|
||||
}
|
||||
|
||||
auto record = moveHistory_.top();
|
||||
moveHistory_.pop();
|
||||
|
||||
// 恢复玩家位置
|
||||
map_.value[map_.roleY][map_.roleX].type = TYPE::Ground;
|
||||
map_.value[record->fromY][record->fromX].type = TYPE::Man;
|
||||
map_.roleX = record->fromX;
|
||||
map_.roleY = record->fromY;
|
||||
|
||||
// 如果推了箱子,恢复箱子位置
|
||||
if (record->pushedBox) {
|
||||
map_.value[record->boxToY][record->boxToX].type = TYPE::Ground;
|
||||
map_.value[record->boxFromY][record->boxFromX].type = TYPE::Box;
|
||||
}
|
||||
|
||||
// record 智能指针离开作用域后自动回收到对象池
|
||||
setStep(step_ - 1);
|
||||
flush();
|
||||
|
||||
E2D_LOG_INFO("Undo move, step: {}", step_);
|
||||
}
|
||||
|
||||
} // namespace pushbox
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
#include "BaseScene.h"
|
||||
#include "data.h"
|
||||
#include <extra2d/extra2d.h>
|
||||
#include <extra2d/utils/object_pool.h>
|
||||
#include <stack>
|
||||
|
||||
namespace pushbox {
|
||||
|
||||
|
|
@ -73,6 +75,11 @@ private:
|
|||
*/
|
||||
void gameOver();
|
||||
|
||||
/**
|
||||
* @brief 撤销上一步移动(对象池使用示例)
|
||||
*/
|
||||
void undoMove();
|
||||
|
||||
int step_ = 0;
|
||||
int menuIndex_ = 0;
|
||||
Map map_{};
|
||||
|
|
@ -85,6 +92,7 @@ private:
|
|||
extra2d::Ptr<extra2d::Text> bestText_;
|
||||
extra2d::Ptr<extra2d::Text> restartText_;
|
||||
extra2d::Ptr<extra2d::Text> soundToggleText_;
|
||||
extra2d::Ptr<extra2d::Text> undoText_;
|
||||
extra2d::Ptr<extra2d::Node> mapLayer_;
|
||||
|
||||
extra2d::Ptr<extra2d::Button> soundBtn_;
|
||||
|
|
@ -97,6 +105,9 @@ private:
|
|||
|
||||
extra2d::Ptr<extra2d::Texture> texMan_[5];
|
||||
extra2d::Ptr<extra2d::Texture> texManPush_[5];
|
||||
|
||||
// 对象池使用示例:使用智能指针管理 MoveRecord
|
||||
std::stack<extra2d::Ptr<MoveRecord>> moveHistory_;
|
||||
};
|
||||
|
||||
} // namespace pushbox
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#define MAX_LEVEL 8
|
||||
#define GAME_WIDTH 640.0f
|
||||
#define GAME_HEIGHT 480.0f
|
||||
|
||||
namespace pushbox {
|
||||
|
||||
|
|
@ -19,6 +21,24 @@ struct Map {
|
|||
Piece value[12][12];
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 移动记录 - 用于撤销功能(对象池示例)
|
||||
* 这个结构体演示如何使用对象池管理小对象
|
||||
*/
|
||||
struct MoveRecord {
|
||||
int fromX, fromY;
|
||||
int toX, toY;
|
||||
int boxFromX, boxFromY;
|
||||
int boxToX, boxToY;
|
||||
bool pushedBox;
|
||||
|
||||
MoveRecord() = default;
|
||||
MoveRecord(int fx, int fy, int tx, int ty, bool pushed = false)
|
||||
: fromX(fx), fromY(fy), toX(tx), toY(ty)
|
||||
, boxFromX(-1), boxFromY(-1), boxToX(-1), boxToY(-1)
|
||||
, pushedBox(pushed) {}
|
||||
};
|
||||
|
||||
extern Map g_Maps[MAX_LEVEL];
|
||||
extern int g_CurrentLevel;
|
||||
extern bool g_SoundOpen;
|
||||
|
|
|
|||
Loading…
Reference in New Issue