用经典的生产-消费者模型解决游戏开发中异步加载和使用问题
封面来自:https://www.youtube.com/watch?app=desktop&v=VXJSJ6c3ZIs
前言
最近实践了很多有关异步记载和使用的业务场景,无一例外的,由于 异步
的存在,业务代码需要非常小心的维护各种状态,才能足够稳定和安全
只遇到一次还好,代码和人有一个能跑就行,遇到很多次心情就很烦躁了,所以准备抽空研究下这种业务场景较为通用的解决方案
正文
场景例程
- 整个世界被拆分成无数个nxn的tile,需要根据相机位置动态加载,卸载tile(以下简称
地图流式行为
- 相机位置每帧都在变动,即每帧都有可能触发地图流式行为,正常视角渲染所需tile数量在32~50之间
- 出于表现和内存性能的平衡考虑,最大支持同时存在256个加载的tile,基于LRU策略进行调度
- tile的加载是异步的
- 最终需要把加载的tile资源按照正确的索引上传至GPU
问题展示
直接上代码,我们以一帧的LateUpdate为例(此LateUpdate每帧执行),将资源加载和使用看为一个整体
但由于异步的特性,当某一帧的异步完成时,一些数据和状态可能已经发生了变动,会影响到另一帧的状态,而我们这里又只维护了本帧的任务状态
,所以需要额外更新一次数据,将其融入到全局的状态表
中,才能保证每一次异步的处理都是正确的,而不会因为异步时序的不确定性导致冲突
1 | public async UniTaskVoid LateUpdate() |
而生产者-消费者模型可以很好的规划和解耦此类问题
解决方案
简单来说就是解耦生产(加载)和消费(上传GPU)流程,使得生产只处理生产,消费只处理消费
代码中特殊标识出来的行表示数据处理是全局的生产者队列
1 | private async UniTaskVoid Produce() |
至此,我们将维护的状态对象从每帧的异步内容转换为全局的生产者队列
总结
其实大家可以看到,两种实现方式代码差别并不大,只是把维护的目标数据从一些复杂的状态转换成单一的,全局生产者队列而已,换句话说,我们当然也可以将原本的代码优化成只维护一份全局的数据状态的形式来达成同样的效果
这更多的是一种思路和理解成本上的优化,这对于程序的迭代是大有裨益的
参考
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 登峰造极者,殊途亦同归。!
评论