在瞭解unity的資源管理方式以後,接下來細談一下Unity的資源是如何從磁盤中加載到運行時的內存中,以及又是如何被卸載的。這部分較爲繁瑣,可能會寫較多的過程。bootstrap
在unity中的腳本資源,大致能夠分爲C++編譯的引擎dll文件,c#編譯的dll文件,lua腳本文件(基於lua熱更的方式下)。c#
在工程的Library/ScriptAssemblies文件夾下,會有當前工程非引擎相關的dll文件列表:api
在遊戲啓動的時候,會執行Assembly.Load的操做,將這些dll文件加載進進程中(editor類相關的dll不會被加載)。緩存
可以加載,天然也能在運行時被卸載,因此目前一種熱更新方案ILRuntime就是對dll文件進行加載,熱更新,卸載,加載最新的dll這樣的方式進行操做。這種熱更新方式主要是針對Assembly-CSharp.dll/Assembly-CSharp-firstpass.dll進行操做。
若是不是主動進行卸載,那麼這些被加載的dll文件,在遊戲進程中,是不會被卸載釋放的,只有在遊戲進程結束的時候,纔會被系統從內存中卸載出去。app
lua因爲腳本文件的屬性,能夠被當作類文本文件進行熱更新,同時在遊戲啓動的時候纔開啓一個Lua虛擬機,在Lua虛擬機中才執行lua文件的require相關操做。
這類文件的加載,其實質就是將這部分代碼讀入到lua虛擬機的全局緩存中,而所謂的卸載,就是將這部分緩存置爲nil,和上面的dll文件的加載和卸載含義有一些差別。 工具
非腳本資源,纔是整個遊戲進程中須要處理的主要部分,會伴隨整個遊戲進程,直到遊戲進程結束。
我的對unity對資源的加載過程的理解,其本質就是一個反序列化的過程。性能
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相關文件的。ui
在遊戲啓動的時候,會將啓動場景以及Resources目錄下的資源逐個建立對應的InstanceID, 放入到緩存中,這部分InstanceID的建立耗時會隨着Resources目錄包含資源的增多而增大,因此儘可能減少這部分資源的數目。
在遊戲啓動完成後,後續按需加載的Object,都會對應的建立InstanceID, 在卸載該資源的時候,會對應使這個映射關係失效,後續從新加載該object的時候,是會被從新建立對應的InstanceID, 先前建立的InstanceID是不會被從新定位到新加載的Object的。lua
如今在Unity中主要的資源管理是基於AssetBundle的管理,那麼運行時,是如何從bundle中加載出想要的Object的?spa
在Unity中,bundle會被分爲兩種大類,場景Bundle和非場景Bundle,利用unity自帶的WebExtract 和 Binary2Text兩個工具,是能夠解壓bundle爲文本文件的。
場景Bundle在使用WebExtract解壓後,會獲得兩類文件:
普通bundle在使用WebExtract解壓後,會獲得兩類文件:
以轉換成功的bundle的序列化文件爲參照,能夠分析主要包含如下幾個部分:
能夠理解爲依賴的外部assetbundle,這兒並非依賴的bundle的名字,而是相似的cab-guidstring的形式,可見unity內部對於bundle的相互依賴處理,是基於這樣一套的命名來進行管理的。
當前bundle包含的object的信息map:
分析完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的映射。
若是bundle中的object上有對應的script,那麼在構建Bundle的時候,會爲這個script構建一份特殊的資源:MonoScript,對應的存入到bundle中,monoscript這種資源,並無包含實際的運行時的代碼,而是存儲這個腳本的assembly name, namespace name and class name,在裝配該Object的時候,因爲dll已經提早裝載,因此會自動的索引到裝載的assembly/namespace/class name腳本,而後裝配到該object上。
在不考慮資源計數管理的狀況下,當資源的引用爲空的時候,是能夠執行資源的卸載的。對於資源的卸載,分爲Resources資源和bundle資源:
1)能夠調用Resources.UnloadAsset接口來卸載資源,使用該接口後,下次再加載該Object,會從新創建舊InstanceID和該Object的映射關係
2) 在執行場景切換的時候,若是選擇 non-additively mode,這時候會自動的觸發Resources.UnloadUnusedAsset
bundle的卸載,有AssetBundle.Unload(true)/Unload(false)兩種:
1)Unload(true): 將bundle從內存中卸載,同時將從bundle中加載的全部資源都卸載掉
2) Unload(false): 將bundle從內存中卸載,從bundle中已經加載的資源會被保留,若是再從新加載該bundle,對應的不會從新構建bundle和資源的映射關係,之前加載的資源就容易形成內存泄露。
目前推薦本身計數管理,只調用Unload(true)
Unity的官方文檔:
Don't use it. several reasons:
1) 使用resources使得精細化細粒度的內存管理更困難
2) 不恰當的使用Resources目錄會增大遊戲的啓動時間以及build時間
並且隨着Resources目錄中文件的增長,對其中資源的管理會愈來愈困難
3) Resources目錄沒法根據平臺定製資源內容(除非在build的時候從新拷貝指定的資源到Resource目錄)
4) Resources目錄沒法提供熱更新
在某些狀況下,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目錄中
在build的時候,Resources目錄下的Assets/Objects會被序列化到一個序列化文件中。在這個文件中,包含了元數據以及索引信息,相似於AssetBundle。
索引信息其實就是一個序列化的查找樹,用來定位資源名字到資源的file guid和local ID, 同時用於查找這個資源自己。
在大部分的平臺上,查找樹是BST, 其構建的時間複雜度爲O(nlog(n)), 構建的時間會隨着n的增大而增大(資源數越多構建時間越久)
在遊戲啓動的時候,這個BST樹構建的過程是沒法跳過的,若是Resources下的資源數目超過10,000,在低端手機上其佔用的啓動時間會達到幾秒。而事實上這些資源索引並非所有須要預先加載的,這會下降遊戲的性能。
簡要的闡述了整個資源加載和卸載的流程,對於AssetBundle的使用和管理,屬於新的分類內容,在後續再細談。