我一直在尝试创建自己的 IMGUI 库,但找不到识别 IMGUI 调用问题的解决方案。
示例代码:
for (std::size_t i = 0; i < options.size(); ++i)
if (ui.radio_button(selected_option == i, options[i].str))
selected_option = i;
与 IMGUI 模式一样,UI 创建代码和相关逻辑运行每一帧。每个小部件调用都会立即返回它是否已在前一帧进行过交互。因此,我需要识别帧之间的小部件。这里的“小部件”通常是一个函数调用,或者是父小部件内的“嵌套”函数调用:
{
auto col = ui.scoped_column();
ui.widget1(...);
ui.widget2(...);
ui.widget3(...);
{
auto row = ui.scoped_row();
ui.widget4(...);
ui.widget5(...);
ui.widget6(...);
}
ui.widget7(...);
}
对于创建交互式小部件(按钮、单选、悬停、下拉)的每个调用,我都需要一些 ID。ID 被放入一个哈希表中,其中存储了一些与交互相关的信息(悬停、点击、边界等),下一帧将检查该表是否在调用相应的小部件 ID 时发生了某些事情。
因此,我需要一些方法来唯一标识小部件函数调用。
到目前为止我已经尝试过:小部件输入和当前小部件树状态的哈希。亲爱的 ImGui 库使用这种机制。我也试过了,我不得不说:它被设计为在某些时候失败。ID 必须是绝对唯一的,并且在循环/递归中使用少量小部件的 hello-world 实验中,我遇到了 2 种模式,这些模式会为以下哈希函数生成无限数量的冲突:
// https://en.wikipedia.org/wiki/Fowler–Noll–Vo_hash_function
std::uint64_t hash_fnv1a_64(const void* data, std::size_t len) noexcept
{
std::uint64_t hash = UINT64_C(0xcbf29ce484222325);
auto ptr = reinterpret_cast<const unsigned char*>(data);
const auto end = ptr + len;
while (ptr != end)
hash ^= (hash ^ *ptr++) * UINT64_C(0x100000001b3);
return hash;
}
模式是:
hash: 6000921948455098694; lengths: 34, 42, 50
202 0 0 0 211 0 0 0 202 0 0 0 211 0 0 0 202 0 0 0 211 0 0 0 114 101 99 117 114 115 101 46 46 46
202 0 0 0 211 0 0 0 202 0 0 0 211 0 0 0 202 0 0 0 211 0 0 0 202 0 0 0 211 0 0 0 114 101 99 117 114 115 101 46 46 46
202 0 0 0 211 0 0 0 202 0 0 0 211 0 0 0 202 0 0 0 211 0 0 0 202 0 0 0 211 0 0 0 202 0 0 0 211 0 0 0 114 101 99 117 114 115 101 46 46 46
hash: 10160622389312194022; lengths: 35, 43, 51
202 0 0 0 211 0 0 0 202 0 0 0 211 0 0 0 202 0 0 0 211 0 0 0 202 0 0 0 109 111 114 101 46 46 46
202 0 0 0 211 0 0 0 202 0 0 0 211 0 0 0 202 0 0 0 211 0 0 0 202 0 0 0 211 0 0 0 202 0 0 0 109 111 114 101 46 46 46
202 0 0 0 211 0 0 0 202 0 0 0 211 0 0 0 202 0 0 0 211 0 0 0 202 0 0 0 211 0 0 0 202 0 0 0 211 0 0 0 202 0 0 0 109 111 114 101 46 46 46
所以简而言之,我需要一种保证绝对唯一性的机制。任何重复的 ID(在我的项目中)都会导致未定义的行为(具有重复 ID 的上下文菜单会导致无限递归)。
我尝试过的其他一些想法及其问题:
- per-function-call increment:这很简单,但是如果下一帧包含不同数量的小部件,ID 将被移动并匹配不同的小部件。一个潜在的解决方案是每帧运行两次 UI 代码(第一次没有交互),但我担心是否有任何看不见的后果/问题
- 使用对象地址:C++ 保证每个对象都有一个唯一的地址,但是:
- 放入小部件(例如字符串)的数据地址可以在帧之间更改
- 某些地址可能会在 1 帧内意外重用(例如
ui.widget(str1 + str2)
) - 不保证字符串文字具有唯一地址
- 使用
__LINE__
- 不能跨文件工作并且不支持动态小部件 - 在 UI 对象中注册每个交互式小部件,但随后它使动态小部件复杂化,并且通过将小部件对象保留在框架之间的库用户代码中来破坏 IMGUI 的目的
- 多种方法的某种组合?
我知道可能没有理想的解决方案,但我对任何支持/反对特定解决方案的论点感兴趣。由于多种原因,哈希不起作用(没有数据长度保证、相同的字符串、冲突、不同对象的相同二进制表示)