144 lines
3.3 KiB
C++
144 lines
3.3 KiB
C++
/**
|
|
* @file Id.h
|
|
* @brief 使用“索引 + 代数”打包方案的 32 位标识符工具。
|
|
* @details
|
|
* 位布局:
|
|
* - 低位:索引
|
|
* - 高位:代数
|
|
*
|
|
* 当索引被复用时,通过递增代数降低悬挂句柄误用风险。
|
|
*/
|
|
#pragma once
|
|
#include "CommonHeader.h"
|
|
|
|
|
|
namespace XEngine::id
|
|
{
|
|
/**
|
|
* @brief 标识符相关类型与常量。
|
|
*/
|
|
using id_type = u32;
|
|
|
|
namespace detail {
|
|
/**
|
|
* @brief 高位代数字段的位宽。
|
|
*/
|
|
constexpr u32 generation_bits{ 8 };
|
|
/**
|
|
* @brief 低位索引字段的位宽。
|
|
*/
|
|
constexpr u32 index_bit{ sizeof(id_type) * 8 - generation_bits };
|
|
/**
|
|
* @brief 用于提取索引字段的掩码。
|
|
*/
|
|
constexpr id_type index_mask{ (id_type{1} << index_bit) - 1 };
|
|
/**
|
|
* @brief 右移后用于提取代数字段的掩码。
|
|
*/
|
|
constexpr id_type generation_mask{ (id_type{1} << generation_bits) - 1 };
|
|
}
|
|
/**
|
|
* @brief 表示无效标识符的哨兵值。
|
|
*/
|
|
constexpr id_type invalid_id{ (id_type)-1 };
|
|
/**
|
|
* @brief 延迟回收删除项时的触发阈值。
|
|
*/
|
|
constexpr u32 min_deleted_elements{ 1024 };
|
|
|
|
/**
|
|
* @brief 可容纳代数字段的最小无符号整型。
|
|
*/
|
|
using generation_type = std::conditional_t<detail::generation_bits <= 16, std::conditional_t<detail::generation_bits <= 8, u8, u16>, u32>;
|
|
static_assert(sizeof(generation_type) * 8 >= detail::generation_bits);
|
|
static_assert((sizeof(id_type) - sizeof(generation_type)) > 0);
|
|
|
|
|
|
|
|
/**
|
|
* @brief 判断标识符是否有效。
|
|
* @param id 打包后的标识符。
|
|
* @return 当 @p id 不是无效哨兵值时返回 true。
|
|
*/
|
|
constexpr bool
|
|
is_valid(id_type id)
|
|
{
|
|
return id != invalid_id;
|
|
}
|
|
|
|
/**
|
|
* @brief 从打包标识符中提取索引字段。
|
|
* @param id 打包后的标识符。
|
|
* @return 低位索引字段。
|
|
* @pre 提取后的索引不能等于保留的全 1 索引值。
|
|
*/
|
|
constexpr id_type
|
|
index(id_type id)
|
|
{
|
|
id_type index{ id & detail::index_mask };
|
|
assert(index != detail::index_mask);
|
|
return index;
|
|
}
|
|
|
|
/**
|
|
* @brief 从打包标识符中提取代数字段。
|
|
* @param id 打包后的标识符。
|
|
* @return 高位代数字段。
|
|
*/
|
|
constexpr id_type
|
|
generation(id_type id)
|
|
{
|
|
return (id >> detail::index_bit) & detail::generation_mask;
|
|
}
|
|
|
|
/**
|
|
* @brief 为同一索引生成下一代标识符。
|
|
* @param id 当前打包标识符。
|
|
* @return 索引不变且代数递增后的标识符。
|
|
* @pre 代数递增后不能溢出到保留范围。
|
|
*/
|
|
constexpr id_type
|
|
new_generation(id_type id)
|
|
{
|
|
const id_type generation{ id::generation(id) + 1 };
|
|
assert(generation < (((u64)1 << detail::generation_bits) - 1));
|
|
return index(id) | (generation << detail::index_bit);
|
|
}
|
|
|
|
|
|
|
|
|
|
#if _DEBUG
|
|
namespace detail {
|
|
struct id_base
|
|
{
|
|
constexpr explicit id_base(id_type id) : _id(id) {}
|
|
constexpr operator id_type() const { return _id; }
|
|
private:
|
|
id_type _id;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @brief 在调试构建下声明强类型标识符。
|
|
* @details
|
|
* 调试构建使用派生自 id_base 的包装类型。
|
|
* 发布构建退化为 id_type 别名以保持零开销抽象。
|
|
*/
|
|
#define DEFINE_TYPED_ID(name) \
|
|
struct name final : id::detail::id_base \
|
|
{ \
|
|
constexpr explicit name(id::id_type id) \
|
|
: id_base{id}{} \
|
|
constexpr name() : id_base{ 0 }{} \
|
|
};
|
|
#else
|
|
/**
|
|
* @brief 发布模式下的零运行时开销类型别名。
|
|
*/
|
|
#define DEFINE_TYPED_ID(name) using name = id::id_type;
|
|
#endif
|
|
|
|
|
|
}
|