1 个不稳定版本
0.1.0 | 2020年1月10日 |
---|
#953 在 GUI
65KB
1.5K SLoC
winflip
对制作类似于 https://github.com/floooh/sokol 的东西进行的 Rust 研究。
这是一个实验。我只是想尝试将 sokol_app.h
端口到 Rust 中,以了解更多关于如何使跨平台窗口设置库最小化的知识。这可能有一天会值得使用,也许不会。目前它只能在 Linux+X11 上运行,尽管它设计成可以根据需要添加更多后端。
为什么不使用 winit
?基本上,如 这里 所述,我觉得 winit
试图在太多平台上完美地做太多事情,所以我想要探索具有更多有限目标的东西会是什么样子。
目标
- 在 Windows、Linux 和 WebGL+WASM 上运行
- 设置窗口并处理事件
- 设置 OpenGL 上下文
- 体积较小
- 易于游戏使用
非目标
- 多个窗口
- 多个 OpenGL 上下文
- 任何特定的事件循环风格
- 易于原生 GUI 应用程序使用
- 多线程
- 完美
目前不需要担心的事情
- 游戏手柄支持
- 移动支持
先前的技术
这个主要目的是直接移植 sokol_app.h
,但还有一些其他类别可以考虑,仅为了对比
winit
glutin
minifb
pixels
此外,在 sokol_app.h
上运行 c2rust
实际上效果相当不错!这是一条有希望的探究路线,但也有一些问题
sokol_app
正在积极开发,因此我们必须多次进行此操作以纳入未来的错误修复--不能一次运行就使其工作,必须自动化以使其始终工作- 运行
c2rust
并非易事,需要设置相当多的管道,还要做更多才能做得好 c2rust
的输出有时仍需要手动进行一些调整才能编译。至少通常不多。c2rust
说它还不支持 Windows?并且也不支持交叉编译,因为它无法设置和取消设置可能存在于世界中的所有神奇的平台特定 ifdef
当前状态和想法
作为一个实验或“原型”,它基本上算是完成了。经过大约四天的工作,它在Linux上使用X11创建了一个窗口,正确地调用用户提供的回调函数,并正确退出。它没有实现一些功能(特别是窗口标题、hidpi和剪贴板支持),并且其GL上下文创建存在bug,但如果在代码x11::run()
中注释掉GLX设置函数,它就能正常工作。在错误处理方面,它基本上处于原型状态,并且不允许用户控制事件循环,只允许控制回调。尽管如此,它还是创建了一个窗口并运行了一个事件循环,这已经完成了大约80%的工作,并且结构使得添加X11之外的其他后端相当容易。大部分代码是不安全的且未经审查的,但创建一个安全接口应该相当简单。
在依赖关系方面,x11-dl
库提供了我们所需的大部分功能。glutin_glx_sys
提供了我们所需的大部分功能的另一个子集,但组织方式略有不同,不能直接替换;它包含更多的GLX功能而较少的X11功能。抱歉,我不记得具体的细节。所以现在我只使用x11-dl
,并自己动态加载所需的GLX函数和定义。
移植sokol_app.h
有些奇怪,但总体来说非常直接。我确信它没有处理许多窗口库的边缘情况,但我没有找到明显的问题。X11在这个方面并没有暴露出其臭名昭著的丑陋,它只是相当平凡的陈旧的C代码,尽管一些像错误处理这样的东西相当糟糕。如果你砍掉了90%不再使用的X11代码并实际记录下留下的代码,它可能不会有那么糟糕的名声。sokol_app.h
本身是相当好的C代码,除了在各个地方使用静态全局变量,但至少它们的命名是一致的。所以,结果很容易遵循并移植到Rust。将C代码移植到Rust再次是一次记住C有多么糟糕的练习。它按照1980年的标准来说还可以,但我们现在可以做得更好。RAII、不可透明转换的特定整数类型、一些基本特质如Clone和Drop,以及真正的枚举类型让生活好多了,特别是当你有设计来实际使用这些功能的API时。实际上,我认为C最大的糟糕之处是其怪异的愿意说“是的或者那个可以是int并且没关系”给几乎任何类型,这使得建立任何类型的强抽象类型变得极其困难。还有,标准库的缺失:一个可移植的程序不应该需要编写自己的strncmp()
和assert()
。呃。
看到它如何与 winit
相比较同样很有趣。winflip
大约是 2000 行代码,如果完成的话可能会是 2500 行。如果每个后端都再增加 2000-3000 行代码,那么支持 Wayland、Windows、MacOS 和 wasm+web-sys,总共可能需要大约 15k 行重要代码。截至 2020 年 1 月,winit
大约有 35k 行代码,维护者本身也不一定对它的复杂性感到高兴。我认为 winflip
作为一种轻量级实现的数据点,对理解如何实现 winit
的主要功能非常有用。
尽管 Glutin 更糟!实际上,glutin_*_sys
包看起来非常实用,作为低级平台绑定。如果没有这些,glutin
似乎只有 9000 行代码,实际上做得很少。这是因为历史原因:在很久以前,winit
还不存在,只有 glutin
。但最终,人们希望将窗口设置和图形上下文设置分开,窗口设置被重构成了现在的 winit
。我认为 最好 从 glutin
中去除一些无用的部分,使其成为一个更精简的库,只负责图形上下文设置。现在,有了 raw-window-handle
包,这完全可行。在 sokol_app.h
中实际进行 OpenGL 设置的部分,除了由 glutin_*_sys
包处理的 DLL 加载外,只有几百行代码。有没有志愿者来帮忙呢?
当然,行数并不是一个好的复杂性代理。但我们只有这些。假设没有人故意使代码晦涩或冗长,我们至少可以广泛地假设它是一种间接指标,表明程序或库中包含多少“东西”。
最后一点... winit
的“事件循环 2.0”重构相当复杂,以至于它 需要相当多的工作来解释,即使是那些之前做过游戏或 UI 开发、了解内部机制的人也是如此。原因基本上是它试图提供一个 API,这个 API 既能与基于回调的 API(如网页浏览器和 Android)一起工作,也能与更传统的轮询事件循环 API(如 X11 或 Windows)一起工作。再次强调,没有人对此感到高兴,但没有人有更好的结构化想法,同时又不牺牲对某些人很重要的功能。我个人发现,winflip
的设置,它只使用循环中调用的 frame()
、update()
和 event()
回调,实际上使用起来更简单,也更易于正确使用和集成到其他系统中,但它确实牺牲了一些东西,比如平滑缩放、帧绘制的一些延迟问题等。对我而言,这些都是我愿意做出的牺牲。
思考为什么更喜欢 winflip
的配置有点奇怪,因为我并不是很确定。当你退一步看,两种风格实际上是等效的,你只需要提供一个调用回调的事件循环,就可以将轮询事件循环转换为回调,或者通过让回调收集到共享队列中的事件来实现相反的操作。我认为不同的“感觉”是因为 winit
的做法,非常具体的事件,比如“用户按下键”,被穿插得更抽象的事件,如“帧开始”或“事件循环清除”,这些实际上是事件循环本身的... 状态变化,而不是真正的事件。然后,当你将它如何使用 ControlFlow
来提供关于下一步做什么的反馈方式混合在一起... 这最终变成了一个有些奇怪感觉的交织状态机,伪装成事件回调。我仍然不确定。
所以,这很有趣!
依赖项
~115KB