#ifndef __MEM_PROBE_HPP__ #define __MEM_PROBE_HPP__ #include #include #include namespace jdi { /** * @brief 可用物理内存的估算值(字节,带 200ms 缓存)。 * * ⚠️ **为什么需要它**:本项目用 `-fno-exceptions` 构建(`.xdata`+`.pdata` 占产物体积约 9%, * 关闭后**实测省 27,136 B**:909,824 → 882,688)。关闭异常后, * `std::vector::reserve` 之类的分配失败**不再抛 * `std::bad_alloc`**,而是直接 `std::terminate` —— 因此凡是"按数据规模预留大块内存" * 的地方,都必须先**预检验**(本函数)再决定是否预留。 * * `GlobalMemoryStatusEx` 是系统调用,结果按 200ms 缓存:内存变化远慢于该间隔, * 而绘制路径上每帧可能请求多次。 * * @return 可用物理内存字节数;查询失败时返回 `SIZE_MAX`(视为"内存充足")。 */ std::size_t MemAvailablePhysical(); /** * @brief 判断"再分配 `bytes` 字节是否安全"。 * * 判据是"预留后仍保有至少 `kMemHeadroomRatio` 的可用内存",避免把系统逼到换页。 * * @param [IN] bytes 计划分配的字节数。 * @return 安全返回 true。 */ bool MemCanAllocate(std::size_t bytes); /** * @brief 按数据规模预留的**安全版**:只有确认内存够才 reserve。 * * 预留失败在语义上只是"少了一次性优化":不预留时 `push_back` 会按 1.5~2 倍自行增长, * 结果依然正确(只是多几次 realloc)。因此在 `-fno-exceptions` 下这条路径 * **既不会崩、也不会改变行为**。 * * @tparam T 元素类型。 * @param [IN,OUT] container 目标容器(需支持 `reserve` / `capacity` / `size`)。 * @param [IN] count 期望容量(元素个数)。若已足够则什么都不做。 * @return 真的执行了预留返回 true。 */ template bool MemReserveIfRoom(Container& container, std::size_t count) { if (count <= container.capacity()) { return false; } const std::size_t elementSize = sizeof(typename Container::value_type); // 溢出保护:count * elementSize 可能超出 size_t。 if (elementSize != 0 && count > (std::numeric_limits::max() / elementSize)) { return false; } if (!MemCanAllocate(count * elementSize)) { return false; } container.reserve(count); return true; } /** * @brief 安装"内存不足"兜底处理(`std::set_terminate`)。 * * 在 `-fno-exceptions` 下,任何遗漏的分配失败都会走到 terminate。 * 默认的 terminate 会静默 abort(用户只看到程序消失);本函数改为 * **弹一次模态提示(含"请减少同时显示的网格数/信号数"的建议)后干净退出**, * 让用户知道发生了什么。 * * 幂等:重复调用只安装一次。 */ void MemInstallTerminateHandler(); } // namespace jdi #endif // __MEM_PROBE_HPP__