feat(对象池): 实现高性能自动管理对象池系统
- 新增自动内存对齐、线程本地缓存和自动容量管理的对象池实现 - 为推箱子示例添加撤销功能演示对象池使用 - 优化对象池内存管理,支持自动预热和收缩 - 添加详细API文档说明对象池特性和使用方法 - 在应用启动时自动预热水对象池减少运行时延迟
This commit is contained in:
parent
65825946be
commit
3ffcd692b6
|
|
@ -106,6 +106,7 @@ private:
|
||||||
void mainLoop();
|
void mainLoop();
|
||||||
void update();
|
void update();
|
||||||
void render();
|
void render();
|
||||||
|
void prewarmObjectPools();
|
||||||
|
|
||||||
// 配置
|
// 配置
|
||||||
AppConfig config_;
|
AppConfig config_;
|
||||||
|
|
|
||||||
|
|
@ -1,64 +1,99 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <extra2d/core/types.h>
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/utils/logger.h>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <new>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
namespace extra2d {
|
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 {
|
class ObjectPool {
|
||||||
public:
|
public:
|
||||||
static_assert(std::is_default_constructible_v<T>, "T must be default constructible");
|
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(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() {
|
~ObjectPool() {
|
||||||
// 确保所有对象都已归还后再销毁
|
|
||||||
clear();
|
clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 禁止拷贝
|
|
||||||
ObjectPool(const ObjectPool&) = delete;
|
ObjectPool(const ObjectPool&) = delete;
|
||||||
ObjectPool& operator=(const ObjectPool&) = delete;
|
ObjectPool& operator=(const ObjectPool&) = delete;
|
||||||
|
ObjectPool(ObjectPool&&) noexcept = delete;
|
||||||
// 禁止移动(避免地址失效问题)
|
ObjectPool& operator=(ObjectPool&&) noexcept = delete;
|
||||||
ObjectPool(ObjectPool&& other) noexcept = delete;
|
|
||||||
ObjectPool& operator=(ObjectPool&& other) noexcept = delete;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 分配一个对象
|
* @brief 分配一个对象(自动预热、自动扩展)
|
||||||
* @return 指向分配的对象的指针
|
* @return 指向分配的对象的指针,失败返回 nullptr
|
||||||
*/
|
*/
|
||||||
T* allocate() {
|
T* allocate() {
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
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_) {
|
if (isDestroyed_) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (freeList_.empty()) {
|
if (!prewarmed_) {
|
||||||
grow();
|
prewarmInternal();
|
||||||
}
|
}
|
||||||
|
|
||||||
T* obj = freeList_.back();
|
if (!freeListHead_) {
|
||||||
freeList_.pop_back();
|
growInternal();
|
||||||
|
}
|
||||||
|
|
||||||
// 在原地构造对象
|
T* obj = popFreeList();
|
||||||
new (obj) T();
|
if (obj) {
|
||||||
|
new (obj) T();
|
||||||
allocatedCount_++;
|
allocatedCount_.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -67,45 +102,65 @@ public:
|
||||||
*/
|
*/
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
T* allocate(Args&&... args) {
|
T* allocate(Args&&... args) {
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
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_) {
|
if (isDestroyed_) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (freeList_.empty()) {
|
if (!prewarmed_) {
|
||||||
grow();
|
prewarmInternal();
|
||||||
}
|
}
|
||||||
|
|
||||||
T* obj = freeList_.back();
|
if (!freeListHead_) {
|
||||||
freeList_.pop_back();
|
growInternal();
|
||||||
|
}
|
||||||
|
|
||||||
// 在原地构造对象
|
T* obj = popFreeList();
|
||||||
new (obj) T(std::forward<Args>(args)...);
|
if (obj) {
|
||||||
|
new (obj) T(std::forward<Args>(args)...);
|
||||||
allocatedCount_++;
|
allocatedCount_.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 回收一个对象
|
* @brief 回收一个对象(自动异常处理)
|
||||||
* @param obj 要回收的对象指针
|
* @param obj 要回收的对象指针
|
||||||
* @return true 如果对象成功回收到池中,false 如果池已销毁或对象无效
|
* @return true 如果对象成功回收
|
||||||
*/
|
*/
|
||||||
bool deallocate(T* obj) {
|
bool deallocate(T* obj) {
|
||||||
if (obj == nullptr) {
|
if (!obj) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调用析构函数
|
try {
|
||||||
obj->~T();
|
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_);
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
// 检查对象池是否还在运行(避免在对象池销毁后访问)
|
|
||||||
if (!isDestroyed_) {
|
if (!isDestroyed_) {
|
||||||
freeList_.push_back(obj);
|
pushFreeList(obj);
|
||||||
allocatedCount_--;
|
allocatedCount_.fetch_sub(1, std::memory_order_relaxed);
|
||||||
|
tryAutoShrink();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -115,23 +170,22 @@ public:
|
||||||
* @brief 获取当前已分配的对象数量
|
* @brief 获取当前已分配的对象数量
|
||||||
*/
|
*/
|
||||||
size_t allocatedCount() const {
|
size_t allocatedCount() const {
|
||||||
return allocatedCount_.load();
|
return allocatedCount_.load(std::memory_order_relaxed);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取池中可用的对象数量
|
|
||||||
*/
|
|
||||||
size_t availableCount() const {
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
return freeList_.size();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取池中总的对象容量
|
* @brief 获取池中总的对象容量
|
||||||
*/
|
*/
|
||||||
size_t capacity() const {
|
size_t capacity() const {
|
||||||
|
return totalCapacity_.load(std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取内存使用量(字节)
|
||||||
|
*/
|
||||||
|
size_t memoryUsage() const {
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
return blocks_.size() * BlockSize;
|
return blocks_.size() * BlockSize * sizeof(T);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -139,47 +193,195 @@ public:
|
||||||
*/
|
*/
|
||||||
void clear() {
|
void clear() {
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
|
||||||
isDestroyed_ = true;
|
isDestroyed_ = true;
|
||||||
|
|
||||||
// 释放所有内存块
|
|
||||||
for (auto& block : blocks_) {
|
for (auto& block : blocks_) {
|
||||||
// 使用与分配时匹配的释放方式
|
alignedFree(block);
|
||||||
::operator delete[](block);
|
|
||||||
}
|
}
|
||||||
blocks_.clear();
|
blocks_.clear();
|
||||||
freeList_.clear();
|
freeListHead_ = nullptr;
|
||||||
allocatedCount_ = 0;
|
totalCapacity_.store(0, std::memory_order_relaxed);
|
||||||
|
allocatedCount_.store(0, std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
struct FreeNode {
|
||||||
* @brief 扩展池容量
|
FreeNode* next;
|
||||||
*/
|
};
|
||||||
void grow() {
|
|
||||||
// 分配新的内存块(使用标准对齐)
|
struct ThreadCache {
|
||||||
T* block = static_cast<T*>(::operator new[](sizeof(T) * BlockSize));
|
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);
|
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) {
|
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_;
|
mutable std::mutex mutex_;
|
||||||
std::vector<T*> blocks_; // 内存块列表
|
FreeNode* freeListHead_;
|
||||||
std::vector<T*> freeList_; // 空闲对象列表
|
std::vector<void*> blocks_;
|
||||||
std::atomic<size_t> allocatedCount_{0};
|
std::atomic<size_t> allocatedCount_;
|
||||||
bool isDestroyed_ = false; // 标记对象池是否已销毁
|
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 {
|
class PooledAllocator {
|
||||||
public:
|
public:
|
||||||
using PoolType = ObjectPool<T, BlockSize>;
|
using PoolType = ObjectPool<T, BlockSize>;
|
||||||
|
|
@ -188,11 +390,10 @@ public:
|
||||||
explicit PooledAllocator(std::shared_ptr<PoolType> pool) : pool_(pool) {}
|
explicit PooledAllocator(std::shared_ptr<PoolType> pool) : pool_(pool) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 创建一个使用内存池的对象
|
* @brief 创建一个使用内存池的对象(自动管理)
|
||||||
*/
|
*/
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
Ptr<T> makeShared(Args&&... args) {
|
Ptr<T> makeShared(Args&&... args) {
|
||||||
// 使用 weak_ptr 避免循环引用
|
|
||||||
std::weak_ptr<PoolType> weakPool = pool_;
|
std::weak_ptr<PoolType> weakPool = pool_;
|
||||||
T* obj = pool_->allocate(std::forward<Args>(args)...);
|
T* obj = pool_->allocate(std::forward<Args>(args)...);
|
||||||
if (!obj) {
|
if (!obj) {
|
||||||
|
|
@ -213,16 +414,12 @@ private:
|
||||||
std::weak_ptr<PoolType> pool;
|
std::weak_ptr<PoolType> pool;
|
||||||
|
|
||||||
void operator()(T* obj) const {
|
void operator()(T* obj) const {
|
||||||
// 尝试获取 pool
|
|
||||||
if (auto sharedPool = pool.lock()) {
|
if (auto sharedPool = pool.lock()) {
|
||||||
// 尝试归还给对象池
|
|
||||||
if (!sharedPool->deallocate(obj)) {
|
if (!sharedPool->deallocate(obj)) {
|
||||||
// 对象池已销毁,手动释放内存
|
Logger::log(LogLevel::Warn, "PooledAllocator: Pool destroyed, memory leaked");
|
||||||
::operator delete[](obj);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Pool 已被销毁,释放内存
|
Logger::log(LogLevel::Warn, "PooledAllocator: Pool expired during deallocation");
|
||||||
::operator delete[](obj);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -231,28 +428,29 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 全局内存池管理器
|
// 全局内存池管理器 - 自动管理所有池的生命周期
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
class ObjectPoolManager {
|
class ObjectPoolManager {
|
||||||
public:
|
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() {
|
std::shared_ptr<ObjectPool<T, BlockSize>> getPool() {
|
||||||
// 使用静态局部变量确保延迟初始化和正确析构顺序
|
|
||||||
static auto pool = std::make_shared<ObjectPool<T, BlockSize>>();
|
static auto pool = std::make_shared<ObjectPool<T, BlockSize>>();
|
||||||
return pool;
|
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) {
|
Ptr<T> makePooled(Args&&... args) {
|
||||||
auto pool = getPool<T, BlockSize>();
|
auto pool = getPool<T, BlockSize>();
|
||||||
std::weak_ptr<ObjectPool<T, BlockSize>> weakPool = pool;
|
std::weak_ptr<ObjectPool<T, BlockSize>> weakPool = pool;
|
||||||
|
|
@ -263,22 +461,14 @@ public:
|
||||||
return Ptr<T>(obj, [weakPool](T* p) {
|
return Ptr<T>(obj, [weakPool](T* p) {
|
||||||
if (auto sharedPool = weakPool.lock()) {
|
if (auto sharedPool = weakPool.lock()) {
|
||||||
if (!sharedPool->deallocate(p)) {
|
if (!sharedPool->deallocate(p)) {
|
||||||
// 对象池已销毁
|
Logger::log(LogLevel::Warn, "ObjectPoolManager: Pool destroyed during deallocation");
|
||||||
::operator delete[](p);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Pool 已被销毁
|
Logger::log(LogLevel::Warn, "ObjectPoolManager: Pool expired");
|
||||||
::operator delete[](p);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 清理所有未使用的内存池资源
|
|
||||||
* 在程序退出前调用,确保资源正确释放
|
|
||||||
*/
|
|
||||||
void cleanup();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ObjectPoolManager() = default;
|
ObjectPoolManager() = default;
|
||||||
~ObjectPoolManager() = default;
|
~ObjectPoolManager() = default;
|
||||||
|
|
@ -296,7 +486,7 @@ private:
|
||||||
return pool; \
|
return pool; \
|
||||||
}
|
}
|
||||||
|
|
||||||
#define E2D_MAKE_POOSED(T, ...) \
|
#define E2D_MAKE_POOLED(T, ...) \
|
||||||
extra2d::ObjectPoolManager::getInstance().makePooled<T>(__VA_ARGS__)
|
extra2d::ObjectPoolManager::getInstance().makePooled<T>(__VA_ARGS__)
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
#include <extra2d/resource/resource_manager.h>
|
#include <extra2d/resource/resource_manager.h>
|
||||||
#include <extra2d/scene/scene_manager.h>
|
#include <extra2d/scene/scene_manager.h>
|
||||||
#include <extra2d/utils/logger.h>
|
#include <extra2d/utils/logger.h>
|
||||||
|
#include <extra2d/utils/object_pool.h>
|
||||||
#include <extra2d/utils/timer.h>
|
#include <extra2d/utils/timer.h>
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
@ -155,6 +156,11 @@ bool Application::init(const AppConfig &config) {
|
||||||
// 初始化音频引擎
|
// 初始化音频引擎
|
||||||
AudioEngine::getInstance().initialize();
|
AudioEngine::getInstance().initialize();
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// 6. 预热对象池(自动管理)
|
||||||
|
// ========================================
|
||||||
|
prewarmObjectPools();
|
||||||
|
|
||||||
initialized_ = true;
|
initialized_ = true;
|
||||||
running_ = true;
|
running_ = true;
|
||||||
|
|
||||||
|
|
@ -162,6 +168,17 @@ bool Application::init(const AppConfig &config) {
|
||||||
return true;
|
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() {
|
void Application::shutdown() {
|
||||||
if (!initialized_)
|
if (!initialized_)
|
||||||
return;
|
return;
|
||||||
|
|
@ -170,6 +187,10 @@ void Application::shutdown() {
|
||||||
|
|
||||||
// 打印 VRAM 统计
|
// 打印 VRAM 统计
|
||||||
VRAMManager::getInstance().printStats();
|
VRAMManager::getInstance().printStats();
|
||||||
|
|
||||||
|
// 打印对象池内存统计
|
||||||
|
E2D_LOG_INFO("Object pool memory usage: {} bytes (auto-managed)",
|
||||||
|
ObjectPoolManager::getInstance().getPool<Node>()->memoryUsage());
|
||||||
|
|
||||||
// 先结束所有场景,确保 onExit() 被正确调用
|
// 先结束所有场景,确保 onExit() 被正确调用
|
||||||
if (sceneManager_) {
|
if (sceneManager_) {
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,7 @@
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
// ObjectPoolManager 单例实现
|
// ObjectPoolManager 单例实现
|
||||||
ObjectPoolManager& ObjectPoolManager::getInstance() {
|
// 所有对象池通过静态局部变量自动管理生命周期
|
||||||
static ObjectPoolManager instance;
|
// 程序退出时自动清理,无需手动调用 cleanup
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ObjectPoolManager::cleanup() {
|
|
||||||
// 静态对象池会在程序退出时自动清理
|
|
||||||
// 这个方法用于显式触发清理(如果需要)
|
|
||||||
// 由于使用了 weak_ptr,循环引用问题已解决
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
} // 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 使用内存池优化小对象分配,无需用户干预:
|
Extra2D 使用内存池优化小对象分配,无需用户干预:
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
#include "audio_manager.h"
|
#include "audio_manager.h"
|
||||||
#include "storage.h"
|
#include "storage.h"
|
||||||
#include <extra2d/extra2d.h>
|
#include <extra2d/extra2d.h>
|
||||||
|
#include <extra2d/utils/object_pool.h>
|
||||||
|
|
||||||
namespace pushbox {
|
namespace pushbox {
|
||||||
|
|
||||||
|
|
@ -99,6 +100,11 @@ PlayScene::PlayScene(int level) : BaseScene() {
|
||||||
soundToggleText_->setPosition(offsetX + 520.0f, offsetY + 330.0f);
|
soundToggleText_->setPosition(offsetX + 520.0f, offsetY + 330.0f);
|
||||||
addChild(soundToggleText_);
|
addChild(soundToggleText_);
|
||||||
|
|
||||||
|
// 撤销提示(对象池使用示例)
|
||||||
|
undoText_ = extra2d::Text::create("Z键撤销", font20_);
|
||||||
|
undoText_->setPosition(offsetX + 520.0f, offsetY + 370.0f);
|
||||||
|
addChild(undoText_);
|
||||||
|
|
||||||
mapLayer_ = extra2d::makePtr<extra2d::Node>();
|
mapLayer_ = extra2d::makePtr<extra2d::Node>();
|
||||||
mapLayer_->setAnchor(0.0f, 0.0f);
|
mapLayer_->setAnchor(0.0f, 0.0f);
|
||||||
mapLayer_->setPosition(0.0f, 0.0f);
|
mapLayer_->setPosition(0.0f, 0.0f);
|
||||||
|
|
@ -128,6 +134,10 @@ void PlayScene::updateMenuColors() {
|
||||||
soundToggleText_->setTextColor(menuIndex_ == 1 ? extra2d::Colors::Red
|
soundToggleText_->setTextColor(menuIndex_ == 1 ? extra2d::Colors::Red
|
||||||
: extra2d::Colors::White);
|
: extra2d::Colors::White);
|
||||||
}
|
}
|
||||||
|
if (undoText_) {
|
||||||
|
undoText_->setTextColor(menuIndex_ == 2 ? extra2d::Colors::Red
|
||||||
|
: extra2d::Colors::White);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayScene::onUpdate(float dt) {
|
void PlayScene::onUpdate(float dt) {
|
||||||
|
|
@ -159,6 +169,12 @@ void PlayScene::onUpdate(float dt) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Z 键撤销(对象池使用示例)
|
||||||
|
if (input.isKeyPressed(extra2d::Key::Z)) {
|
||||||
|
undoMove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// A 键执行选中的菜单项
|
// A 键执行选中的菜单项
|
||||||
if (input.isButtonPressed(extra2d::GamepadButton::A)) {
|
if (input.isButtonPressed(extra2d::GamepadButton::A)) {
|
||||||
executeMenuItem();
|
executeMenuItem();
|
||||||
|
|
@ -210,6 +226,9 @@ void PlayScene::executeMenuItem() {
|
||||||
soundBtn_->setOn(g_SoundOpen);
|
soundBtn_->setOn(g_SoundOpen);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 2: // 撤销
|
||||||
|
undoMove();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -279,6 +298,11 @@ void PlayScene::flush() {
|
||||||
void PlayScene::setLevel(int level) {
|
void PlayScene::setLevel(int level) {
|
||||||
g_CurrentLevel = level;
|
g_CurrentLevel = level;
|
||||||
saveCurrentLevel(g_CurrentLevel);
|
saveCurrentLevel(g_CurrentLevel);
|
||||||
|
|
||||||
|
// 清空移动历史(智能指针自动回收到对象池)
|
||||||
|
while (!moveHistory_.empty()) {
|
||||||
|
moveHistory_.pop();
|
||||||
|
}
|
||||||
|
|
||||||
if (levelText_) {
|
if (levelText_) {
|
||||||
levelText_->setText("第" + std::to_string(level) + "关");
|
levelText_->setText("第" + std::to_string(level) + "关");
|
||||||
|
|
@ -343,6 +367,9 @@ void PlayScene::move(int dx, int dy, int direct) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 使用对象池创建移动记录(自动管理内存)
|
||||||
|
auto record = E2D_MAKE_POOLED(MoveRecord, map_.roleX, map_.roleY, targetX, targetY, false);
|
||||||
|
|
||||||
if (map_.value[targetY][targetX].type == TYPE::Ground) {
|
if (map_.value[targetY][targetX].type == TYPE::Ground) {
|
||||||
g_Pushing = false;
|
g_Pushing = false;
|
||||||
map_.value[map_.roleY][map_.roleX].type = TYPE::Ground;
|
map_.value[map_.roleY][map_.roleX].type = TYPE::Ground;
|
||||||
|
|
@ -383,6 +410,13 @@ void PlayScene::move(int dx, int dy, int direct) {
|
||||||
return;
|
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[boxY][boxX].type = TYPE::Box;
|
||||||
map_.value[targetY][targetX].type = TYPE::Man;
|
map_.value[targetY][targetX].type = TYPE::Man;
|
||||||
map_.value[map_.roleY][map_.roleX].type = TYPE::Ground;
|
map_.value[map_.roleY][map_.roleX].type = TYPE::Ground;
|
||||||
|
|
@ -392,6 +426,9 @@ void PlayScene::move(int dx, int dy, int direct) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 保存移动记录到历史栈
|
||||||
|
moveHistory_.push(record);
|
||||||
|
|
||||||
map_.roleX = targetX;
|
map_.roleX = targetX;
|
||||||
map_.roleY = targetY;
|
map_.roleY = targetY;
|
||||||
setStep(step_ + 1);
|
setStep(step_ + 1);
|
||||||
|
|
@ -415,4 +452,36 @@ void PlayScene::gameOver() {
|
||||||
setLevel(g_CurrentLevel + 1);
|
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
|
} // namespace pushbox
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@
|
||||||
#include "BaseScene.h"
|
#include "BaseScene.h"
|
||||||
#include "data.h"
|
#include "data.h"
|
||||||
#include <extra2d/extra2d.h>
|
#include <extra2d/extra2d.h>
|
||||||
|
#include <extra2d/utils/object_pool.h>
|
||||||
|
#include <stack>
|
||||||
|
|
||||||
namespace pushbox {
|
namespace pushbox {
|
||||||
|
|
||||||
|
|
@ -72,6 +74,11 @@ private:
|
||||||
* @brief 游戏通关
|
* @brief 游戏通关
|
||||||
*/
|
*/
|
||||||
void gameOver();
|
void gameOver();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 撤销上一步移动(对象池使用示例)
|
||||||
|
*/
|
||||||
|
void undoMove();
|
||||||
|
|
||||||
int step_ = 0;
|
int step_ = 0;
|
||||||
int menuIndex_ = 0;
|
int menuIndex_ = 0;
|
||||||
|
|
@ -85,6 +92,7 @@ private:
|
||||||
extra2d::Ptr<extra2d::Text> bestText_;
|
extra2d::Ptr<extra2d::Text> bestText_;
|
||||||
extra2d::Ptr<extra2d::Text> restartText_;
|
extra2d::Ptr<extra2d::Text> restartText_;
|
||||||
extra2d::Ptr<extra2d::Text> soundToggleText_;
|
extra2d::Ptr<extra2d::Text> soundToggleText_;
|
||||||
|
extra2d::Ptr<extra2d::Text> undoText_;
|
||||||
extra2d::Ptr<extra2d::Node> mapLayer_;
|
extra2d::Ptr<extra2d::Node> mapLayer_;
|
||||||
|
|
||||||
extra2d::Ptr<extra2d::Button> soundBtn_;
|
extra2d::Ptr<extra2d::Button> soundBtn_;
|
||||||
|
|
@ -97,6 +105,9 @@ private:
|
||||||
|
|
||||||
extra2d::Ptr<extra2d::Texture> texMan_[5];
|
extra2d::Ptr<extra2d::Texture> texMan_[5];
|
||||||
extra2d::Ptr<extra2d::Texture> texManPush_[5];
|
extra2d::Ptr<extra2d::Texture> texManPush_[5];
|
||||||
|
|
||||||
|
// 对象池使用示例:使用智能指针管理 MoveRecord
|
||||||
|
std::stack<extra2d::Ptr<MoveRecord>> moveHistory_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace pushbox
|
} // namespace pushbox
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define MAX_LEVEL 8
|
#define MAX_LEVEL 8
|
||||||
|
#define GAME_WIDTH 640.0f
|
||||||
|
#define GAME_HEIGHT 480.0f
|
||||||
|
|
||||||
namespace pushbox {
|
namespace pushbox {
|
||||||
|
|
||||||
|
|
@ -19,6 +21,24 @@ struct Map {
|
||||||
Piece value[12][12];
|
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 Map g_Maps[MAX_LEVEL];
|
||||||
extern int g_CurrentLevel;
|
extern int g_CurrentLevel;
|
||||||
extern bool g_SoundOpen;
|
extern bool g_SoundOpen;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue