設計並行化遊戲引擎的框架

GameRes遊戲開發資源網http://www.gameres.com

設計並行化遊戲引擎的框架

作者:Jeff  Andrews

翻譯:Vincent

聯繫方式: QQ:14173579 MSN:[email protected]

 

設計一個功能可分解的、數據可分解的系統可以提供大規模的並行化執行,同時保證發揮多核處理器的性能。

 

隨着多核心處理器的降臨,對可並行計算遊戲引擎的需求已經變得越來越重要了。儘管僅僅依靠GPU和單線程遊戲引擎依然是可行的,但是在一個系統上使用多核處理器所具有的優勢會給用戶帶來更深刻的體驗。譬如,使用多核CPU一個遊戲可以增加更多的物理剛體對象來提升效果,或者開發出更加智慧的類人化的AI。

 

並行化遊戲引擎框架,或者稱爲多線程引擎,目的是在開發平臺上利用所有的處理器來提升性能。(引擎)通過並行化處理,各個功能模塊可以利用所有可用的處理器。當然,說比做要容易,畢竟在遊戲引擎中很多東西是互相交叉的,這通常會引起線程錯誤。因此,需要設計一套系統來合適地處理數據同步問題,同時避免被同步鎖所限制。此外,也需要一套方法來保證在並行方式下處理數據同步時使串行處理消耗儘可能小。本文要求讀者需要對現代計算機遊戲發展以及遊戲引擎線程編程有很好的理解和工作經驗。

 

2.並行處理態

 

並行處理態的概念對於一個高效的具有多線程運行時態的引擎來說是非常重要的。引擎如果要實現真正意義上的並行處理——即儘可能少的同步損耗,則需要引擎內部各個系統在運行時坐到儘量少的交互。儘管數據需要共享,但是現在每個系統都應該有自己的一份數據拷貝,而不是通過一個公共的方式來訪問數據。這樣各個系統之間將不再有數據依賴關係。任何一個共享數據的變化都會被送到一個狀態管理器那裏,並且被加入一個變化隊列,不妨稱作消息提示隊列。一旦各個系統完成處理任務,他們將會被提示改變自己的狀態,同時更新各自內部的數據結構(作爲消息隊列的一部分)。使用這一機制將會大大減少同步損耗,使得各個系統能更加獨立地工作。

 

2.1執行模式

 

當各個系統同步運行時(即各系統的操作被限制在同一個時鐘內),對於執行狀態的管理將會達到最優。這個時鐘的頻率可以等於幀速率,當然這並不是絕對的。這個時鐘的頻率甚至可以不是一個固定的值,然而若使這個跨度等於處理一幀所需要的時間——無論這一幀有多麼長,我們就可以完全不考慮頻率了。你對執行態的管理的實現將會決定這個時鐘跨度。圖示1描繪了不同系統在使用自由的時鐘步進時的狀態,這種狀態下這些系統並非在同一個時鐘內完成執行。除此之外,圖示2描繪了所有系統在同一個鎖定的時鐘下是如何執行的。

 

圖示1. 自由步進模式下的執行態

 

2.1.1 自由步進模式

 

在這一模式下系統的運行時間取決於任務所需要的時間。這裏的自由並非指系統在完成任務之前是不自由的,而是指系統可以自由選擇需要使用的時鐘數。

 

在這個方式下,一個普通的對於狀態變化的提示對於狀態管理器來說是不夠的,相關的數據也需要被包含在該提示中。這是因爲當一個系統修改了共享數據時它仍有可能還在執行,而這時別的系統也需要更新這些數據。這就需要越來越多的內存做備份,這種方式顯然不是最理想的。

 

2.1.2 鎖定步進模式

 

這一模式要求所有的系統在同一個跨度內完成各自的處理。這樣既易於實現同時又不需要將數據附加在提示中,因爲系統的狀態發生變化時可以在運行週期的結尾簡單地通過訪問別的系統來獲取數據。

 

鎖定步進模式可以通過在多個步驟中進行交叉執行來實現一個假的自由步進模式。譬如當AI在第一個時鐘計算出它初始的“宏觀視角”下的目標後,在下一個時鐘內它可以在宏觀目標下關注更具體的目標,而不僅僅是重複上一個宏觀目標。

 

圖示2. 鎖定步進下的執行態

 

2.2 數據同步

 

