細談unity資源載入和解除安裝

zblade發表於2019-06-27

轉載請標明出處:http://www.cnblogs.com/zblade/

一、概要

在瞭解unity的資源管理方式之後,接下來細談一下Unity的資源是如何從磁碟中載入到執行時的記憶體中,以及又是如何被解除安裝的。這部分較為繁瑣,可能會寫較多的過程。

二、指令碼資源的載入和解除安裝

在unity中的指令碼資源,大體可以分為C++編譯的引擎dll檔案,c#編譯的dll檔案,lua指令碼檔案(基於lua熱更的方式下)。

2.1 dll檔案的載入和解除安裝

在工程的Library/ScriptAssemblies資料夾下,會有當前工程非引擎相關的dll檔案列表:

在遊戲啟動的時候,會執行Assembly.Load的操作,將這些dll檔案載入進程式中(editor類相關的dll不會被載入)。

能夠載入,自然也能在執行時被解除安裝,所以目前一種熱更新方案ILRuntime就是對dll檔案進行載入,熱更新,解除安裝,載入最新的dll這樣的方式進行操作。這種熱更新方式主要是針對Assembly-CSharp.dll/Assembly-CSharp-firstpass.dll進行操作。
如果不是主動進行解除安裝,那麼這些被載入的dll檔案,在遊戲程式中,是不會被解除安裝釋放的,只有在遊戲程式結束的時候,才會被系統從記憶體中解除安裝出去。

2.2 lua檔案的載入和解除安裝

lua由於指令碼檔案的屬性,可以被當做類文字檔案進行熱更新,同時在遊戲啟動的時候才開啟一個Lua虛擬機器,在Lua虛擬機器中才執行lua檔案的require相關操作。
這類檔案的載入,其實質就是將這部分程式碼讀入到lua虛擬機器的全域性快取中,而所謂的解除安裝,就是將這部分快取置為nil,和上面的dll檔案的載入和解除安裝含義有一些差異。    

三、非指令碼資源的載入和解除安裝

非指令碼資源,才是整個遊戲程式中需要處理的主要部分,會伴隨整個遊戲程式,直到遊戲程式結束。
個人對unity對資源的載入過程的理解,其本質就是一個反序列化的過程。

3.1 Serialization and Instance

unity在序列化的時候,對於每個元件,也是單獨逐個的執行序列化的操作的,其序列化資訊的關鍵資訊是檔案本身的fileID, 以及依賴檔案的fileID 和guid.
對應的,在unity的Instance操作中,unity會為該GameObject建立一個唯一的InstanceID, 在程式內部會快取這樣一個InstanceID <-> gameobject的對映關係表,同時 fileID/GUID/LocalID 會對應的對映到該檔案的原始檔儲存位置。這個InstanceID具有唯一性,當InstanceID建立完成後,如果object沒有被load,則會觸發unity執行一次資源的load,基於fileID/Guid/local id來執行object的載入。
實際的遊戲執行中,並不會直接依賴fileID/GUID來執行檔案的對映,而是會將這兩個ID轉換成一個新的ID,所以在實際執行的遊戲中是看不到fileID/GUID相關檔案的。

3.2 InstanceID的建立和失效

在遊戲啟動的時候,會將啟動場景以及Resources目錄下的資源逐個建立對應的InstanceID, 放入到快取中,這部分InstanceID的建立耗時會隨著Resources目錄包含資源的增多而增大,所以儘量減小這部分資源的數目。
在遊戲啟動完成後,後續按需載入的Object,都會對應的建立InstanceID, 在解除安裝該資源的時候,會對應使這個對映關係失效,後續重新載入該object的時候,是會被重新建立對應的InstanceID, 先前建立的InstanceID是不會被重新定位到新載入的Object的。

3.3 AssetBundle中檔案的載入

現在在Unity中主要的資源管理是基於AssetBundle的管理,那麼執行時,是如何從bundle中載入出想要的Object的?

3.3.1 Bundle檔案的組成

在Unity中,bundle會被分為兩種大類,場景Bundle和非場景Bundle,利用unity自帶的WebExtract 和 Binary2Text兩個工具,是可以解壓bundle為文字檔案的。
場景Bundle在使用WebExtract解壓後,會得到兩類檔案:

  • BuildPlayer-sceneName: 場景序列化檔案,也就是hierarchy序列化的結果
  • BuildPlayer-sceneName.sharedasset: 場景依賴的檔案

普通bundle在使用WebExtract解壓後,會得到兩類檔案:

  • CAB-GuidString: 該二進位制檔案為該bundle的序列化檔案,以及可能包含的具體Object檔案
  • CAB-GuidString.resS:如果包含這個檔案,則上面的檔案目前是不能轉換成txt檔案的

以轉換成功的bundle的序列化檔案為參照,可以分析主要包含以下幾個部分:   

