<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          28.從chrome消息隊列談unity3d的UI框架設計

          共 2291字,需瀏覽 5分鐘

           ·

          2023-03-04 11:33

          1 緣起

          在UI架構圈,MVC是一個著名的小團伙,其中M是被偷窺重度成癮者,不過我們一般稱之為觀察者,畢竟讀書人的事,怎么能叫偷呢對吧。

          M喜歡散播自己的花邊小新聞,屁大點兒事,都會翻譯成小道消息重大事件散播出去。VC則是倆狗仔,不管M出了啥事,總能第一時間聞到消息,搖旗吶喊。

          這種玩法同時滿足了M和狗仔們的大多數需求,因此很快在UI架構圈流行開來。去年我用unity3d設計過一版UI框架,也是基于MVC模式。

          本來這樣也挺好,M出自己的名,狗仔追自己的星。但M太秀了,有時候一股腦爆出一大波新聞,讓狗仔們忙得應接不暇,有時候又波瀾不驚,讓狗仔們急得抓耳撓腮。

          MVC模式中,M拋出的事件默認都是同步處理的。這帶來了一些問題:如果同一幀內收到并處理太多事件的話,可能由于處理時間太長,導致游戲卡頓。


          2 取經


          最近的學習研究chrome瀏覽器的一些架構設計,發(fā)現(xiàn)跟unity3d游戲設計有一些相似相溶的地方,隨手做了一番對比:

          Unity3d
          Chrome
          消息隊列
          消息隊列
          定時任務
          setTimeout(), 延遲隊列
          觀察者
          MutationObserver
          Task/Coroutine
          Promise
          async/await
          async/await
          游戲渲染幀
          requestAnimationFrame()

          早期的js對dom事件的支持并不友好,只能通過setTimeout()輪詢處理。這種方式簡單粗暴,但也有一些問題:如果輪詢間隔時間過短,則大部分情況下CPU在空轉;而如果輪詢間隔時間過長,則dom事件會得不到及時響應。

          2000年的時候js引入了Mutation Event,采用觀察者模式,當dom發(fā)生變化時立刻觸發(fā)相應事件。Mutation Event屬于同步回調模式,解決了實時響應的問題,但也為頁面展示帶來了性能隱患。當我們使用js大量修改dom結構時,會頻繁拋出事件,如果所有的dom事件都同步處理,勢必會導致頁面卡頓。

          也因此,Mutation Event被反對使用,并逐漸從web標準事件中被移除。dom4之后推薦使用MutationObserver,其作出的改進其實主要就一點:把所有的dom事件統(tǒng)一放入到微任務消息隊列中,在每一幀的結尾一次性觸發(fā)。通過異步回調解決性能問題,通過微任務解決實時性問題。


          3 消息隊列


          chrome中的異步任務體系都是基于消息隊列完成的,包括dom事件、fetch網絡請求、promise異步任務等等。

          js中的異步任務還細分為了宏任務與微任務,其中只有MutationObserver 和promise是微任務,其它的像setTimeout()之類的都是宏任務。

          宏任務由宿主(比如chrome中的window對象)維護,但js引擎需要有自己的異步任務體系,因此引入了微任務隊列。


          后端也有消息隊列,比如Kafka、RocketMQ等,但與chrome把消息隊列主要用于異步任務處理不同,后端消息隊列最重要的作用主要有三:定序 (Ordering),分離式數據庫 (Unbundling Database)、重放 (Replay)。以首字母可簡記為:OUR。

          我們在做unity3d框架設計的時候,其實也有大量使用消息隊列用于處理異步任務。

          比如處理網絡協(xié)議:通常的做法是單獨起一個網絡線程,接收網絡協(xié)議,并把它們放到消息隊列中,然后由主線程按順序異步處理這些網絡協(xié)議。

          再比如Coroutine庫:可以把所有的協(xié)程對象按順序放入到一個消息隊列中,每一幀按順序遍歷它們,并調用它們的next()方法。

          這中間還誕生過一個很有趣的技巧:放置于消息隊列中的消息,往往對先后順序有要求,但對處理時間并不敏感。如果發(fā)現(xiàn)當前幀已經占用了太長的時間,可以直接跳出當前幀處理過程,把剩余的消息推遲到下一幀去處理。即無需每一幀都清空當前消息隊列,從而可以緩解游戲卡頓的問題。我把這個小技巧稱為分幀。


          4 緣落

          但UI框架遇到的問題又略有不同。游戲中有很多非常復雜的UI,比如背包,在加載結束之后,需要按需初始化大量的控件。初始化控件的邏輯是游戲業(yè)務邏輯,而不是框架底層邏輯,因此很難完全封裝在框架中。另外,初始化過程通常是在一幀內完成的,數量越多越有可能導致游戲卡頓。

          針對這個問題,通用優(yōu)化方案是將這些控件預先拖到一個MonoBehaviour腳本中,從而在加載的時候同步完成初始化過程。這個方案不是本文的重點,因此不再更多展開。另外,這個方案只能解決靜態(tài)控件的初始化問題,對于背包這種需要根據服務器協(xié)議初始化大量動態(tài)控件的復雜UI起不到優(yōu)化效果。


          基于前述消息隊列和分幀的思想,再參考MonoBehaviour的Start()方法可以配置成協(xié)程,我這里提一個新的優(yōu)化思路:把UI框架中用于控件初始化的OnLoaded()方法設計為協(xié)程,這樣當遇到大量控件需要初始化的時候,可以支持分幀處理,從而緩解因為大量初始化動態(tài)控件導致的卡幀。

          這個設計會帶來一些新的問題,其中一個是:很多UI事件會有先后順序要求,比如UI框架中的OnOpened()方法應該在OnLoaded()方法之后回調。但將OnLoaded()方法修改為協(xié)程后,就可能發(fā)生OnLoaded()方法未完全回調完成,就回調到了OnOpened()方法的情況。這需要進一步調整UI框架邏輯,以約束UI事件之間的先后順序關系。


          5 緣盡

          相逢是緣,相知是福,再不關注,就真的緣盡了老板。

          5 References

          1. 本文提到的UI框架位于unicorn庫中,unicorn-examples示例代碼

          2. 18 | 宏任務和微任務:不是所有任務都是一個待遇


          瀏覽 57
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  台湾无码字幕无码 | 欧洲久久青青草 | sm调教网站 | 婷婷久久内射频 | 色情 视频网站入口第一集 |