如果您制作的游戏架构将系统组件(IE、渲染、物理、逻辑、输入、脚本等)拆分为不同的线程,您如何处理需要实时通信的情况?
例如,如果一个脚本想要在屏幕上绘制一个盒子,理想情况下它会在渲染组件发出“FrameDrawn”事件时执行此操作,以便在每帧绘制结束时将盒子绘制在屏幕顶部. 如果脚本组件和渲染组件位于彼此不同的线程上,这怎么可能?
如果您制作的游戏架构将系统组件(IE、渲染、物理、逻辑、输入、脚本等)拆分为不同的线程,您如何处理需要实时通信的情况?
例如,如果一个脚本想要在屏幕上绘制一个盒子,理想情况下它会在渲染组件发出“FrameDrawn”事件时执行此操作,以便在每帧绘制结束时将盒子绘制在屏幕顶部. 如果脚本组件和渲染组件位于彼此不同的线程上,这怎么可能?
通常,渲染线程是唯一一个将东西绘制到屏幕上的线程。但是,由于线程可以通信,所以脚本线程可以告诉渲染线程“嘿,我想在下一帧画一个框”。
我们的项目处理线程通信的方式是两种方式之一。对于动态编辑的东西——对象列表、移动车辆等,我们创建一个互斥锁,在数据被更改时锁定数据。如果渲染器想要绘制它,但更新线程正在删除该对象,渲染器将不得不等待。对于其他东西,比如 ui,我们只有由 ui 线程写入并由渲染器读取的全局标志,因此不需要互斥锁。
大多数游戏将拥有大量数据和几个线程根据需要引用该数据的某些子集。线程之间的通信很少是显式的,更常见的是隐式通信,通过一个线程引起的数据更改完成,然后由第二个线程注意到。更改受到互斥锁、信号量或其他低级同步原语的保护。
对于您的绘图示例,脚本线程将更改 GUI 组件中的一些数据,而渲染线程在下次渲染 GUI 时会看到新数据并相应地绘制框。
但是请记住,大多数游戏开发人员并没有像您的示例中那样对事物进行线程化,部分原因是使用共享大量数据并依赖低级别锁定来确保正确性的模型很难有效地做到这一点。理想情况下,更多的游戏开发者会转向共享更少数据的模型,但由于软实时演示响应要求,这很困难。
您所要求的实际上是不可能的,这就是为什么一般规则是只有一个 UI 线程。如果另一个线程想要在屏幕上显示某些东西,它应该向 UI 线程发送一条消息,UI 线程将在渲染时显示该消息。