基於多個系統可以對同一個共享數據做出改變,那麼就需要確定在這些變化中到底那個值纔是正確且可以使用的。有兩種機制來解決這個問題:

l  時間,最後一個做出變化的系統的值是正確的。

l  權限,具有更高權限的系統的值是正確的。當多個系統擁有相同權限時可以與時間機制結合使用。

 

在這兩種機制下,那些被認爲是舊的數據將會被覆蓋或者從提示隊列中拋棄掉。

 

因爲數據是共享的,那麼在給數據賦相對值時可能因爲這些數據是沒有順序的而變得難以掌握。爲了消除這一障礙,當系統更新數據時使用絕對值來賦值以達到新舊交替。絕對值和相對值的結合使用是比較理想的,但是這也要根據情況而定。譬如,像位置,朝向這種公共數據,應該用絕對值來標識,這是因爲在創建一個變換矩陣時需要考慮接收數據的順序。然而,一個創建粒子的系統,在完全擁有粒子信息的情況下,可以只做相對值的更新。

 

3.引擎

 

設計引擎時應關注結構的彈性,以使得在擴展功能時更加簡便。基於此,引擎在各種受到限制(譬如內存)的平臺上應用時可以很好地做出調整。

 

引擎由兩部分組成,一部分是框架,另一部分是管理器。框架(章節3.1)包含了遊戲中會重複出現的擁有多個實例的那些部分,同時也包含那些出現在主循環的東西。管理器(章節3.2)作爲單件存在並且獨立於遊戲邏輯。

 

下面的圖描述了組成引擎的各個部分:


圖示 3:引擎的高級框架

 

值得注意的是,處理遊戲的功能,即某個系統,是與引擎區別對待的。基於模塊化的目的,將引擎作爲一種“膠水”將各個功能聯結起來。模塊化使得系統可以按照需要進行加載或者卸載。

 

接口是引擎和系統之間進行通信的途徑。在系統實現了接口之後引擎就可以使用系統的功能了,相反在引擎實現了接口之後系統也可以訪問引擎中的管理器。

 

附錄A對這一概念做出了更加清晰的解釋,“引擎示例圖”。正如章節2所言,“並行執行態”的概念使得系統在本質上是離散的。這樣系統在並行運行時就不會互相干擾。然而這種並行在系統之間需要通信時無法保證數據的穩定。系統間通信的理由有兩個:

l  通知另一個系統共享數據已經發生了變化。(譬如位置,朝向)

l  請求一些自身並不包含的功能。(譬如AI系統要求地形/物理系統執行一次射線碰撞檢測)

 

第一個通信問題通過實現前一章所述的狀態管理器來解決。狀態管理器將在章節3.2.3“狀態管理器”進行更詳細的討論。

 

要解決第二個問題,需要在系統中加入一個用來給不同系統提供服務的機制。章節3.2.3“服務管理器”將會進行深入的解釋。

 

3.1 框架

 

框架的作用是把引擎中不同的部分聯結起來。引擎的初始化將在框架內完成,但是管理器的初始化是全局的,不受框架影響。場景的信息同樣也保存在框架內部。基於彈性的考量,場景,或者稱爲通用場景,等於僅僅作爲容器組成整個場景的通用對象。章節3.1.2對此提供了更詳細的信息。

 

遊戲循環同樣在框架內執行,下面是遊戲循環的流程:

圖示 4:遊戲主循環

 

由於引擎運行在一個窗口環境,那麼遊戲循環的第一步就是處理來自操作系統的窗口消息。如果這些消息沒有被處理那麼引擎也不需要做額外的工作。下一步是由調度器向任務管理器發佈系統的任務。這一部分將在章節3.1.1進行更詳細的討論。接下來,由狀態管理器(章節3.2.2)跟蹤的消息被分發給需要做出響應的部分。最後,由框架來確認執行的狀態並決定引擎是否退出,還是繼續執行其他的任務,譬如進入下一個場景。引擎的執行態由環境管理器負責,這一部分將在章節3.2.4進行討論。

 

3.1.1 調度器

 