1) External References:

   

可以理解為依賴的外部assetbundle,這兒並不是依賴的bundle的名字,而是類似的cab-guidstring的形式,可見unity內部對於bundle的相互依賴處理,是基於這樣一套的命名來進行管理的。    

2) object map:

當前bundle包含的object的資訊map:

3) bundle標頭檔案: AssetBundle

4) bundle包含的object的詳細序列化資訊       

3.3.2 Bundle檔案的載入過程

分析完bundle的組成後,接下來分析從bundle中載入Object的過程,這兒以LZ4的壓縮為標準,基於AssetBundle.LoadFromFile(Async)做為介面。 在載入Object的時候,會首先觸發載入該object所在的bundle,如果該bundle有依賴bundle,那麼需要先載入該bundle的依賴bundle,Unity並不會自動載入依賴bundle.

unity在載入bundle的時候,會先載入該bundle的序列化檔案,也就是前面說到的External References/Object map/bundle標頭檔案,然後基於得到序列化資訊,進一步從bundle的object序列化資訊中載入對應的object。

如果該object有多個依賴的資源,unity會在內部自動從該bundle或者依賴bundle中將依賴資源載入出來,然後執行資源的裝載,最終返回一份例項到記憶體中,完成InstanceID 和 gameobject的對映。

3.3.3 Bundle檔案中對script的載入

如果bundle中的object上有對應的script,那麼在構建Bundle的時候,會為這個script構建一份特殊的資源:MonoScript,對應的存入到bundle中,monoscript這種資源,並沒有包含實際的執行時的程式碼,而是儲存這個指令碼的assembly name, namespace name and class name,在裝配該Object的時候,由於dll已經提前裝載,所以會自動的索引到裝載的assembly/namespace/class name指令碼,然後裝配到該object上。

3.4 資源的解除安裝

在不考慮資源計數管理的情況下,當資源的引用為空的時候,是可以執行資源的解除安裝的。對於資源的解除安裝,分為Resources資源和bundle資源:

3.4.1 Resources資源

1)可以呼叫Resources.UnloadAsset介面來解除安裝資源,使用該介面後,下次再載入該Object,會重新建立舊InstanceID和該Object的對映關係

2) 在執行場景切換的時候,如果選擇 non-additively mode,這時候會自動的觸發Resources.UnloadUnusedAsset 

3.4.2 Bundle資源

bundle的解除安裝,有AssetBundle.Unload(true)/Unload(false)兩種:

1)Unload(true): 將bundle從記憶體中解除安裝,同時將從bundle中載入的所有資源都解除安裝掉

2) Unload(false): 將bundle從記憶體中解除安裝,從bundle中已經載入的資源會被保留,如果再重新載入該bundle,對應的不會重新構建bundle和資源的對映關係,以前載入的資源就容易造成記憶體洩露。

目前推薦自己計數管理,只呼叫Unload(true)

四、Resources的使用

Unity的官方文件:

4.1. Best Practices for the Resources System

Don't use it. several reasons:

1) 使用resources使得精細化細粒度的記憶體管理更困難

2) 不恰當的使用Resources目錄會增大遊戲的啟動時間以及build時間
而且隨著Resources目錄中檔案的增加,對其中資源的管理會越來越困難

3) Resources目錄無法根據平臺定製資源內容(除非在build的時候重新拷貝指定的資源到Resource目錄)

4) Resources目錄無法提供熱更新

4.2. Proper uses of the Resources system

在某些情況下,Resources目錄還是可以使用:

1) rapidly prototype開發階段

2) generally required throughout a project's lifetime

3) Not memory-intensive

4) Not prone to patching, or does not vary across platforms or devices

5) Used for minimal bootstrapping 某些和平臺無關的配置檔案,不佔用較大記憶體,可以放入到Resources目錄中

4.3. Serialization of Resources

在build的時候,Resources目錄下的Assets/Objects會被序列化到一個序列化檔案中。在這個檔案中,包含了後設資料以及索引資訊,類似於AssetBundle。

索引資訊其實就是一個序列化的查詢樹,用來定位資源名字到資源的file guid和local ID, 同時用於查詢這個資源本身。

在大部分的平臺上,查詢樹是BST, 其構建的時間複雜度為O(nlog(n)), 構建的時間會隨著n的增大而增大(資源數越多構建時間越久)

在遊戲啟動的時候,這個BST樹構建的過程是無法跳過的,如果Resources下的資源數目超過10,000,在低端手機上其佔用的啟動時間會達到幾秒。而事實上這些資源索引並不是全部需要預先載入的,這會降低遊戲的效能。

五、總結

簡要的闡述了整個資源載入和解除安裝的流程,對於AssetBundle的使用和管理,屬於新的分類內容,在後續再細談。

相關文章