将 SDL 集成到 GLib 主循环中

Integrate SDL into GLib mainloop

我正在计划一个使用 SDL 的小型 Vala 游戏项目,我想知道如何将 SDL 正确集成到 GLib 主循环中。上次我用 Vala 和 SDL 做一些事情时,我使用了标准的 SDL 事件循环,但老实说,这是一堆废话,它破坏了整个漂亮的 Vala 或更确切地说是 GLib 信号系统。

我发现 an integration for Cogl 并且我正在寻找与 SDL 相同的东西。

GLib 源由三个回调组成:

  • 在轮询之前检查源是否准备就绪(并避免 poll 调用)
  • 一个检查轮询后源是否仍然准备就绪
  • 一个发送附加的回调

您可以相当简单地进行源检查和调度事件。

public delegate bool SDLSourceFunc (SDL.Event event);

public class SDLSource : Source 
{
    public SDL.Event event;

    public bool prepare (out uint timeout) 
    {
        timeout = 0;
        return true;
    }

    public bool check ()
    {
        return SDL.Event.poll (out event) > 0;
    }

    public bool dispatch (SourceFunc callback) 
    {
        return ((SDLSourceFunc) callback) (event);
    }

    public void add_callback (SDLSourceFunc callback)
    {
        base.add_callback ((SourceFunc) callback);
    }
}

然后,您将使用 Source.CONTINUE:

循环
var source = new SDLSource ();

source.add_callback ((event) => {
    // handle event here
    return Source.CONTINUE;
});

source.attach (MainContext.@default ());

这是非常基本的:您的来源可以使用 SDL.EventMaskSDL.peep 过滤特定事件。为单个源分派多个事件并附加相关文件描述符也更有效。

如果你使用一些异步代码,你可以直接从 Source 调度中唤醒协程:

public async void next_event_async () 
{
    var source = new SDLSource ();
    source.attach (MainContext.@default ());
    source.add_callback (handle_event_async.callback);
    yield;
    return source.event;
}

遗憾的是,SDL 使这变得非常困难。理想情况下,SDL API 会提供一个文件描述符来侦听,只要有事件可用,就会发出信号,这样您就可以将它添加到 glib 主循环中,但它没有这个。

虽然不太理想,但我认为最简单和最便携的方法就是安装一个定时器来定期检查 SDL 事件。您可以使用 g_timeout_add 定期唤醒主循环,然后在回调中调用 SDL_PollEvent。

这类似于 ,除了要注意那里给出的方法会有效地导致忙等待(准备函数 returns 如果没有可用的事件则为零超时)所以它最终将始终使用 100% CPU.

Clutter 曾经有一个 SDL backend,您仍然可以在 Git 历史中看到它。它基本上采用这种定期唤醒主循环的方法,除了它创建自定义源而不是使用 g_timeout_add。

考虑到您正在编写游戏,如果您希望无论如何都要不断重绘并让进程定期阻塞 SDL_GL_SwapWindow,那么只安装一个带有 [=13= 的空闲处理程序可能更有意义] 然后在回调中执行所有 SDL 操作。您可以在空闲回调开始时检查事件,然后在结束时调用 SDL_GL_SwapWindow,这会阻止等待重绘完成,这意味着它不会占用 100% CPU.