調度器管理主時鐘供執行時使用,主時鐘頻率應該是事先設置好的。時鐘頻率也可以是沒有限制的,譬如在基準測試模式下需要時鐘可以在運行結束前就停止。調度器通過任務管理器在一個時鐘長度內將系統進行註冊。在自由步進模式下(章節2.1.1)調度器和系統進行通信來決定系統完成執行所需要的時鐘數,以及哪些系統做好了執行的準備或者在某一個時鐘後完成執行。鎖定步進模式(章節2.1.2)下所有的系統的起始和結束都分別在同一個時鐘內,因此調度器只需要等待系統完成執行即可。

 

3.1.2 通用場景和對象

 

通用場景和對象作爲某些功能的容器存在於系統之中。通用場景和對象自身並不擁有任何功能,除了與引擎進行交互的功能。然而它們可以被擴展成包含系統功能的容器。由此這些容器可以在鬆耦合關係下接管可用系統的屬性,而不必與某個特定的系統進行粘合。鬆耦合這一特點使得系統之間可以互相獨立,從而使得並行執行成爲可能。下面的圖標描述了通用場景和對象在系統內的擴展:

圖示 5:通用場景和對象的擴展

 

擴展的工作實例如下:一個通用場景被擴展成可以包含圖形、物理,以及其他屬性的容器。圖形場景擴展用來初始化屏幕和其他渲染對象,物理場景擴展用來設置剛體世界,譬如重力等等。場景包含對象,因此,一個通用場景會擁有若干通用對象。一個通用場景也可以被擴展成爲包含圖形、物理,以及其他屬性的容器。圖形對象擴展用來具體渲染屏幕上的某一對象,物理對象擴展用來進行剛體之間的碰撞交互。引擎與系統之間的進一步的關係可以在附錄B的圖示“引擎與系統關係圖”中查看。

 

另一點需要指出的是,通用場景和通用對象需要將各自的擴展通過狀態管理器進行註冊,以此來響應其他擴展(譬如系統)造成的由變化產生的提示。譬如,某個圖形擴展在註冊後,可以捕獲由物理擴展造成的位置和朝向的變化所產生的提示。

 

更多的關於系統組件的信息可以在章節5.2“系統組件”找到。

 

3.2 管理器

 

管理器在引擎中作爲單件提供全局的功能,這意味着每一種管理器只有一個實例存在。這是由於它們管理的資源不應該被複制,否則將會造成冗餘以及給性能帶來潛在的影響。管理器同時也提供一些跨系統的通用功能。

 

3.2.1 任務管理器

 

任務管理器用自己的線程池來調度系統的任務。線程池爲每一個處理器分配一個線程來實現最優的n路處理,這樣做避免過度使用線程資源以及不必要的操作系統內的任務切換。

 

任務管理器從調度器接收需要處理的和需要等待的任務列表。調度器從各個系統獲得需要處理的任務列表。每一個系統只有一個主要任務,這個主要的任務根據自身需要處理的數據可以分成若干子任務。以上兩個特點可以被稱爲功能分解和數據分解。

 

下面的圖示描繪了任務管理器如何在一個四核系統上給線程分配任務:


圖示6:任務管理器和線程池實例

 

撇開調度器和主任務不說,任務管理器擁有一個初始化模式,憑藉各系統所在的線程來串行調用該系統,以此使得系統可以初始化由它儲存的本地線程。附錄D“關於實現任務的提示”可以幫助你一步步實現任務管理器。

 

3.2.2 狀態管理器

 

狀態管理是消息機制的一部分,它用來跟蹤由某一系統的變化產生的提示,並且將這些提示分配給其他需要響應的系統。爲了減少不必要的廣播提示,系統必須註冊那些自己感興趣的提示。這個機制是基於觀察者模式的,這一模式可以在附錄C“觀察者設計模式”得到更詳細的解釋。簡單地講,觀察者模式就是:觀察者觀察任何感興趣的變化,控制者作爲傳遞者將這個變化傳遞給觀察者。

 

這一機制的工作原理如下:

1.觀察者向控制者(狀態管理器)註冊自己感興趣的對象。

2.當對象的某一屬性發生變化時,它將這一變化傳遞給控制者。

3.當控制者收到來自框架的提示時,它將這一提示轉交給觀察者。

4.觀察者訪問這一對象來獲得具體發生變化的數據。

 

自由步進模式(章節2.1.1)會給這一機制帶來額外的複雜性。首先,當提示產生時相關的數據需要被包含,這是由於產生這一提示的系統也許會因爲還在運行中,從而使得通過訪問該系統獲得共享數據無法實現。接下來,如果某個需要接收提示的系統在時鐘結束時還不能做好接收提示的準備,那麼狀態管理器需要保留該提示直到系統做好準備。

 

