如何在多个事件循环中正确使用 SDL_PeepEvents?
How do I use SDL_PeepEvents properly in multiple event loops?
我想做一件简单的事情 — 在具有处理 window 逻辑的单元中,我想更新 window(第一个事件循环),然后在另一个具有例如逻辑的单元键盘处理,更新其状态(第二个事件循环)。 Window 事件必须放回 SDL queue 以便在更新键盘时可见(以便能够检查 window 是否处于焦点状态)。
简而言之,有一个事件queue(SDL内部),但是有很多循环读取事件。如果一个事件需要在多个循环中可见,处理完后必须return到queue。为了使这成为可能,每个循环都应该迭代 queue 的内容,以便不处理在处理后被推回 queue 的事件。
为此,我使用 SDL_PeepEvents
,其中 return 是 queued 事件的数量,并在 for
循环中迭代了这么多次。测试程序代码如下所示:
uses
SDL2;
procedure UpdateWindow();
var
Event: TSDL_Event;
Index: Integer;
begin
for Index := 0 to SDL_PeepEvents(nil, -1, SDL_PEEKEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT) - 1 do
begin
SDL_PollEvent(@Event);
if Event.Type_ = SDL_WINDOWEVENT then
case Event.Window.Event of
SDL_WINDOWEVENT_FOCUS_GAINED: WriteLn('focus gain - window');
SDL_WINDOWEVENT_FOCUS_LOST: WriteLn('focus lost - window');
end;
// put all events back in the queue to be seen in other event loops
SDL_PushEvent(@Event);
end;
end;
procedure UpdateKeyboard();
var
Event: TSDL_Event;
Index: Integer;
begin
for Index := 0 to SDL_PeepEvents(nil, -1, SDL_PEEKEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT) - 1 do
begin
SDL_PollEvent(@Event);
// reading window events a second time
if Event.Type_ = SDL_WINDOWEVENT then
case Event.Window.Event of
SDL_WINDOWEVENT_FOCUS_GAINED: WriteLn('focus gain - keyboard');
SDL_WINDOWEVENT_FOCUS_LOST: WriteLn('focus lost - keyboard');
end;
SDL_PushEvent(@Event);
end;
end;
var
Window: PSDL_Window;
Done: Boolean = False;
begin
SDL_Init(SDL_INIT_EVERYTHING);
Window := SDL_CreateWindow('Events test', SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, SDL_WINDOW_SHOWN);
while not Done do
begin
SDL_PumpEvents();
UpdateWindow();
UpdateKeyboard();
// clear events queue
SDL_FlushEvents(SDL_FIRSTEVENT, SDL_LASTEVENT);
SDL_Delay(20);
end;
SDL_DestroyWindow(Window);
SDL_Quit();
end.
以上代码有效,但仅在某些时候有效。当程序启动并出现 window 时,有时它说 window 和键盘处于焦点状态,有时只是 window 只有焦点。这意味着 SDL_WINDOWEVENT_FOCUS_GAINED
事件在第一个循环中被处理,但在第二个循环中没有看到(即使它被添加回 queue)。
在控制台 window 和 SDL window 可见的情况下,我交替单击两者,激活和停用 SDL window。每次激活 SDL window 时,应该向控制台添加两行(一行用于事件循环),在停用 window 时也是如此。不幸的是,单击时控制台的内容如下所示:
focus gain - window
focus gain - keyboard
focus lost - window
focus lost - keyboard
focus gain - window
focus lost - window
focus lost - keyboard
focus gain - window
focus lost - window
focus lost - keyboard
focus gain - window
focus lost - window
focus lost - keyboard
focus gain - window
focus gain - keyboard
focus lost - window
focus lost - keyboard
focus gain - window
focus lost - window
focus lost - keyboard
focus gain - window
focus lost - window
focus lost - keyboard
focus gain - window
focus lost - window
focus lost - keyboard
{...}
如您所见,有时在激活 window 时,第二个循环看不到 SDL_WINDOWEVENT_FOCUS_GAINED
事件,因此它不会将 focus gained - keyboard
行添加到安慰。尽管第一个循环中的每个事件都使用 SDL_PushEvent
.
queued 返回,但此事件在某处丢失了
问题:如何在多个循环中正确处理 SDL 事件以及如何将已处理的事件放回 queue 以便它们对多个循环可见?
Lazarus 2.2.0、FPC 3.2.2、SDL2-for-Pascal headers 和 SDL 2.0.22.0 — 均为 64 位。
如果可能的话,完全不要那样做。最好的解决方案是只获取一次事件。如果您想多次处理事件,没有什么能阻止您将感兴趣的所有事件提取到单个数组中,并根据需要多次迭代该数组。您可以通过单个 SDL_PeepEvents(SDL_GETEVENT)
调用将所有事件提取到大数组中。
您遇到的问题基于两个因素:SDL_PollEvent
的 return 值在队列耗尽时为 0;但在文档中没有任何地方指出 SDL_PushEvent
将事件放入同一处理帧 - 即 PollEvent
可能表示队列已耗尽并且 return 您新推送的事件在下一帧。如果发生这种情况,在你的第二个循环中你错误地处理了一个事件(因为 PollEvent
说 0 并且没有数据填充到 Event
结构中),并跳过最后一个事件。
但是,保留更高级别的逻辑,您有两个选择:
使用 SDL_PeepEvent
获取事件,而不是 SDL_PollEvent
。 IE。将 SDL_PollEvent(@Event)
替换为 SDL_PeepEvent(@Event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT)
.
检查 SDL_PollEvent
的 return 值,如果它 returns 0 - 你必须再次调用它,因为这意味着没有弹出事件从队列。此解决方案容易出现竞争条件,您不能保证它不会在幕后调用 SDL_PumpEvents
,并且您最后的 SDL_FlushEvents
可能会终止一些未处理的事件。
我想做一件简单的事情 — 在具有处理 window 逻辑的单元中,我想更新 window(第一个事件循环),然后在另一个具有例如逻辑的单元键盘处理,更新其状态(第二个事件循环)。 Window 事件必须放回 SDL queue 以便在更新键盘时可见(以便能够检查 window 是否处于焦点状态)。
简而言之,有一个事件queue(SDL内部),但是有很多循环读取事件。如果一个事件需要在多个循环中可见,处理完后必须return到queue。为了使这成为可能,每个循环都应该迭代 queue 的内容,以便不处理在处理后被推回 queue 的事件。
为此,我使用 SDL_PeepEvents
,其中 return 是 queued 事件的数量,并在 for
循环中迭代了这么多次。测试程序代码如下所示:
uses
SDL2;
procedure UpdateWindow();
var
Event: TSDL_Event;
Index: Integer;
begin
for Index := 0 to SDL_PeepEvents(nil, -1, SDL_PEEKEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT) - 1 do
begin
SDL_PollEvent(@Event);
if Event.Type_ = SDL_WINDOWEVENT then
case Event.Window.Event of
SDL_WINDOWEVENT_FOCUS_GAINED: WriteLn('focus gain - window');
SDL_WINDOWEVENT_FOCUS_LOST: WriteLn('focus lost - window');
end;
// put all events back in the queue to be seen in other event loops
SDL_PushEvent(@Event);
end;
end;
procedure UpdateKeyboard();
var
Event: TSDL_Event;
Index: Integer;
begin
for Index := 0 to SDL_PeepEvents(nil, -1, SDL_PEEKEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT) - 1 do
begin
SDL_PollEvent(@Event);
// reading window events a second time
if Event.Type_ = SDL_WINDOWEVENT then
case Event.Window.Event of
SDL_WINDOWEVENT_FOCUS_GAINED: WriteLn('focus gain - keyboard');
SDL_WINDOWEVENT_FOCUS_LOST: WriteLn('focus lost - keyboard');
end;
SDL_PushEvent(@Event);
end;
end;
var
Window: PSDL_Window;
Done: Boolean = False;
begin
SDL_Init(SDL_INIT_EVERYTHING);
Window := SDL_CreateWindow('Events test', SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, SDL_WINDOW_SHOWN);
while not Done do
begin
SDL_PumpEvents();
UpdateWindow();
UpdateKeyboard();
// clear events queue
SDL_FlushEvents(SDL_FIRSTEVENT, SDL_LASTEVENT);
SDL_Delay(20);
end;
SDL_DestroyWindow(Window);
SDL_Quit();
end.
以上代码有效,但仅在某些时候有效。当程序启动并出现 window 时,有时它说 window 和键盘处于焦点状态,有时只是 window 只有焦点。这意味着 SDL_WINDOWEVENT_FOCUS_GAINED
事件在第一个循环中被处理,但在第二个循环中没有看到(即使它被添加回 queue)。
在控制台 window 和 SDL window 可见的情况下,我交替单击两者,激活和停用 SDL window。每次激活 SDL window 时,应该向控制台添加两行(一行用于事件循环),在停用 window 时也是如此。不幸的是,单击时控制台的内容如下所示:
focus gain - window
focus gain - keyboard
focus lost - window
focus lost - keyboard
focus gain - window
focus lost - window
focus lost - keyboard
focus gain - window
focus lost - window
focus lost - keyboard
focus gain - window
focus lost - window
focus lost - keyboard
focus gain - window
focus gain - keyboard
focus lost - window
focus lost - keyboard
focus gain - window
focus lost - window
focus lost - keyboard
focus gain - window
focus lost - window
focus lost - keyboard
focus gain - window
focus lost - window
focus lost - keyboard
{...}
如您所见,有时在激活 window 时,第二个循环看不到 SDL_WINDOWEVENT_FOCUS_GAINED
事件,因此它不会将 focus gained - keyboard
行添加到安慰。尽管第一个循环中的每个事件都使用 SDL_PushEvent
.
问题:如何在多个循环中正确处理 SDL 事件以及如何将已处理的事件放回 queue 以便它们对多个循环可见?
Lazarus 2.2.0、FPC 3.2.2、SDL2-for-Pascal headers 和 SDL 2.0.22.0 — 均为 64 位。
如果可能的话,完全不要那样做。最好的解决方案是只获取一次事件。如果您想多次处理事件,没有什么能阻止您将感兴趣的所有事件提取到单个数组中,并根据需要多次迭代该数组。您可以通过单个 SDL_PeepEvents(SDL_GETEVENT)
调用将所有事件提取到大数组中。
您遇到的问题基于两个因素:SDL_PollEvent
的 return 值在队列耗尽时为 0;但在文档中没有任何地方指出 SDL_PushEvent
将事件放入同一处理帧 - 即 PollEvent
可能表示队列已耗尽并且 return 您新推送的事件在下一帧。如果发生这种情况,在你的第二个循环中你错误地处理了一个事件(因为 PollEvent
说 0 并且没有数据填充到 Event
结构中),并跳过最后一个事件。
但是,保留更高级别的逻辑,您有两个选择:
使用
SDL_PeepEvent
获取事件,而不是SDL_PollEvent
。 IE。将SDL_PollEvent(@Event)
替换为SDL_PeepEvent(@Event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT)
.检查
SDL_PollEvent
的 return 值,如果它 returns 0 - 你必须再次调用它,因为这意味着没有弹出事件从队列。此解决方案容易出现竞争条件,您不能保证它不会在幕后调用SDL_PumpEvents
,并且您最后的SDL_FlushEvents
可能会终止一些未处理的事件。