【世界播资讯】28.从chrome消息队列谈unity3d的UI框架设计


【资料图】

1 缘起

在UI架构圈,MVC是一个著名的小团伙,其中M是被偷窥重度成瘾者,不过我们一般称之为观察者,毕竟读书人的事,怎么能叫偷呢对吧。

M喜欢散播自己的花边小新闻,屁大点儿事,都会翻译成小道消息重大事件散播出去。VC则是俩狗仔,不管M出了啥事,总能第一时间闻到消息,摇旗呐喊。

这种玩法同时满足了M和狗仔们的大多数需求,因此很快在UI架构圈流行开来。去年我用unity3d设计过一版UI框架,也是基于MVC模式。

本来这样也挺好,M出自己的名,狗仔追自己的星。但M太秀了,有时候一股脑爆出一大波新闻,让狗仔们忙得应接不暇,有时候又波澜不惊,让狗仔们急得抓耳挠腮。

MVC模式中,M抛出的事件默认都是同步处理的。这带来了一些问题:如果同一帧内收到并处理太多事件的话,可能由于处理时间太长,导致游戏卡顿。

2 取经

最近的学习研究chrome浏览器的一些架构设计,发现跟unity3d游戏设计有一些相似相溶的地方,随手做了一番对比:

Unity3dChrome
消息队列消息队列
定时任务setTimeout(), 延迟队列
观察者MutationObserver
Task/CoroutinePromise
async/awaitasync/await
游戏渲染帧requestAnimationFrame()

早期的js对dom事件的支持并不友好,只能通过setTimeout()轮询处理。这种方式简单粗暴,但也有一些问题:如果轮询间隔时间过短,则大部分情况下CPU在空转;而如果轮询间隔时间过长,则dom事件会得不到及时响应。

2000年的时候js引入了Mutation Event,采用观察者模式,当dom发生变化时立刻触发相应事件。Mutation Event属于同步回调模式,解决了实时响应的问题,但也为页面展示带来了性能隐患。当我们使用js大量修改dom结构时,会频繁抛出事件,如果所有的dom事件都同步处理,势必会导致页面卡顿。

也因此,Mutation Event被反对使用,并逐渐从web标准事件中被移除。dom4之后推荐使用MutationObserver,其作出的改进其实主要就一点:把所有的dom事件统一放入到微任务消息队列中,在每一帧的结尾一次性触发。通过异步回调解决性能问题,通过微任务解决实时性问题。

3 消息队列

chrome中的异步任务体系都是基于消息队列完成的,包括dom事件、fetch网络请求、promise异步任务等等。

js中的异步任务还细分为了宏任务与微任务,其中只有MutationObserver 和promise是微任务,其它的像setTimeout()之类的都是宏任务。

宏任务由宿主(比如chrome中的window对象)维护,但js引擎需要有自己的异步任务体系,因此引入了微任务队列。

后端也有消息队列,比如Kafka、RocketMQ等,但与chrome把消息队列主要用于异步任务处理不同,后端消息队列最重要的作用主要有三:定序 (Ordering),分离式数据库 (Unbundling Database)、重放 (Replay)。以首字母可简记为:OUR。

我们在做unity3d框架设计的时候,其实也有大量使用消息队列用于处理异步任务。

比如处理网络协议:通常的做法是单独起一个网络线程,接收网络协议,并把它们放到消息队列中,然后由主线程按顺序异步处理这些网络协议。

再比如Coroutine库:可以把所有的协程对象按顺序放入到一个消息队列中,每一帧按顺序遍历它们,并调用它们的next()方法。

这中间还诞生过一个很有趣的技巧:放置于消息队列中的消息,往往对先后顺序有要求,但对处理时间并不敏感。如果发现当前帧已经占用了太长的时间,可以直接跳出当前帧处理过程,把剩余的消息推迟到下一帧去处理。即无需每一帧都清空当前消息队列,从而可以缓解游戏卡顿的问题。我把这个小技巧称为分帧。

4 缘落

但UI框架遇到的问题又略有不同。游戏中有很多非常复杂的UI,比如背包,在加载结束之后,需要按需初始化大量的控件。初始化控件的逻辑是游戏业务逻辑,而不是框架底层逻辑,因此很难完全封装在框架中。另外,初始化过程通常是在一帧内完成的,数量越多越有可能导致游戏卡顿。

针对这个问题,通用优化方案是将这些控件预先拖到一个MonoBehaviour脚本中,从而在加载的时候同步完成初始化过程。这个方案不是本文的重点,因此不再更多展开。另外,这个方案只能解决静态控件的初始化问题,对于背包这种需要根据服务器协议初始化大量动态控件的复杂UI起不到优化效果。

基于前述消息队列和分帧的思想,再参考MonoBehaviour的Start()方法可以配置成协程,我这里提一个新的优化思路:把UI框架中用于控件初始化的OnLoaded()方法设计为协程,这样当遇到大量控件需要初始化的时候,可以支持分帧处理,从而缓解因为大量初始化动态控件导致的卡帧。

这个设计会带来一些新的问题,其中一个是:很多UI事件会有先后顺序要求,比如UI框架中的OnOpened()方法应该在OnLoaded()方法之后回调。但将OnLoaded()方法修改为协程后,就可能发生OnLoaded()方法未完全回调完成,就回调到了OnOpened()方法的情况。这需要进一步调整UI框架逻辑,以约束UI事件之间的先后顺序关系。

5 缘尽

相逢是缘,相知是福,再不关注,就真的缘尽了老板。

5 References

本文提到的UI框架位于unicorn库中,unicorn-examples示例代码

18 | 宏任务和微任务:不是所有任务都是一个待遇

关键词: 网络协议 动态控件 轮询间隔

为您推荐

戏怨攻略大全 戏怨游戏攻略第一章/第二章/第三章/第四章/第五章图文流程[多图] 今日报

戏怨游戏怎么通关?这款恐怖类型的解密游戏一共有五章,不同的章节关卡

来源:游戏鸟手游网2023-07-03

京东方A:2023年柔性AMOLED出货量目标为超1.2亿片

京东方A近期接受投资者调研时称,2023年,随着公司柔性AMOLED业务的

来源:新京报2023-07-03

税务小哥vlog:探访“专精特新”“小巨人”税费优惠政策培训

朝阳区税务局联合朝阳园管委会、朝阳区社保中心合作开展朝阳区内北京市

来源:北京商报官方账号2023-07-03

【世界播资讯】NBA5消息:太阳600万得戈登!热火追利拉德受阻,勇士再签1名后卫

前言NBA休赛期自由市场开启之后,各家球队动作频频,追逐心仪的球员,

来源:江湖再无24号2023-07-03

「抖音618团券节」热卖品牌榜出炉,这些商家卖爆了!|今日报

肯德基、海南爱大集国际旅行社、伊颜悦色 抖音618团券节期间,来

来源:互联网2023-07-03

莫里森(关于莫里森的简介)-环球热推荐

1、莫里森,1971年7月9日出生,美国田径运动员,是美国110米栏名将,曾

来源:互联网2023-07-03

单机卸率每小时3057吨 青岛港第26次刷新铁矿石接卸世界纪录 快看

每经AI快讯,7月2日,随着最后一个清舱机械吊装出舱,“远谊海”轮在青

来源:每日经济新闻2023-07-03

面部整形美学设计_面部整形大概多少钱

1、面部整形的概念比较模糊,需要具体情况具体分析才能判断价格;如果

来源:互联网2023-07-03