這一框架實現了兩個狀態管理器,分別在場景層面和對象層面來處理變化。這樣做的原因是:大多數情況下,場景和對象的消息是不同的,所以將它們分開可以減少不必要的消息處理。然而,任何跟場景有關的對象的變化都應該註冊給場景,以此使得場景可以收到這些提示。爲了減少同步消耗,狀態管理器將會爲每一個由任務管理器創建的線程準備一個變化隊列。這樣當訪問這些隊列時就不會造成同步。這些隊列在執行完畢後可以使用章節2.2中提到的方法來合併。

 

圖示7:內部的通用對象變化提示

 

當你認爲這些變化的提示應該被串行地分發時,事實上將之並行化處理也是可行的。當系統處理各自的任務時它們會在所有的對象上進行操作。譬如,如果對象之間發生了交互,物理系統會移動對象,檢測碰撞,設置新的作用力等等。在變化提示的過程中某個系統的對象將不再和本系統內的對象發生交互,但是卻會和自身所關聯的其他擴展對象發生交互。這意味着該系統內的通用對象在此時是互相獨立的,從而可以並行地被更新。注意,儘管在少數情況下需要同步處理,然而,從前一些看上去必須串行處理的東西現在可以被並行化了。

 

3.2.3 服務管理器

 

服務管理器爲系統提供了自身所不具備的功能的訪問途徑。需要注意的是,服務管理器並不直接爲系統提供服務,而是通過預定義的接口來實現。任何實現了這些暴露的接口的系統可以將自身註冊給服務管理器來獲得服務。

 

由於引擎的設計目的是爲了使系統之間儘可能保持離散,因此可以提供的服務事實上是很少的。同時,系統自身不能提供任何需要的服務,而只能通過服務管理器來選擇。

 

圖示8:服務管理器實例

 

服務管理器的另外一個角色是給各個系統提供互相訪問屬性的途徑。屬性是一些系統專有的不通過消息系統傳遞的值。譬如圖形系統的窗口分辨率,或者物理系統的重力值。服務管理器提供的訪問途徑不允許系統進行直接訪問。這樣做也可以保證屬性發生的變化可以串行被地加入隊列和分發出去。注意訪問系統屬性這件事是極少發生的,因此這不需要被當作一個普遍的應用。在控制窗口打開/關閉線框模式,或者玩家通過界面系統改變屏幕分辨率時這些訪問纔會發生,因此基本上這些訪問不會每一幀都出現。

 

3.2.4 環境管理器

 

環境管理器爲引擎的運行環境提供功能。下面是一個由環境管理器提供的功能組列表:

l  變量。引擎內共享的變量名和數據。通常在加載場景或者用戶設定之後被設置,或者被各個系統查詢與或執行的結果。

l  執行。關於執行的信息,譬如場景的結束或者程序的結束。通常被引擎或者系統進行設置或查詢。

 

3.2.5 平臺管理器

 

平臺管理器處理對操作系統的調用,在這些調用之上提供一些附加的功能。這樣做的好處是,將多個普通的功能打包來響應一個調用,從而不必分步實現所有調用並且毋須關注它們之間的細微差別。

 

一個例子來自平臺管理器加載系統的動態鏈接庫的調用。除了加載系統,管理器還要獲得函數入口點,然後調用庫的初始化函數。管理器還會爲這個庫保留一個句柄,在引擎退出後卸載庫。

 

平臺管理器還要提供處理器的信息,譬如處理器支持哪一個SIMD指令,以及進程初始化的反應。這些功能僅供系統查詢使用。

 

4 接口

 

接口爲框架,管理器和系統之間進行通信提供了途徑。在引擎內部框架可以直接訪問管理器。然而,系統並不駐留在引擎中,再加上系統之間的功能有所差異,因此需要提供一個通用的方法來訪問它們。此外,系統不能直接訪問管理器,因此需要爲系統提供方法來訪問它們,但是這並非是必要和全面的,因爲系統內部的東西應該只允許訪問框架。

 

接口提供了一組通用的訪問方法。這樣一來框架就可以通過這些顯示的方法和系統進行通信,從而沒有必要知道具體到每一個系統的細節。

 

