【发布时间】:2020-06-15 19:00:08
【问题描述】:
以下问题源自https://github.com/cycfi/elements/issues/144,这是我努力在元素 GUI 库中找到一种方法来每帧调用一次回调。
到目前为止,在我见过的每个库中,都有一些回调/显式循环持续处理用户输入、测量自上一帧以来的时间并执行渲染。
在元素库中,这样的循环是特定于平台的实现细节,相反,使用库的代码可以访问boost::asio::io_context 对象,任何可调用对象都可以是posted。 poll 在特定于平台的事件循环中被调用。
将代码从典型的瀑布式 update(time_since_last_frame) 更改为 posting 执行此操作的函子没有问题,但这是真正问题开始的地方:
- 发布的函子只被调用一次。图书馆作者的回答是“再发一次”。
- 如果我立即从函子再次发布,我会创建一个无休止的繁忙循环,因为一旦来自
poll的函子完成,boost asio 就会运行新发布的函子。由于无限的自我转发回调循环,这完全冻结了运行 GUI 的线程。库作者的回答是“使用计时器发布”。 - 如果我使用计时器发帖,我不会修复任何问题:
- 如果时间太短,会在回调结束前用完,所以再次调用新发布的回调副本……再次带来无限循环。
- 如果时间太大导致无限循环,但小到可以在一帧内多次放置,则每帧运行多次...这是一种浪费,因为计算 UI/ 没有意义每帧多次动画/输入状态。
- 如果时间过长,则不会在每一帧上调用回调。应用程序多次渲染而不处理用户生成的事件……这是一种浪费,因为每次逻辑更新都会多次渲染相同的状态。
- 无法计算 FPS,因为使用库的代码甚至不知道在发布的回调之间(如果有)渲染了多少帧。
换句话说:
- 在典型的更新+输入+渲染循环中,循环会尽可能快地运行,产生尽可能多的帧(或者由于休眠而产生指定的上限)。如果代码很慢,那只是 FPS 损失。
- 在元素库中,如果回调太快,则每帧会重复多次,因为注册的计时器可能会在一帧内完成多次。如果代码太慢,那就是一个“死锁”的回调循环,永远无法摆脱 asio 的
poll。
我不希望我的代码每 X 次都被调用(或者因为 OS 调度程序而超过 X 次)。我希望我的代码每帧调用一次(最好使用 time delta 参数,但我也可以从之前的调用中自己测量它)。
在元素库中这样使用 asio 是一个糟糕的设计吗? 我发现“带有计时器的帖子”解决方案是一种反模式。对我来说,这就像通过在其中一个线程中添加一个睡眠来修复两个线程之间的死锁,并希望它们在这样的更改之后永远不会发生冲突——如果元素我发布了一个定时回调并希望它不会太快浪费 CPU 但是也不要慢到导致无限的定时回调循环。理想时间太难计算了,因为影响它的因素太多,包括用户操作——基本上是双输的情况。
额外说明1:我试过defer而不是poll,没有区别。
额外说明 2:我已经为图书馆创建了 100 多个问题/PR,因此很有可能一个激励性的答案将在另一个 PR 中结束。换句话说,尝试修改库的解决方案也很好。
额外说明 3:MCVE(这里没有计时器,这会导致几乎无限循环,直到计数器完成,在计算期间 GUI 线程被冻结):
#include <elements.hpp>
using namespace cycfi::elements;
bool func()
{
static int x = 0;
if (++x == 10'000'000)
return true;
return false;
}
void post_func(view& v)
{
if (!func())
v.post([&v](){ post_func(v); });
}
int main(int argc, char* argv[])
{
app _app(argc, argv);
window _win(_app.name());
_win.on_close = [&_app]() { _app.stop(); };
view view_(_win);
view_.content(box(rgba(35, 35, 37, 255)));
view_.post([&view_](){ post_func(view_); });
_app.run();
return 0;
}
【问题讨论】:
-
“自我转发”和“每帧一次”绝对是相互矛盾的目标。你能否澄清一下(也许用更少的词?)
-
我希望我的代码每帧调用一次,通过“自我转发”我的意思是一个可调用对象,当完成时,将其自身的另一个副本添加到 asio 的 io 上下文队列中。更正了标题。
-
这意味着您最终将每帧调用无数次。 (因为您每帧添加一个,但之前发布的每个处理程序都会自行重新发布)
-
这就是我要避免的。我可以添加一个计时器,以便在 X 时间之后调用它,但它不能保证在帧之间只调用一次。
标签: c++ boost-asio event-loop