前回
https://qiita.com/kawawa1989/items/2e1f2fe89002a5bef726
からの続き。
settings.json
を読み込み ResourceManagerRuntimeData インスタンスを取得したところから。
そもそもsettings.jsonの中身はどうなっているのか?
{
"m_buildTarget": "StandaloneOSX",
"m_SettingsHash": "",
"m_CatalogLocations": [
{
"m_Keys": [
"AddressablesMainContentCatalog"
],
"m_InternalId": "{UnityEngine.AddressableAssets.Addressables.RuntimePath}/catalog.json",
"m_Provider": "UnityEngine.AddressableAssets.ResourceProviders.ContentCatalogProvider",
"m_Dependencies": [],
"m_ResourceType": {
"m_AssemblyName": "Unity.Addressables, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null",
"m_ClassName": "UnityEngine.AddressableAssets.ResourceLocators.ContentCatalogData"
},
"SerializedData": []
}
],
"m_ProfileEvents": false,
"m_LogResourceManagerExceptions": true,
"m_ExtraInitializationData": [],
"m_DisableCatalogUpdateOnStart": false,
"m_IsLocalCatalogInBundle": false,
"m_CertificateHandlerType": {
"m_AssemblyName": "",
"m_ClassName": ""
},
"m_AddressablesVersion": "1.19.19",
"m_maxConcurrentWebRequests": 500,
"m_CatalogRequestsTimeout": 0
}
今回特に必要なのは m_CatalogLocations
の中身。
settings.jsonの中には ResourceLocationDataが含まれている。
このm_CatalogLocationsを使ってResourceLocationMapインスタンスを生成している。
AddressablesImpl m_Addressables; // フィールド
// ProvideResource で ResourceManagerRuntimeData を取得
AsyncOperationHandle<ResourceManagerRuntimeData> rtdOp = m_Addressables.ResourceManager.ProvideResource<ResourceManagerRuntimeData>(runtimeDataLocation);
.
.
.
// Operationが完了したとする
ResourceManagerRuntimeData rtd = m_rtdOp.Result; // Result には ResourceManagerRuntimeData インスタンスが含まれるはず
// CatalogLocations の情報を使用して ResourceLocationMap を生成する
ResourceLocationMap locMap = new ResourceLocationMap("CatalogLocator", rtd.CatalogLocations);
// 生成した locMap を追加する
m_Addressables.AddResourceLocator(locMap);
if (locMap.Locate(ResourceManagerRuntimeData.kCatalogAddress, typeof(ContentCatalogData), out var catalogs))
{
Type provType = typeof(ProviderOperation<ContentCatalogData>);
var catalogOp = m_Addressables.ResourceManager.CreateOperation<ProviderOperation<ContentCatalogData>>(provType, provType.GetHashCode(), null, null);
IResourceProvider catalogProvider = null;
foreach (IResourceProvider provider in m_Addressables.ResourceManager.ResourceProviders)
{
if (provider is ContentCatalogProvider)
{
catalogProvider = provider;
break;
}
}
// Dependencies が存在する場合は、そちらの解決を行うGroupOperation を生成
var dependencies = addressables.ResourceManager.CreateGroupOperation<string>(loc.Dependencies, true);
// catalogOp に必要な情報を設定
catalogOp.Init(addressables.ResourceManager, catalogProvider, loc, dependencies, true);
// Operation を実行
// (Dependencies、catalogOpの両方の処理が完了したらDoneになるOperation)
var catalogHandle = m_Addressables.ResourceManager.StartOperation(catalogOp, dependencies);
dependencies.Release();
// catalogHandle が完了するまで待機し、完了したらデリゲートを実行する Operation
// ChainOperation を生成する。
// catalogHandle が完了したら OnCatalogDataLoaded が呼ばれる。
var chainOp = m_Addressables.ResourceManager.CreateChainOperation(catalogHandle, res => OnCatalogDataLoaded(addressables, res, providerSuffix, remoteHashLocation));
m_loadCatalogOp = chainOp;
}
注意: 上記コードは実際の実装をかなり割愛しています。
ResourceManagerRuntimeData の CatalogLocations に実際に読み込むべき catalog の Location 情報が含まれている。
今回の場合はInternalId: "{UnityEngine.AddressableAssets.Addressables.RuntimePath}/catalog.json"
(つまり"Library/com.unity.addressables/aa/OSX/catalog.json")
を読み込む。
理由はよくわからないが、locationの中にあるProviderを参照しておらず、ハードコードでContentCatalogProviderを使用するように実装されている点が場合によっては少し注意する必要がある。
なぜかというと最初にロードする catalog に関しては必ず ContentCatalogProvider
を使用しないといけない性質上、それ以外の ResourceProvider を使って catalog を読み込むことができない。
その為、例えば catalog を暗号化したいといった場面があるときに対応することができない。
もし、ここの ResourceProvider を外部から指定できるなら独自に暗号化した catalog を復号しながら読み込む ResourceProvider などを独自定義することも可能だが、残念ながらそういった事をしたい場合は Addressables を書き換える必要がある。(メンテナンスコストの観点から考えるとそれはそれで微妙な気がするが...)
ContentCatalogProvider がどのような処理を行なっているかは、前回のTextDataProviderと似た話になるのでここでは割愛。
先ほど生成したChainOperationの完了時コールバック OnCatalogDataLoaded メソッドを調べる。
AsyncOperationHandle<IResourceLocator> OnCatalogDataLoaded(AddressablesImpl addressables, AsyncOperationHandle<ContentCatalogData> op, string providerSuffix, IResourceLocation remoteHashLocation)
{
ContentCatalogData data = op.Result;
foreach (var providerData in data.ResourceProviderData)
{
var provider = providerData.CreateInstance<IResourceProvider>(providerData.Id);
m_Addressables.ResourceManager.ResourceProviders.Add(provider);
}
m_Addressables.InstanceProvider = data.InstanceProviderData.CreateInstance<IInstanceProvider>();
m_Addressables.SceneProvider = data.SceneProviderData.CreateInstance<ISceneProvider>();
ResourceLocationMap locMap = data.CreateCustomLocator(data.location.PrimaryKey, providerSuffix);
m_Addressables.AddResourceLocator(locMap, data.localHash, data.location);
m_Addressables.AddResourceLocator(new DynamicResourceLocator(addressables));
// 結果だけを返す CompletedOperation を生成して返す
return m_Addressables.ResourceManager.CreateCompletedOperation<IResourceLocator>(locMap, string.Empty);
}
おおまかな流れとしては以下のような感じ
- ResourceRuntimeDataの取得
- ResourceRuntimeDataの CatalogLocations を使って ResourceLocationMapを生成
- 2.で生成した ResourceLocationMap を使用して ContentCatalogData を ContentCatalogProvider を使って取得する
- ContentCatalogDataの取得に成功したらcatalogの情報を使い各種設定を行う
- 初期化完了
AsyncOperation の処理に関して
ここまででいくつかのAsyncOperationが出てきたが、非常にややこしいので情報をまとめる
クラス図
ひとつひとつ順番に調べてみる
GroupOperation
IList<AsyncOperationHandle>
を戻り値として持つ非同期処理クラス
各AsyncOperationHandle がロードを完了する度に m_LoadedCount をインクリメントさせ、全ての要素のロードが完了したらCompleteが実行される。
主に依存しているアセットバンドルをまとめてロード、同期するために使用される。
ProviderOperation
ResourceProvider の各種リソース読み込み処理を管理する非同期処理クラス
TObject
型を戻り値に持つ。
m_DepOp
はリソースに結びついた依存しているアセットバンドルの ProviderOperation インスタンスのリストで、まずm_DepOpの依存関係を先に解決し、m_DepOpに含まれる全ての ProviderOperation のロードが終わってからExecuteが実行される。
ChainOperation
非同期処理と非同期処理を連結させるときに使用されるクラス
m_DepOp
に設定されたOperationが完了するとm_WrappedOp
が実行され、m_WrappedOp
が完了するとDoneとなる。
CompletedOperation
その名の通り、ただresultを受け取ってCompleteを実行するだけのクラス。
既にロード済みのリソースを返す時などに使われる
先ほど書いた ContentCatalogData のロード処理でも OnCatalogDataLoaded で CompletedOperation を返している。
AsyncOperationの共通ルール
・ResourceManager の CreateOperation でインスタンス生成する。
・ResourceManager の StartOperation から実行される。
・Init → Start → Execute の順番で実行される。
・依存が存在する場合(Start呼び出し時に有効なdependencyが存在する場合)、依存先が先に完了しないと実行されない
InitializationOperation.CreateInitializationOperationの初期化の流れで各Operationの処理の流れを辿ってみる
まず最初にInitializationOperationインスタンスを生成する。
var initOp = new InitializationOperation(aa);
InitializationOperation も AsyncOperationBase を継承しているので、
他のAsyncOperationと同じように使うことができる。
return aa.ResourceManager.StartOperation<IResourceLocator>(initOp, groupOpHandle);
ここでStartOperation内でStartが実行される。
しかし、groupOpHandle
をdependencyとして渡しているため、
groupOpHandle
が完了されてから initOp
が Execute される。
groupOpHandle は initOp.m_rtdOp
を dependency としているため、
initOp.m_InitGroupOps
は initOp.m_rtdOp
が終了されてから Execute される。
var groupOpHandle = aa.ResourceManager.StartOperation(initOp.m_InitGroupOps, initOp.m_rtdOp);