4.1 對象和觀察者接口

 

對象和觀察者接口用來將對象註冊給觀察者,從而可以將對象發生的變化傳遞給觀察者。使用觀察者進行註冊/註銷這一功能對每一個對象都是通用的。

 

4.2 管理器接口

 

管理儘管作爲單件存在,然而它們只允許框架訪問自己,對於各個系統而言是無法訪問的。爲了給系統提供訪問途徑,每一個管理器需要提供一個接口來暴露一些功能子集。當系統初始化之後便可以訪問這些子集了。

 

接口的定義是與管理器有關的,因此這些接口並不是通用的,而是根據每一個管理器來定義的。

 

4.3 系統接口

 

爲使框架能夠訪問自己,系統也需要實現一些接口供框架使用。如果沒有這些接口框架就不得不自己去實現每一個新增加的系統。

 

系統由四個組件組成,因此係統需要實現四個接口。它們是:系統、場景、對象,以及任務。這幾個組件將在章節5“系統”進行闡述。接口的作用就是用來訪問這些組件。系統接口用來創建和銷燬場景。場景接口用來創建和銷燬對象,此外還用來獲取主任務。任務接口被任務管理器用來在線程池內發佈任務。

 

場景接口和對象接口繼承自對象和觀察者接口,這樣一來系統之間,通用場景和通用對象之間就可以互相通信了。

 

4.4 變化接口

 

還有一些專門的接口用來在系統之間傳遞數據。任何做出調整的系統都必須實現這些接口。以地形爲例,地形接口可以獲取某個對象的位置,朝向,以及尺寸。任何對地形做出調整的系統都必須實現地形接口,這樣某個系統就可以在獲取地形變化時毋須關心其他系統了。

 

5 系統

 

系統爲引擎提供了所需的遊戲功能。沒有這些系統引擎將在一個無任務的狀態下無限循環。爲了使引擎和系統相互獨立,系統必須實現章節4.3“系統接口”所敘述的那些接口。這樣一來,當向引擎增加新的系統時就不需要關心細節問題,使過程更爲方便。

 

5.1 類型

 

引擎應該包含一些預定義好的系統類型作爲標準遊戲組件。譬如:地形,圖形,物理(剛體碰撞),聲音,輸入,人工智能,以及動畫。

 

在這些常用功能之外,自定義類型的系統也是可以考慮的。需要注意的是,自定義類型的系統需要自己給其他系統提供接口,因爲引擎並不提供這些信息。

 

5.2 系統組件

 

一個系統需要實現若干組件。譬如:系統、場景、對象,以及任務。這些組件用來與引擎中其他部分進行通信。

 

下面的圖示描述了組件之間的關係:

圖示9:系統組件

 

附錄A“引擎和系統關係圖”爲引擎和和系統之間的關係提供了更爲詳細的信息。

 

5.2.1 系統

 

系統組件用來初始化系統資源,這些系統資源在整個引擎運行過程中基本上是保持不變的。譬如:圖形系統分析所有資源的位置以確定如何實現更快的加載,但是並不關心這些資源的用處。同樣,圖形系統設置的屏幕分辨率也是這樣一類資源。

 

系統爲框架提供了主入口點,以及自身的信息。譬如自己的類型,創建和銷燬場景的方法。

 

5.2.2 場景

 

場景組件,或者稱爲系統場景,用來管理與場景相關的資源。通用場景將這種場景作爲擴展功能來使得系統提供的屬性可用。譬如物理場景在場景初始化之後創建了一個世界並給它設置重力屬性。

 

場景提供了創建和銷燬對象的方法。它同時擁有任務組件,這個任務組件除了對場景進行操作外,還提供獲取該場景的方法。

 

5.2.3 對象

 

對象組件也可以稱爲系統對象,它們與場景內可以被玩家看到的對象相關聯。通用對象將對象組件作爲功能擴展使得外部可以通過通用對象暴露的接口來訪問該對象組件的屬性。

 

舉例來說,一個通用對象將地形,圖形和物理進行擴展,在屏幕上創建一個木質的大梁。地形系統保留對象的位置,朝向和尺寸信息;圖形系統將網格顯示在屏幕上;物理系統對它使用剛體碰撞檢測和重力效應。

 

