Files
DX12/Engine/Utilities/FreeList.h
SpecialX 4d13d8df89 feat(d3d12): 新增纹理资源类,修复 Surface 重复释放问题
核心变更:
- 新增 d3d12_texture 和 d3d12_render_texture 类
- 新增 d3d12_texture_init_info 结构,支持三种资源创建方式
- 新增 D3D12Helpers.h,提供堆属性辅助结构
- 改用 utl::free_list 管理 surface,解决重复释放问题
- 为 d3d12_surface 添加移动语义,支持撕裂检测

文档完善:
- 为 FreeList.h 和 Vector.h 添加完整 Doxygen 中文注释
- 更新 D3D12 学习 Wiki,添加 SRV、资源创建方式、纹理资源类章节
- 新增变更记录文档
2026-04-01 16:17:42 +08:00

328 lines
8.5 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @file FreeList.h
* @brief 带槽位复用机制的稀疏对象容器模板
*
* @details
* free_list 是一种特殊的容器,维护"已使用槽位 + 空闲链表"结构,
* 特别适合需要频繁增删元素且使用索引作为外部句柄的场景。
*
* ## 核心特性
*
* - **O(1) 增删操作**:新增和删除操作近似常数时间复杂度
* - **槽位复用**:删除后的槽位会被回收并重新利用,避免频繁内存分配
* - **索引句柄**:使用 u32 索引作为外部句柄,与 id 系统完美配合
* - **内存紧凑**:底层使用连续内存,缓存友好
*
* ## 数据结构原理
*
* ```
* 初始状态:
* _array: [ 空 | 空 | 空 | 空 ]
* _next_free_index = invalid_id
* _size = 0
*
* 添加元素 A、B、C 后:
* _array: [ A | B | C | 空 ]
* _next_free_index = invalid_id
* _size = 3
*
* 删除元素 B 后(槽位 1 被标记为空闲):
* _array: [ A | ->2 | C | 空 ] // 槽位 1 存储下一个空闲索引 2
* _next_free_index = 1 // 指向第一个空闲槽位
* _size = 2
*
* 再删除元素 A 后:
* _array: [ ->1 | ->2 | C | 空 ] // 槽位 0 存储下一个空闲索引 1
* _next_free_index = 0
* _size = 1
*
* 添加新元素 D 时(复用槽位 0:
* _array: [ D | ->2 | C | 空 ]
* _next_free_index = 1 // 指向下一个空闲槽位
* _size = 2
* ```
*
* ## 空闲链表机制
*
* 删除元素时,该槽位被加入空闲链表:
* 1. 调用元素的析构函数
* 2. 将当前 `_next_free_index` 值写入该槽位(作为链表下一个节点)
* 3. 更新 `_next_free_index` 指向该槽位
*
* 添加元素时,优先从空闲链表获取槽位:
* 1. 如果 `_next_free_index` 有效,复用该槽位
* 2. 从槽位中读取下一个空闲索引,更新 `_next_free_index`
* 3. 否则,在数组末尾追加新元素
*
* ## 类型要求
*
* 由于空闲槽位需要存储 u32 索引,要求:
* - `sizeof(T) >= sizeof(u32)`(至少 4 字节)
*
* ## 使用场景
*
* - 游戏对象管理(实体 ID 系统)
* - 资源句柄管理(纹理、网格等)
* - 渲染表面管理(多窗口场景)
* - 任何需要稳定索引句柄的系统
*
* ## 注意事项
*
* - 删除后的索引可能被新元素复用,外部系统需要处理这种情况
* - 不保证元素在内存中的顺序
* - 与 std::vector 配合使用时可能触发重复构造(见 USE_STL_VECTOR 宏)
*
* @example
* @code
* utl::free_list<game_object> objects;
*
* // 添加对象
* u32 id1 = objects.add(player_data);
* u32 id2 = objects.add(enemy_data);
*
* // 访问对象
* objects[id1].update();
*
* // 删除对象(槽位被回收)
* objects.remove(id1);
*
* // 新对象可能复用 id1 的槽位
* u32 id3 = objects.add(new_data); // id3 可能等于 id1
* @endcode
*
* @see utl::vector
* @see DEFINE_TYPED_ID
*/
#pragma once
#include "CommonHeader.h"
namespace XEngine::utl {
#if USE_STL_VECTOR
#pragma message("WARNING: using utl::free_list with std::vector result in duplicate calls to class constructor!")
#endif
/**
* @class free_list
* @brief 带槽位复用机制的稀疏对象容器
*
* @tparam T 元素类型,必须满足 `sizeof(T) >= sizeof(u32)`
*
* @details
* 该容器使用空闲链表管理已删除的槽位,实现 O(1) 的增删操作。
* 特别适合需要稳定索引句柄的场景,如游戏对象管理、资源句柄等。
*/
template<typename T>
class free_list
{
static_assert(sizeof(T) >= sizeof(u32), "Type T must be at least 4 bytes to store free list indices");
public:
/**
* @brief 默认构造函数,创建空容器
*
* @details
* 创建一个空的 free_list不预分配任何内存。
* 首次添加元素时会触发内存分配。
*/
free_list() = default;
/**
* @brief 预留容量的构造函数
*
* @param count 预留的元素槽位数量
*
* @details
* 预分配指定数量的槽位,避免后续添加元素时的多次扩容。
* 适用于已知大概元素数量的场景。
*/
explicit free_list(u32 count)
{
_array.reserve(count);
}
/**
* @brief 析构函数,销毁容器
*
* @details
* 断言检查容器为空_size == 0确保所有元素已被正确移除。
* 在 DEBUG 模式下使用 USE_STL_VECTOR 时,会清空内存以便检测悬空引用。
*/
~free_list()
{
assert(!_size);
#if USE_STL_VECTOR
memset(_array.data(), 0, _array.size() * sizeof(T));
#endif
}
/**
* @brief 添加新元素并返回其槽位 ID
*
* @tparam params 构造参数类型包
* @param p 传递给元素构造函数的参数
* @return 新分配的槽位 IDu32 索引)
*
* @details
* 添加元素的策略:
* 1. **有空闲槽位**:从空闲链表头部取出一个槽位复用
* 2. **无空闲槽位**:在数组末尾追加新元素
*
* 复用槽位时:
* - 从该槽位读取下一个空闲索引,更新 `_next_free_index`
* - 使用 placement new 在该槽位构造新元素
*
* @note 返回的 ID 在元素被删除前保持有效
* @note 删除后该 ID 可能被新元素复用
*/
template<class ... params>
constexpr u32 add(params&&... p)
{
u32 id{ u32_invalid_id };
if (_next_free_index == u32_invalid_id)
{
id = (u32)_array.size();
_array.emplace_back(std::forward<params>(p)...);
}
else
{
id = _next_free_index;
assert(id < _array.size() && already_removed(id));
_next_free_index = *(const u32 *const)std::addressof(_array[id]);
new (std::addressof(_array[id])) T(std::forward<params>(p)...);
}
++_size;
return id;
}
/**
* @brief 移除指定 ID 的元素并回收其槽位
*
* @param id 要移除元素的槽位 ID
*
* @details
* 移除元素的步骤:
* 1. 调用元素的析构函数
* 2. DEBUG 模式下填充 0xcc 标记已删除
* 3. 将当前 `_next_free_index` 写入该槽位(链表链接)
* 4. 更新 `_next_free_index` 指向该槽位
*
* @pre id 必须是有效的已分配槽位
* @pre 该槽位未被删除(不能重复删除)
*/
constexpr void remove(u32 id)
{
assert(id < _array.size() && !already_removed(id));
T& item{ _array[id] };
item.~T();
DEBUG_OP(memset(std::addressof(_array[id]), 0xcc, sizeof(T)));
*(u32 *const)std::addressof(_array[id]) = _next_free_index;
_next_free_index = id;
--_size;
}
/**
* @brief 获取当前有效元素数量
*
* @return 容器中有效元素的个数
*
* @note 这与 capacity() 不同size() 返回实际存储的元素数
*/
constexpr u32 size() const
{
return _size;
}
/**
* @brief 获取已分配槽位总数
*
* @return 底层数组的大小(包括空闲槽位)
*
* @details
* 返回值 >= size(),因为可能存在已删除但未释放的槽位。
* 这些槽位会在后续添加元素时被复用。
*/
constexpr u32 capacity() const
{
return _array.size();
}
/**
* @brief 检查容器是否为空
*
* @return 如果容器中没有有效元素则返回 true
*/
constexpr bool empty() const
{
return _size == 0;
}
/**
* @brief 通过 ID 访问元素(可修改)
*
* @param id 元素的槽位 ID
* @return 元素的引用
*
* @pre id 必须有效且指向未删除的元素
*/
[[nodiscard]] constexpr T& operator[](u32 id)
{
assert(id < _array.size() && !already_removed(id));
return _array[id];
}
/**
* @brief 通过 ID 访问元素(只读)
*
* @param id 元素的槽位 ID
* @return 元素的常量引用
*
* @pre id 必须有效且指向未删除的元素
*/
[[nodiscard]] constexpr const T& operator[](u32 id) const
{
assert(id < _array.size() && !already_removed(id));
return _array[id];
}
private:
/**
* @brief 检查指定槽位是否已被删除
*
* @param id 要检查的槽位 ID
* @return 如果槽位已被删除则返回 true
*
* @details
* 通过检查槽位内容是否被 0xcc 填充来判断是否已删除。
* 仅在 sizeof(T) > sizeof(u32) 时有效,否则直接返回 true。
*
* @note 仅用于断言检查,确保不会访问已删除的元素
*/
constexpr bool already_removed(u32 id) const
{
if constexpr (sizeof(T) > sizeof(u32))
{
u32 i{ sizeof(u32) };
const u8 *const p{ (const u8 *const)std::addressof(_array[id]) };
while ((p[i] == 0xcc) && (i < sizeof(T))) ++i;
return i == sizeof(T);
}
else
{
return true;
}
}
#if USE_STL_VECTOR
utl::vector<T> _array;
#else
utl::vector<T, false> _array;
#endif
u32 _next_free_index{ u32_invalid_id };
u32 _size{ 0 };
};
}