在某個特定場合裏,一個系統對象可能會對其他的通用對象或者其擴展的變化感興趣。這時就可以通過通用對象來建立鏈接使得這個系統對象可以觀察到其他的對象。

 

5.2.4 任務

 

任務組件,即系統任務,用來對場景進行操作。當一個任務得到來自任務管理器的更新指令時,它會對場景內的對象使用系統功能進行操作。

 

任務可以在任務管理器的幫助下將自身分解成若干子任務,從而進行額外的多線程操作。這要求引擎可以快速地配置多核處理器。這一技術就是前面提到的數據分解。

 

在場景更新過程中,場景中任何對象的變化都被傳遞至狀態管理器。參閱章節3.2.2詳細瞭解狀態管理器。

 

6 總結

 

由於各章節之間相互交叉,想要一次性吸收這些信息比較困難。引擎的工作流程可以被分解成如下的幾個部分。

 

6.1 初始化階段

 

引擎從初始化管理器和框架開始:

l  框架調用場景加載器來加載場景。

l  加載器決定該場景將會使用到哪些系統,然後通知平臺管理器來加載這些模塊。

l  平臺管理器加載模塊,通過管理器接口命令管理器創建新的系統。

l  模塊返回系統實例的指針。

l  系統模塊向服務管理器註冊自己能夠提供的服務。

 

圖示10:引擎管理器和系統初始化

 

6.2 場景加載階段

 

這一階段將控制權交給場景加載器:

l  加載器創建一個通用場景,然後通過所有系統的接口來實例化系統場景,接着將這些系統場景成爲這個通用場景的擴展。

l  通用場景檢查系統場景來確認它們如何改變共享數據,以及自身可能收到的關於共享數據的變化。

l  通用場景向狀態管理器註冊與變化相匹配的系統場景使得未來這些場景可以收到對變化的提示。

l  加載器爲每一個場景中的對象創建一個通用對象,然後確定這個通用對象應該成爲哪些系統的擴展。對通用對象的註冊方式與通用場景相類似。

l  加載器通過系統場景的接口將系統對象實例化,然後讓這些系統對象成爲通用對象的擴展。

l  調度器通過系統場景接口來確定它們的主要任務,然後將這些任務發佈給任務管理器。

圖示11:通用場景和對象的初始化

 

6.3 遊戲循環階段

l  調用平臺管理器處理所有窗口消息,以及其他跟平臺相關的操作。

l  操作被傳遞給調度器,調度器等待時鐘結束。

l  調度器在自由步進模式下檢查在上一個時鐘內哪些系統任務完成了。然後把所有準備好的任務發佈給任務管理器。

l  調度器確定在當前時鐘內哪些任務將要結束,並做好結束準備。

l  在鎖定步進模式下,調度器將所有任務發佈出去,然後在每一個時鐘步進都檢查是否有完成的任務。

 

6.3.1 任務的執行

 

執行操作被傳遞給任務管理器。

l  任務管理器將任務進行排列,然後開始將任務分配給可用的線程。

l  任務在執行過程中會修改整個場景或者某個具體的對象的內部數據結構。

l  任何共享數據,譬如位置和朝向,都應該在其他系統中有複本。系統任務通過命令產生變化的系統場景或者系統對象通知給各自的觀察者來實現這個目的。在這個情形下觀察者事實上就是狀態管理器內部控制變化的那個控制器。

l  變化控制器將變化信息進行排列以便進行後續的處理,那些觀察者不感興趣的變化類型通常是可以忽略的。

l  任務可以命令服務管理器提供需要的服務。服務管理器還可以用來改變某些沒有暴露在消息機制中的系統的屬性。(譬如玩家通過輸入系統改變了圖形系統內部的屏幕分辨率)

l  任務也可以調用環境管理器來獲取環境變量,以及更改運行時狀態。(譬如暫停運行,進入下一個場景等等)


圖示12:任務管理器和任務

 

6.3.2 分發

 

一旦當前時鐘週期內所有任務都結束了,主循環就會命令狀態管理器來分發變化:

l  狀態管理器命令變化控制器分發在隊列中的變化。這個過程通過檢查每一個變化的觀察者來完成。

l  變化控制器將變化告知觀察者(產生該變化的對象的指針同時也被傳給觀察者)。在自由步進模式下,觀察者通過控制器或者發生改變的數據,在鎖定步進模式下觀察者可以直接訪問對象來獲得數據。

l  對某個系統對象的變化感興趣的觀察者可能是跟這個對象粘連在同一個通用對象的系統對象。這樣就可以將變化分發給並行運行的任務。爲了減少同步,可以將產生自同一個通用對象的任務打包進行處理。

 

6.3.3 運行時檢查與退出

 

主循環的最後一步就是檢查運行時狀態。像運行,暫停,進入下一個場景等等都可以被當作運行時狀態。如果運行時狀態是運行態那麼整個遊戲循環會不斷重複。如果運行時被設定爲退出那麼遊戲就會退出,釋放資源,然後結束程序。

 

最後的考量

 

整個文章的關鍵是章節2“並行執行態”。設計可以分解功能,分解數據的系統可以提供大規模的並行運算,同時保證了發揮未來更多核心處理器的性能。需要記住的是,要在消息機制下使用狀態管理器來儘量減小數據同步消耗。

 

觀察者模式是一種利用消息機制的模式,爲了滿足引擎對這方面的需求,需要花費一定的時間來學習和實現它。畢竟,是系統之間的通信機制來完成共享數據同步的工作。

 

任務機制在處理負載平衡上扮演了重要的角色。附錄D可以幫助你的引擎創建一個高效的任務管理器。

 

正如你所看到的,利用定義明晰的消息和架構,設計一個高度並行化的引擎是可行的。適度的並行化可以使得你的遊戲引擎在使用現在和未來的處理器時獲得可觀的性能提升。

 

附錄A 引擎圖示

 

遊戲主循環開始運行(參閱圖示4,“主遊戲循環”)

 

附錄B 引擎和系統關係圖

 

附錄C 觀察者模式

 

觀察者模式可以在《設計模式:可複用面向對象軟件的基礎》一書中找到。

 

這一模式的基本理念就是對某些數據或狀態變化感興趣的東西沒有必要無時無刻去查詢這些變化是否發生。這一模式定義了一個對象和一個觀察者來處理變化提示。工作原理是:觀察者觀察這一對象是否發生了變化。變化控制器在這二者之間扮演一個傳遞者的角色。下圖描述了這個關係:

圖示13:觀察者模式

 

下面是整個事件的流程:

1.觀察者將自己和希望觀察的對象註冊給變化控制器。

2.變化控制器事實上也是一個觀察者。與其他觀察者不同,它不需要將自己和對象進行註冊,相反,它自身擁有一個列表用來記錄哪個觀察者和哪個對象被註冊了。

3.對象(事實上也是變化控制器)將觀察者插入自己的一個觀察者列表。通常你也可以給變化分類供觀察者使用,這樣可以提高變化提示的分發速度。

4.當對象的數據或者狀態發生變化時,它通過回調機制將變化類型的信息告知觀察者。

5.變化控制器將變化排隊,等待分發的信號。

6.在分發過程中控制器調用對應的觀察者。

7.觀察者向對象查詢來獲得發生變化的數據和狀態(或者直接在消息中獲得數據)。

8.當觀察者不再對對象感興趣,或者該對象已經被銷燬,觀察者就告知變化控制器將自己與該對象的關係註銷。

 

附錄D。實現任務機制的建議

 

實現任務分發的方法有很多,然而最佳的一種就是讓工作着的線程數等於平臺上可用的處理器數。如果將雷同的任務指派給一個線程,那麼就會導致線程內部的負載不平衡,因爲各個系統並不會在同一時刻完成任務,這樣將會大大地削弱並行性。建議你研究一下任務庫,譬如Intel的ThreadingBuilding Blocks,它會大大地簡化這一過程。

 

爲保證CPU可以友好地工作,可以對任務管理器進行一些優化:

反向發佈,如果要發佈的主要任務的順序是相對靜態的,那麼每一幀就可以有選擇地反向發佈這些任務。上一幀執行的任務的數據很有可能在緩存中駐留,因此在下一幀反向發佈任務可以保障CPU緩存中數據是正確的,不需要被更新。

緩存共享,有些多核處理器會將共享緩存分成幾個部分,這樣兩個處理器就可以共享同一塊緩存。如果來自同一個系統的多個子任務被分配給了擁有共享緩存的處理器,那麼任務的數據在這一共享緩存中的可能性就會增加。


發佈了10 篇原創文章 · 獲贊 0 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章