CocosCreator の新しいリソース管理システムの分析

CocosCreator の新しいリソース管理システムの分析

1. 資源と建設

1.1 クリエイターリソースファイルの基本

エンジンがリソースを解析して読み込む方法を理解する前に、まずこれらのリソース ファイル (画像、プレハブ、アニメーションなど) のルールを理解しましょう。クリエーター プロジェクト ディレクトリの下には、リソース関連のディレクトリがいくつかあります。

  • アセット クリエイターエディタのリソースマネージャに対応する、すべてのリソースの総合ディレクトリ
  • ライブラリ ローカル リソース ライブラリ、プロジェクトをプレビューするときに使用するディレクトリ
  • ビルド後のプロジェクトのデフォルトディレクトリ

アセット ディレクトリでは、作成者は各リソース ファイルとディレクトリに対して同じ名前の .meta ファイルを生成します。メタ ファイルは、リソース バージョン、UUID、およびさまざまなカスタム情報 (エディターの屬性檢查器で設定) を記録する json ファイルです。たとえば、プレハブ メタ ファイルには、エディターで変更できる optimizePolicy や asyncLoadAssets などのプロパティが記録されます。

{
  "バージョン": "1.2.7",
  "UUID": "a8accd2e-6622-4c31-8a1e-4db5f2b568b5",
  "optimizationPolicy": "AUTO", // プレハブ作成の最適化戦略 "asyncLoadAssets": false, // 読み込みを遅延するかどうか "readonly": false,
  「サブメタ」: {}
}

ライブラリ ディレクトリの下の imports ディレクトリでは、リソース ファイル名が uuid に変換され、uuid の最初の 2 文字が取得されてディレクトリがグループ化され、保存されます。作成者は、すべてのリソースの uuid とアセット ディレクトリ間のマッピング関係、およびリソースとメタの最終更新タイムスタンプを、以下に示すように、uuid-to-mtime.json という名前のファイルに記述します。

{
  "9836134e-b892-4283-b6b2-78b5acf3ed45": {
    「資産」: 1594351233259,
    「メタ」: 1594351616611,
    "相対パス": "効果"
  },
  "430eccbf-bf2c-4e6e-8c0c-884bbb487f32": {
    「資産」: 1594351233254,
    「メタ」: 1594351616643,
    "相対パス": "effects_\__builtin-editor-gizmo-line.effect"
  },
  ...
}

アセット ディレクトリ内のリソースと比較すると、ライブラリ ディレクトリ内のリソースはメタ ファイルの情報をマージします。ファイル ディレクトリは uuid-to-mtime.json にのみ記録され、ライブラリ ディレクトリはディレクトリに対して何も生成しません。

1.2 リソースの構築

プロジェクトがビルドされると、リソースはライブラリ ディレクトリからビルド出力のビルド ディレクトリに移動されます。基本的に、ビルドに関係するシーンとリソース ディレクトリ内のリソース、およびそれらが参照するリソースのみがエクスポートされます。スクリプト リソースは複数の js スクリプトから 1 つの js にマージされ、さまざまな json ファイルも特定のルールに従ってパッケージ化されます。バンドル設定インターフェースとプロジェクトビルドインターフェースでバンドルとプロジェクトを設定できます。

1.2.1 写真、地図帳、自動地図帳

  • https://docs.cocos.com/creator/manual/zh/asset-workflow/sprite.html
  • https://docs.cocos.com/creator/manual/zh/asset-workflow/atlas.html
  • https://docs.cocos.com/creator/manual/zh/asset-workflow/auto-atlas.html

以下に示すように、エディターにインポートされた画像ごとにテクスチャ情報を記述する json ファイルが生成されます。デフォルトでは、プロジェクト内のすべての Texture2D json ファイルが 1 つに圧縮されます。無壓縮場合は、画像ごとに Texture2D json ファイルが生成されます。

{
  "__type__": "cc.Texture2D",
  「コンテンツ」: 「0,9729,9729,33071,33071,0,0,1」
}

テクスチャの Type プロパティを Sprite に設定すると、Creator は SpriteFrame タイプの json ファイルも自動的に生成します。
画像に加えて、アトラス リソースは、cc.SpriteAtlas 情報と各フラグメントの SpriteFrame 情報を含むアトラス json にも対応しています。自動アトラスには、デフォルトでは cc.SpriteAtlas 情報のみが含まれます。すべての SpriteFrame がインライン化されると、すべての SpriteFrame が結合されます。

1.2.2 プレハブとシーン

  • https://docs.cocos.com/creator/manual/zh/asset-workflow/prefab.html
  • https://docs.cocos.com/creator/manual/en/asset-workflow/scene-managing.html

シーン リソースはプレハブ リソースと非常によく似ています。どちらもすべてのノード、コンポーネント、およびその他の情報を記述する json ファイルです。內聯所有SpriteFrameチェックすると、プレハブによって参照される SpriteFrames は、プレハブが配置されている json ファイルにマージされます。SpriteFrame が複数のプレハブによって参照されている場合、各プレハブの json ファイルに SpriteFrame の情報が含まれます。內聯所有SpriteFrameチェックしない場合、SpriteFrame は別の json ファイルになります。

1.2.3 リソースファイルのマージルール

Creator が複数のリソースを 1 つの json ファイルにマージすると、config.json の packs フィールドに打包リソース情報が表示されます。リソースは複数の json ファイルに繰り返しパッケージ化される場合があります。以下は、さまざまなオプションでのクリエイターの構築ルールを示す例です。

  • a.png スプライトタイプの画像1枚
  • dir/b.png、c.png、AutoAtlas dirディレクトリには2つの画像と1つのAutoAtlasが含まれています
  • d.png、d.plist 通常のアトラス
  • e.prefabはSpriteFrame aとbのプレハブを参照します。
  • f.prefabはSpriteFrame bのプレハブを参照します。

以下は、異なるルールに従ってビルドされたファイルです。圧縮せずに生成されたファイル数が最も多いことがわかります。非インラインファイルはインラインファイルより多くなりますが、インラインでは同じファイルが繰り返しインクルードされる可能性があります。たとえば、プレハブeとfは両方とも同じ画像を参照しており、この画像のSpriteFrame.jsonが繰り返しインクルードされます。これらを1つのjsonにマージすると、生成されるファイルは1つだけになります。

リソースファイル圧縮なしデフォルト(インラインではない)デフォルト(インライン) jsonをマージ
pngファイルa.texture.json + a.spriteframe.jsonスプライトフレーム
./dir/b.png b.texture.json + b.spriteframe.json b.スプライトフレーム.json
./dir/c.png c.texture.json + c.spriteframe.json c.spriteframe.json c.spriteframe.json
./dir/オートアトラスオートアトラス.jsonオートアトラス.jsonオートアトラス.json
d.png d.texture.json + d.spriteframe.json d.spriteframe.json d.spriteframe.json
d.plist d.plist.json d.plist.json d.plist.json
e.プレハブプレファブプレファブe.prefab.json(パックa+b)
f.プレハブf.prefab.json f.prefab.json f.prefab.json(パックb)
g.allTexture.json g.allTexture.jsonすべて.json

ほとんどの場合、デフォルト オプションは適切な選択です。Web プラットフォームの場合は、ネットワーク IO を削減し、パフォーマンスを向上させるために、內聯所有SpriteFrameチェックすることをお勧めします。ネイティブ プラットフォームの場合は、パッケージ サイズとホット アップデート中にダウンロードされるコンテンツが増加する可能性があるため、チェックすることはお勧めしません。一部のコンパクト バンドル (たとえば、バンドルをロードするにはバンドル内のすべてのリソースが必要) の場合、すべての json をマージするように構成できます。

2. アセットバンドルの理解と使用

2.1 バンドルを作成する

Asset Bundle は、Creator 2.4 以降のリソース管理ソリューションです。簡単に言うと、ディレクトリを通じてリソースを計画し、プロジェクトの要件に応じてさまざまなリソースを異なるディレクトリに配置し、ディレクトリを Asset Bundle に構成します。以下の役割を果たすことができます。

  • ゲームの起動時間を短縮
  • 最初のパッケージのサイズを縮小する
  • プロジェクト間でリソースを再利用する
  • サブゲームの便利な実装
  • バンドル単位でのホットアップデート

アセット バンドルの作成は非常に簡単です。ディレクトリの屬性檢查器のボックスをチェックするだけで、配置為bundle 。公式ドキュメントにはオプションの詳細な紹介が記載されています。

このドキュメントでは、圧縮について詳しく説明されていません。ここでの圧縮は、zip などの圧縮ではなく、packAssets を使用して複数のリソース json ファイルを 1 つにマージし、io を削減することを指します。

オプションを確認するのは非常に簡単です。本当の鍵は、バンドルをどのように計画するかにあります。計画の原則は、パッケージ サイズを縮小し、起動を高速化し、リソースを再利用することです。サブゲーム、レベルのコピー、システム機能など、ゲームのモジュールに応じてリソースを計画することをお勧めします。

バンドルは、フォルダー内のリソースだけでなく、フォルダーによって参照される他のフォルダー内のリソースも自動的にパッケージ化します (これらのリソースが他のバンドルにない場合)。モジュールに従ってリソースを計画すると、複数のバンドルでリソースを共有するのが簡単になります。共通リソースをバンドルに抽出するか、バンドルの優先度を高く設定してバンドルの依存関係を構築することができます。そうしないと、これらのリソースは複数のバンドルに同時に配置されます (ローカル バンドルの場合は、パッケージ サイズが増加します)。

2.2 バンドルの使用

  • リソースの読み込みについて https://docs.cocos.com/creator/manual/zh/scripting/load-assets.html
  • リソースのリリースについて https://docs.cocos.com/creator/manual/zh/asset-manager/release-manager.html

Bundle の使い方も非常に簡単です。リソースディレクトリ内のリソースであれば、cc.resources.load を使って直接読み込むことができます。

cc.resources.load("テストアセット/プレハブ", function (err, プレハブ) {
    var newNode = cc.instantiate(プレハブ);
    cc.director.getScene().addChild(newNode);
});

その他のカスタム バンドル (バンドル名を使用してローカル バンドルまたはリモート バンドルをロードできます) の場合は、cc.assetManager.loadBundle を使用してバンドルをロードし、ロードされたバンドル オブジェクトを使用してバンドル内のリソースをロードできます。ネイティブ プラットフォームの場合、バンドルがリモート パッケージとして構成されている場合は、ビルド中にビルド リリース パネルにリソース サーバーのアドレスを入力する必要があります。

cc.assetManager.loadBundle('01_graphics', (err, バンドル) => {
    バンドルをロードします。
});

ネイティブまたはミニゲーム プラットフォームでは、次のように Bundle を使用することもできます。

  • 別のプロジェクトからリモートバンドルをロードする場合は、URLを使用してロードする必要があります(他のプロジェクトは別のcocosプロジェクトを参照します)
  • バンドルのダウンロードとキャッシュを自分で管理したい場合は、ローカルの書き込み可能なパスに配置し、これらのバンドルをロードするためのパスを渡すことができます。
// 他のプロジェクトのアセットバンドルを再利用する場合 cc.assetManager.loadBundle('https://othergame.com/remote/01_graphics', (err, bundle) => {
    バンドルをロードします。
});

// ネイティブ プラットフォーム cc.assetManager.loadBundle(jsb.fileUtils.getWritablePath() + '/pathToBundle/bundleName', (err, bundle) => {
    // ...
});

// WeChat ミニゲームプラットフォーム cc.assetManager.loadBundle(wx.env.USER_DATA_PATH + '/pathToBundle/bundleName', (err, bundle) => {
    // ...
});

その他の注意事項:

  • バンドルをロードすると、バンドルの構成とスクリプトのみがロードされます。バンドル内の他のリソースも個別にロードする必要があります。
  • 現在、ネイティブバンドルは zip パッケージをサポートしていません。リモートパッケージのダウンロード方法は、ファイルを 1 つずつダウンロードすることです。利点は、操作が簡単で更新が便利であることです。欠点は、IO が多く、トラフィックの消費量が多いことです。
  • 異なるバンドル内のスクリプトファイルに同じ名前を使用しないでください
  • バンドル A は別のバンドル B に依存しています。B がロードされていない場合、A をロードしても B は自動的にロードされません。代わりに、A が B に依存するリソースをロードするときにエラーが報告されます。

3. 新しいリソースフレームワークの分析

v2.4 リファクタリング後の新しいフレームワーク コードは、より簡潔で明確です。まず、マクロの観点からリソース フレームワーク全体を理解できます。リソース パイプラインは、フレームワーク全体の中核部分です。リソース読み込みプロセス全体を標準化し、パイプラインのカスタマイズをサポートします。

公開文書

  • helper.js は、decodeUuid、getUuidFromURL、getUrlWithUuid などの一般的な関数を多数定義します。
  • utilities.js は、getDepends、forEach、parseLoadResArgs などの一般的な関数を多数定義します。
  • deserialize.js は deserialize メソッドを定義します。このメソッドは、json オブジェクトを Asset オブジェクトに逆シリアル化し、その__depends__プロパティを設定します。
  • depend-util.jsはリソースの依存関係リストを制御します。各リソースのすべての依存関係は_dependsメンバー変数に配置されます。
  • cache.jsは、単純なキーと値のペアのコンテナをカプセル化する一般的なキャッシュクラスです。
  • shared.js は、ロードされたアセット、ダウンロードされたファイル、バンドルなど、主に Cache オブジェクトと Pipeline オブジェクトなどのいくつかのグローバル オブジェクトを定義します。

バンドル

  • config.js バンドル構成オブジェクト。バンドル構成ファイルの解析を担当します。
  • bundle.js バンドルクラスは、バンドル内のリソースのロードとアンロードのための構成と関連インターフェースをカプセル化します。
  • builtins.jsは、 cc.assetManager.builtinsを通じてアクセスできる組み込みバンドルリソースのパッケージです。

パイプライン部分

CCAssetManager.jsはパイプラインを管理し、統一されたロードおよびアンロードインターフェースを提供します。

パイプラインフレームワーク

  • pipeline.jsはパイプラインの組み合わせやフローなどの基本的な機能を実装します
  • task.jsはタスクの基本的なプロパティを定義し、シンプルなタスクプール機能を提供します。
  • request-item.js は、リソース ダウンロード アイテムの基本プロパティを定義します。タスクは複数のダウンロード アイテムを生成する場合があります。

前処理パイプライン

  • urlTransformer.js parseはリクエストパラメータをRequestItemオブジェクトに変換し(および関連するリソース構成を照会し)、combineは実際のURLを変換する役割を担います。
  • preprocess.jsはURLに変換する必要があるリソースを除外し、transformPipelineを呼び出します。

ダウンロードパイプライン

  • download-dom-audio.jsは、オーディオタグを使用してオーディオエフェクトをダウンロードする方法を提供します。
  • download-dom-image.jsは、Imageタグを使用して画像をダウンロードする方法を提供します。
  • download-file.jsはXMLHttpRequestを使用してファイルをダウンロードする方法を提供します。
  • download-script.jsは、スクリプトタグを使用してスクリプトをダウンロードする方法を提供します。
  • downloader.js は、あらゆる形式のファイルのダウンロード、同時制御、失敗時の再試行をサポートします。

解析パイプライン

  • factory.jsは、Bundle、Asset、Texture2D、その他のオブジェクトのファクトリーを作成します。
  • fetch.jsはpackManagerを呼び出してリソースをダウンロードし、依存関係を解決します。
  • parser.jsはダウンロードしたファイルを解析します

他の

  • releaseManager.jsは、依存リソースの解放とシーン切り替え時のリソースの解放を担当するリソース解放インターフェースを提供します。
  • cache-manager.d.ts は、WEB 以外のプラットフォーム上のサーバーからダウンロードされたすべてのキャッシュを管理するために使用されます。
  • pack-manager.js は、解凍、読み込み、キャッシュなど、パッケージ化されたリソースを処理します。

3.1 パイプラインの読み込み

Creator は、パイプラインを使用してリソース読み込みプロセス全体を処理します。これの利点は、リソース処理プロセスが分離され、各ステップが個別のパイプラインに分離されることです。パイプラインは簡単に再利用および結合でき、読み込みプロセス全体をカスタマイズするのに便利です。リソースの暗号化など、独自のパイプラインをいくつか作成してパイプラインに追加できます。

AssetManager には、通常の読み込みパイプライン、プリロード パイプライン、リソース パス変換パイプラインの 3 つの組み込みパイプラインがあります。最後のパイプラインは、最初の 2 つのパイプラインにサービスを提供します。

// 通常の読み込み this.pipeline = pipeline.append(preprocess).append(load);
// プリロード this.fetchPipeline = fetchPipeline.append(preprocess).append(fetch);
// リソース パスを変換します this.transformPipeline = transformPipeline.append(parse).append(combine);

3.1.1 ロードパイプラインを開始する [ロードインターフェース]

次に、最も単純な cc.resource.load など、一般的なリソースがどのようにロードされるかを見てみましょう。bundle.load メソッドでは、cc.assetManager.loadAny が呼び出されます。loadAny メソッドでは、新しいタスクが作成され、通常のロード パイプラインの async メソッドが呼び出されてタスクが実行されます。

ロードするリソースパスはtask.inputに配置され、optionsはtype、bundle、__requestType__などのフィールドを含むオブジェクトであることに注意してください。

// バンドルクラスのロードメソッド load (paths, type, onProgress, onComplete) {
  var { type, onProgress, onComplete } = parseLoadResArgs(type, onProgress, onComplete);
  cc.assetManager.loadAny(paths, { __requestType__: RequestType.PATH, type: type, bundle: this.name }, onProgress, onComplete);
},

// assetManager の loadAny メソッド loadAny (requests, options, onProgress, onComplete) {
  var { options, onProgress, onComplete } = parseParameters(options, onProgress, onComplete);
  
  options.preset = options.preset || 'デフォルト';
  task = new Task({input: request, onProgress, onComplete: asyncify(onComplete), options});
  パイプラインの非同期処理。
},

パイプラインは、前処理とロードの 2 つの部分で構成されます。 preprocess は、preprocess、transformPipeline { parse、combine } のパイプラインで構成されます。preprocess は実際にはサブタスクのみを作成し、それが transformPipeline によって実行されます。通常のリソースをロードする場合、サブタスクの入力とオプションは親タスクと同じです。

subTask を Task.create({input: task.input, options: subOptions}) とします。
task.output = task.source = transformPipeline.sync(subTask);

3.1.2 transformPipelineパイプライン[準備フェーズ]

transformPipeline は、parse と combine の 2 つのパイプラインで構成されています。parse の役割は、ロードされる各リソースの RequestItem オブジェクトを生成し、そのリソース情報 (AssetInfo、uuid、config など) を初期化することです。

まず、入力をトラバーサル用の配列に変換します。リソースをバッチで読み込む場合、各アドインはRequestItemを生成します。

入力項目がオブジェクトの場合、最初にオプションを項目にコピーします(実際には、すべての項目はオブジェクトになります。文字列の場合は、最初のステップでオブジェクトに変換されます)。

  • UUID タイプのアイテムの場合は、まずバンドルをチェックし、バンドルから AssetInfo を抽出します。リダイレクト タイプのリソースの場合は、依存バンドルから AssetInfo を取得します。バンドルが見つからない場合は、エラーが報告されます。
  • PATH タイプ、SCENE タイプ、UUID タイプの処理は基本的に同様であり、いずれもリソースの詳細情報を取得するためのものです。
  • DIR タイプは、バンドルから指定されたパスの情報を抽出し、入力の末尾にバッチで追加します (追加のアドオンを生成します)。
  • URLタイプはリモートリソースタイプであり、特別な処理は必要ありません。
関数解析(タスク){
    //入力を配列に変換します var input = task.input, options = task.options;
    input = Array.isArray(input) ? input : [ input ];

    タスク出力 = [];
    (var i = 0; i < input.length; i++) の場合 {
        var 項目 = 入力[i];
        var out = RequestItem.create();
        if (typeof item === 'string') {
            // 最初にオブジェクトを作成する
            アイテム = Object.create(null);
            項目[options.__requestType__ || RequestType.UUID] = 入力[i];
        }
        if (typeof item === 'object') {
            // ローカルオプションはグローバルオプションと重複します
            // オプションのプロパティをアイテムにコピーします。アドオンは、オプションにはあるがアイテムにはないプロパティをコピーします。cc.js.addon(item, options);
            if (item.preset) {
                cc.js.addon(item, cc.assetManager.presets[item.preset]);
            }
            for (var key in item) {
                スイッチ(キー){
                    // uuid 型リソースの場合、バンドルからリソースの詳細情報を取得します。ケース RequestType.UUID: 
                        var uuid = out.uuid = decodeUuid(item.uuid);
                        バンドルがある場合(item.bundle) {
                            var config = bundles.get(item.bundle)._config;
                            var info = config.getAssetInfo(uuid);
                            if (情報 && info.redirect) {
                                if (!bundles.has(info.redirect)) throw new Error(`まずバンドル ${info.redirect} をロードしてください`);
                                config = bundles.get(info.redirect)._config;
                                アセットIDを取得します。
                            }
                            config は、次の例のように構成されます。
                            out.info = 情報;
                        }
                        out.ext = item.ext || '.json';
                        壊す;
                    ケース '__requestType__':
                    ケース 'ext': 
                    ケース 'バンドル':
                    ケース「プリセット」:
                    case 'type': break;
                    ケース RequestType.DIR: 
                        // 解凍後、入力リストの末尾に動的に追加します。後続のループではこれらのリソースが自動的に解析されます if (bundles.has(item.bundle)) {
                            var 情報 = [];
                            バンドルを取得します。
                            (i = 0, l = infos.length; i < l; i++) の場合 {
                                var info = infos[i];
                                input.push({uuid: info.uuid, __isNative__: false, ext: '.json', bundle: item.bundle});
                            }
                        }
                        out.recycle();
                        出力 = null;
                        壊す;
                    ケース RequestType.PATH: 
                        // PATH 型のリソースは、パスと型に基づいてリソースの詳細情報を取得します if (bundles.has(item.bundle)) {
                            var config = bundles.get(item.bundle)._config;
                            var info = config.getInfoWithPath(item.path, item.type);
                            
                            if (情報 && info.redirect) {
                                if (!bundles.has(info.redirect)) throw new Error(`まずバンドル ${info.redirect} をロードする必要があります`);
                                config = bundles.get(info.redirect)._config;
                                アセット情報を取得します。
                            }

                            if (!info) {
                                out.recycle();
                                新しいエラーをスローします(`バンドル ${item.bundle} に ${item.path} が含まれていません`);
                            }
                            config は、次の例のように構成されます。 
                            out.uuid = info.uuid;
                            out.info = 情報;
                        }
                        out.ext = item.ext || '.json';
                        壊す;
                    ケース RequestType.SCENE:
                        // シーンタイプ。バンドル内の設定から getSceneInfo を呼び出して、シーンの詳細情報を取得します。if (bundles.has(item.bundle)) {
                            var config = bundles.get(item.bundle)._config;
                            var info = config.getSceneInfo(item.scene);
                            
                            if (情報 && info.redirect) {
                                if (!bundles.has(info.redirect)) throw new Error(`まずバンドル ${info.redirect} をロードする必要があります`);
                                config = bundles.get(info.redirect)._config;
                                アセット情報を取得します。
                            }
                            if (!info) {
                                out.recycle();
                                新しいエラーをスローします(`バンドル ${config.name} にはシーン ${item.scene} が含まれていません`);
                            }
                            config は、次の例のように構成されます。 
                            out.uuid = info.uuid;
                            out.info = 情報;
                        }
                        壊す;
                    ケース '__isNative__': 
                        ネイティブ関数は、item.__isNative__ を返します。
                        壊す;
                    ケースリクエストタイプ.URL: 
                        アイテムのurlをコピーします。
                        out.uuid = item.uuid || item.url;
                        out.ext = item.ext || cc.path.extname(item.url);
                        out.isNative = item.__isNative__ !== 未定義ですか? item.__isNative__ : true;
                        壊す;
                    デフォルト: out.options[key] = item[key];
                }
                if (!out) ブレーク;
            }
        }
        if (!out) 継続します;
        タスクの出力をプッシュします。
        if (!out.uuid && !out.url) throw new Error('不明な入力:' + item.toString());
    }
    null を返します。
}

RequestItem の初期情報はバンドルオブジェクトから照会され、バンドル情報はバンドルに付属する config.json ファイルから初期化されます。バンドルがパッケージ化されると、バンドル内のリソース情報が config.json に書き込まれます。

parse メソッドで処理された後、一連の RequestItem が取得されます。多くの RequestItem には AssetInfo や uuid などの情報が付属しています。combine メソッドは各 RequestItem の実際の読み込みパスを構築し、この読み込みパスは最終的に item.url に変換されます。

関数結合(タスク){
    var 入力 = タスク.出力 = タスク.入力;
    (var i = 0; i < input.length; i++) の場合 {
        var 項目 = 入力[i];
        // アイテムにすでに URL が含まれている場合は、これをスキップしてアイテムの URL を直接使用します
        if (item.url) 継続;

        var url = ''、base = '';
        var config = item.config;
        // ディレクトリプレフィックスを決定する if (item.isNative) {
            base = (config && config.nativeBase) ? (config.base + config.nativeBase) : cc.assetManager.generalNativeBase;
        } 
        それ以外 {
            base = (config && config.importBase) ? (config.base + config.importBase) : cc.assetManager.generalImportBase;
        }

        uuid = item.uuid とします。
            
        var ver = '';
        if (item.info) {
            ネイティブの場合
                ver = item.info.nativeVer ? ('.' + item.info.nativeVer): '';
            }
            それ以外 {
                ver = item.info.ver ? ('.' + item.info.ver): '';
            }
        }

        // 最終URLを連結する
        // 醜いハック。WeChatは「myfont.dw213.ttf」のようなフォントの読み込みをサポートしていません。そのため、ディレクトリにハッシュを追加します。
        if (item.ext === '.ttf') {
            url = `${base}/${uuid.slice(0, 2)}/${uuid}${ver}/${item.options.__nativeName__}`;
        }
        それ以外 {
            url = `${base}/${uuid.slice(0, 2)}/${uuid}${ver}${item.ext}`;
        }
        
        アイテムのURLをコピーします。
    }
    null を返します。
}

3.1.3 ロードパイプライン[ロードプロセス]

ロード メソッドは非常にシンプルです。基本的には、新しいタスクを作成し、loadOneAssetPipeline 内の各サブタスクを実行するだけです。

関数ロード(タスク、完了){
    タスクが進行している場合
        task.progress = {終了: 0、合計: task.input.length};
    }
    
    var オプション = task.options、進行状況 = task.progress;
    options.__exclude__ = options.__exclude__ || Object.create(null);
    タスク出力 = [];
    forEach(task.input, 関数(item, cb) {
        // 各入力項目のサブタスクを作成し、実行のために loadOneAssetPipeline に割り当てます。let subTask = Task.create({ 
            入力: 項目、 
            進行状況: タスク.onProgress、 
            オプション、 
            進捗、 
            onComplete: 関数 (err, 項目) {
                err の場合、task.isFinish と cc.assetManager.force が実行され、err が返されます。
                タスク出力をプッシュします(アイテム);
                サブタスクをリサイクルします。
                関数 :
            }
        });
        // サブタスクを実行します。loadOneAssetPipeline はフェッチと解析で構成されます。loadOneAssetPipeline.async(subTask);
    }、 関数 () {
        // 各入力が実行された後、関数は最後に実行されます options.__exclude__ = null;
        タスクが完了したら
            クリア(タスク、true);
            task.dispatch('error') を返します。
        }
        タスクのアセットを収集します。
        クリア(タスク、true);
        終わり();
    });
}

関数名が示すように、loadOneAssetPipeline はアセットをロードするためのパイプラインです。これは、フェッチと解析の 2 つのステップに分かれています。

fetchメソッドはリソースファイルをダウンロードするために使用されます。PackManagerはダウンロードの実装を担当します。fetchはダウンロードしたファイルデータをitem.fileに格納します。

parse メソッドは、読み込まれたリソース ファイルを使用可能なリソース オブジェクトに変換するために使用されます。

ネイティブ リソースの場合、解析には parser.parse を呼び出します。このメソッドは、リソース タイプに応じて異なる解析メソッドを呼び出します。

  • リソースをインポートするには、parseImportメソッドを呼び出し、JSONデータに従ってAssetオブジェクトを逆シリアル化し、それをassetsに格納します。
  • 画像リソースは、parseImage、parsePVRTex、または parsePKMTex メソッドを呼び出して画像形式を解析します (ただし、Texture オブジェクトは作成しません)。
  • サウンドエフェクトリソースは解析のためにparseAudioメソッドを呼び出します
  • plistリソースはparsePlistメソッドを呼び出して解析します。

その他のリソース

uuid がtask.options.__exclude__内にある場合、完了としてマークされ、参照カウントが追加されます。それ以外の場合は、リソース依存関係をロードするかどうかを決定するために、いくつかの複雑な条件が使用されます。

var loadOneAssetPipeline = 新しいパイプライン('loadOneAsset', [
    関数 fetch (task, done) {
        var 項目 = タスク.出力 = タスク.入力;
        var { オプション、isNative、uuid、ファイル } = 項目;
        var { reload } = オプション;
        // リソースがアセットにロードされている場合は、直接完了します。 if (file || (!reload && !isNative && asset.has(uuid))) return done();
        // ファイルをダウンロードします。これは非同期プロセスです。ファイルがダウンロードされると、item.file に配置され、done ドライバー パイプラインが実行されます。packManager.load(item, task.options, function (err, data) {
            もし(エラー){
                (cc.assetManager.force)の場合{
                    エラー = null;
                } それ以外 {
                    cc.error(err.message, err.stack);
                }
                データ = null;
            }
            アイテム.ファイル = データ;
            完了(エラー);
        });
    },
    // リソースファイルをリソースオブジェクトに変換するプロセス function parse (task, done) {
        var item = task.output = task.input、progress = task.progress、exclude = task.options.__exclude__;
        var { id, ファイル, オプション } = 項目;

        ネイティブの場合
            // ネイティブリソースの場合は、parser.parse を呼び出して処理し、処理したリソースを item.content に格納してプロセスを終了します。parser.parse(id, file, item.ext, options, function (err, asset) {
                もし(エラー){
                    場合 (!cc.assetManager.force) {
                        cc.error(err.message, err.stack);
                        完了(err)を返します。
                    }
                }
                アイテムのコンテンツ = アセット;
                task.dispatch('progress', ++progress.finish, progress.total, item);
                ファイルを削除します。
                解析されたIDを削除します。
                終わり();
            });
        } それ以外 {
            var { uuid } = アイテム;
            // 非ネイティブリソース、task.options.__exclude__ にある場合は直接終了します if (uuid in exclude) {
                var { 終了、コンテンツ、エラー、コールバック } = exclude[uuid];
                task.dispatch('progress', ++progress.finish, progress.total, item);
    
                if (finish || checkCircleReference(uuid, uuid, exclude) ) {
                    コンテンツ && content.addRef();
                    アイテムのコンテンツ = コンテンツ;
                    完了(エラー);
                } それ以外 {
                    callbacks.push({ done, item });
                }
            } それ以外 {
                // リロードされておらず、アセットにUUIDが含まれている場合
                if (!options.reload &&assets.has(uuid)) {
                    var アセット = アセット.get(uuid);
                    // options.__asyncLoadAssets__ が有効になっているか asset.__asyncLoadAssets__ が false の場合、依存関係をロードせずにプロセスが終了します if (options.__asyncLoadAssets__ || !asset.__asyncLoadAssets__) {
                        アイテムのコンテンツ = asset.addRef();
                        task.dispatch('progress', ++progress.finish, progress.total, item);
                        終わり();
                    }
                    それ以外 {
                        loadDepends(タスク、アセット、完了、false);
                    }
                } それ以外 {
                    // 再読み込みの場合、またはアセットにない場合は、解析して依存関係を読み込みます。parse(id, file, 'import', options, function (err, asset) {
                        もし(エラー){
                            (cc.assetManager.force)の場合{
                                エラー = null;
                            }
                            それ以外 {
                                cc.error(err.message, err.stack);
                            }
                            完了(err)を返します。
                        }
                        
                        アセット._uuid = uuid;
                        loadDepends(タスク、アセット、完了、true);
                    });
                }
            }
        }
    }
]);

3.2 ファイルのダウンロード

Creator は、ダウンロード作業を完了するためにpackManager.loadを使用します。ファイルをダウンロードするときには、次の 2 つの点を考慮する必要があります。

  • ファイルがパッケージ化されているかどうかにかかわらず、たとえば、すべてのSpriteFramesはインライン化されているため、SpriteFrame jsonファイルはプレハブにマージされます。
  • 現在のプラットフォームは、ネイティブ プラットフォームまたは Web プラットフォームです。一部のローカル リソースについては、ネイティブ プラットフォームがディスクから読み取る必要があります。
// packManager.load の実装 load (item, options, onComplete) {
  // リソースがパッケージ化されていない場合は、downloader.download を直接呼び出してダウンロードします (download には、ダウンロード済みか読み込み中かの判断も含まれます)
  if (item.isNative || !item.info || !item.info.packs) return downloader.download(item.id, item.url, item.ext, item.options, onComplete);
  // ファイルがダウンロードされた場合は直接戻ります if (files.has(item.id)) return onComplete(null, files.get(item.id));

  var パック = item.info.packs;
  // パックがすでにロード中の場合は、コールバックを _loading キューに追加し、ロードが完了した後にコールバックをトリガーします。var pack = packs.find(isLoading);
  if (pack) return _loading.get(pack.uuid).push({ onComplete, id: item.id });

  // 新しいパックをダウンロードする
  パック = パック[0];
  _loading.add(pack.uuid, [{ onComplete, id: item.id }]);
  url を cc.assetManager._transform(pack.uuid, {ext: pack.ext, bundle: item.config.name});
  // パックをダウンロードして解凍し、
  downloader.download(pack.uuid, url, pack.ext, item.options, function (err, data) {
      ファイルを削除します。(pack.uuid);
      もし(エラー){
          cc.error(err.message, err.stack);
      }
      // パッケージを解凍します。内部実装には 2 種類の解凍が含まれています。1 つはプレハブやアトラスなどの json 配列の分割と解凍用、もう 1 つは Texture2D のコンテンツ用です。packManager.unpack(pack.packs, data, pack.ext, item.options, function (err, result) {
          もしエラーが起きたら
              (結果内の変数ID) {
                  ファイルを追加します(id, result[id]);
              }
          }
          var コールバック = _loading.remove(pack.uuid);
          (var i = 0, l = callbacks.length; i < l; i++) の場合 {
              var cb = コールバック[i];
              もし(エラー){
                  cb.onComplete(エラー);
                  続く;
              }

              var データ = 結果[cb.id];
              if (!データ) {
                  cb.onComplete(新しいエラー('パッケージからデータを取得できません'));
              }
              それ以外 {
                  cb.onComplete(null、データ);
              }
          }
      });
  });
}

3.2.1 Webプラットフォームからのダウンロード

Web プラットフォームのダウンロード実装は次のとおりです。

  • ダウンローダー配列を使用して、さまざまなリソースタイプに対応するダウンロード方法を管理します。
  • 重複ダウンロードを避けるためにファイルキャッシュを使用する
  • _downloadingキューを使用して、同じリソースを同時にダウンロードするときにコールバックを処理し、タイミングを確保します。
  • ダウンロードの優先順位、再試行、その他のロジックをサポート
ダウンロード (id、url、タイプ、オプション、onComplete) {
  // ダウンローダー内の対応するダウンロード コールバックのタイプを取得します。let func = downloaders[type] || downloaders['default'];
  自分自身 = this とします。
  // 繰り返しダウンロードを回避する let file, downloadCallbacks;
  if (ファイル = files.get(id)) {
      onComplete(null、ファイル);
  }
  // ダウンロード中の場合はキューに追加します。それ以外の場合は、(downloadCallbacks = _downloading.get(id)) {
      ダウンロードCallbacks.push(onComplete);
      (i = 0, l = _queue.length; i < l; i++) の場合 {
          var item = _queue[i];
          if (item.id === id) {
              var 優先度 = options.priority || 0;
              if (item.priority < 優先度) {
                  項目の優先度 = 優先度;
                  _queueDirty = true;
              } 
              戻る;
          }
      } 
  }
  それ以外 {
      // ダウンロードして失敗したダウンロードの再試行を設定します var maxRetryCount = options.maxRetryCount || this.maxRetryCount;
      var maxConcurrency = options.maxConcurrency || this.maxConcurrency;
      var maxRequestsPerFrame = options.maxRequestsPerFrame || this.maxRequestsPerFrame;

      関数プロセス (インデックス、コールバック) {
          (インデックス === 0)の場合{
              _downloading.add(id, [onComplete]);
          }
          if(!self.limited)return func(urlappendtimestamp(url)、options、callback);
          updateTime();

          関数invoke(){
              func(urlappendtimestamp(url)、option、function(){
                  //ダウンロードが終了したら、_totalnumを更新します
                  _totalnum--;
                  if(!_CheckNextPerioD && _Queue.Length> 0){
                      callinnexttick(handlequeue、maxconcurrency、maxrequestsperframe);
                      _CHECKNEXTPERIOD = true;
                  }
                  callback.apply(これ、引数);
              });
          }

          if(_totalnum <maxconcurrency && _totalnumthisperiod <maxrequestsperframe){
              invoke();
              _totalnum ++;
              _totalnumthisperiod ++;
          }
          それ以外 {
              //リクエストの数が制限に達したら、残りをキャッシュします
              _queue.push({id、priority:options.priority || 0、invoke});
              _QueueDirty = true;

              if(!_CheckNextPerioD && _Totalnum <maxConcurrency){
                  callinnexttick(handlequeue、maxconcurrency、maxrequestsperframe);
                  _CHECKNEXTPERIOD = true;
              }
          }
      }

      // retryが完了したら、ファイルキャッシュにファイルを追加し、_ダウンロードキューから削除し、// retryが完了したら、コールバックを呼び出します
      function Finale(err、result){
          if(!err)files.add(id、result);
          var callbacks = _downloading.remove(id);
          for(i = 0、l = callbacks.length; i <l; i ++){
              callbacks [i](err、result);
          }
      }

      Retry(Process、MaxretryCount、this.retryinterval、Finale);
  }
}

ダウンローダーは、さまざまなリソースタイプに対応するダウンロードメソッドをマップするマップです。主に次のタイプのダウンロード方法が含まれています。

画像クラスのダウンロードイメージ

  • ダウンロードDomimage HTMLの画像要素を使用し、そのsrc属性を指定してダウンロードします
  • ダウンロードBlobファイルとして画像をダウンロードします

ファイルクラス。バイナリファイル、JSONファイル、テキストファイルに分割できます

  • ダウンロードArrayBufferスケル、ビン、PVRなどのファイルをダウンロードするためのdownloadFileを呼び出すArrayBufferタイプを指定します。
  • DownloadText Atlas、TMX、XML、VSHなどのファイルのダウンロードに使用されるDownloadFileを呼び出すテキストタイプを指定します。
  • DownloadJSONは、DownloadFileを呼び出すJSONタイプを指定し、ダウンロード後にJSONを解析します。これはPlistやJSONなどのファイルのダウンロードに使用されます

フォントクラスLoadFontはCSSスタイルを構築し、ダウンロードするURLを指定します

サウンドクラスのダウンロードAudio

  • DownloadDomaudio HTMLオーディオ要素を作成し、ダウンロードするSRC属性を指定します
  • downloadblobサウンドエフェクトをファイルとしてダウンロードします

ビデオクラスのダウンロードVideoWebクライアントが直接返されます

ScriptDownloadScriptはHTMLスクリプト要素を作成し、SRC属性を指定してダウンロードして実行します

バンドルダウンロードバンドルはバンドルのJSONとスクリプトを同時にダウンロードします

ダウンロードFileは、XMLHTTPREQUESTを使用してファイルをダウンロードします。

function downloadfile(url、options、onprogress、oncomplete){
    var {options、onprogress、oncomplete} = parseparameters(options、onprogress、oncomplete);
    var xhr = new xmlhttprequest()、errinfo = 'ダウンロード失敗:' + url + '、status:';
    xhr.open('GET', url, true);
    
    if(options.responsetype!== undefined)xhr.responsetype = options.responsetype;
    if(options.withcredentials!== undefined)xhr.withcredentials = options.withcredentials;
    if(options.mimetype!== undefined && xhr.overridemimeType)xhr.OverRidemimeType(options.mimeType);
    if(options.timeout!== undefined)xhr.timeout = options.timeout;

    if(option.header){
        for(options.headerのvarヘッダー){
            xhr.setRequestHeader(Header、Options.Header [Header]);
        }
    }

    xhr.onload = 関数 () {
        if(xhr.status === 200 || xhr.status === 0){
            oncomplete && oncomplete(null、xhr.response);
        } それ以外 {
            oncomplete && oncomplete(new Error(errinfo + xhr.status + '(応答なし)'));
        }

    };

    if(onprogress){
        xhr.onprogress = function(e){
            if(e.lengthComputable){
                OnProgress(E.Loaded、E.Total);
            }
        };
    }

    xhr.onerror = function(){
        oncomplete && oncomplete(new error(errinfo + xhr.status + '(error)'));
    };
    xhr.ontimeout = function(){
        oncomplete && oncomplete(new Error(errinfo + xhr.status + '(Time Out)'));
    };
    xhr.onabort = function(){
        oncomplete && oncomplete(new error(errinfo + xhr.status + '(abort)'));
    };

    xhr.send(null);
    xhrを返します。
}

3.2.2ネイティブプラットフォームをダウンロードします

ネイティブプラットフォームのエンジン関連ファイルはresources/builtin/jsb-adapter/engineにあります。

Downloader.register({
    // JS
    '.js':downloadscript、
    '.jsc':downloadscript、

    //画像
    '.png':downloadasset、
    '.jpg':downloadasset、
    ...
});

ネイティブプラットフォームでは、ダウンロードをダウンロードするためにリソースをダウンロードする前に、Transformurlが呼び出されます。これは、主にリソースがネットワークリソースであるかどうかを決定します。ダウンロードされていないネットワークリソースのみをダウンロードする必要があります。ダウンロードする必要がない場合は、ファイルの解析場所でファイルを直接読み取ります。

//たとえば、ダウンロードが完了した後、funcが処理します。
// JSONリソースをダウンロードしたい場合、渡されたFUNCはドノージングです。つまり、oncompleteメソッド関数のダウンロード(url、func、options、onfileprogress、oncomplete)を直接呼び出すことができます{
    var result = transformurl(url、options);
    //ローカルファイルの場合は、FUNCを直接指します
    if(result.inlocal){
        func(result.url、options、oncomplete);
    }
    //キャッシュ中の場合、更新リソースの最後の時間が使用されます(LRU)
    else if(result.incache){
        cachemanager.updatelasttime(url)
        func(result.url、options、function(err、data){
            もし(エラー){
                cachemanager.removecache(url);
            }
            oncomplete(err、data);
        });
    }
    それ以外 {
        //無効なネットワークリソースについては、downloadfileを呼び出してvar time = date.now()をダウンロードしてください。
        var StoragePath = '';
        if(options .__ cachebundleroot__){
            StoragePath = `$ {cachemanager.cachedir}/$ {options .__ cachebundleroot __}/$ {time} $ {suffix ++} $ {cc.path.extname(url)} ';
        }
        それ以外 {
            StoragePath = `$ {cachemanager.cachedir}/$ {time} $ {suffix ++} $ {cc.path.extname(url)}`;
        }
        //ダウンロードファイルを使用してダウンロードしてキャッシュoptersion(url、storagepath、options.header、onfileprogress、function(err、path){
            もし(エラー){
                oncomplete(err、null);
                戻る;
            }
            func(パス、オプション、関数(err、data){
                もしエラーが起きたら
                    cachemanager.cachefile(url、storagepath、options .__ cachebundleroot__ __);
                }
                oncomplete(err、data);
            });
        });
    }
}

関数transformurl(url、options){
    var inlocal = false;
    var incache = false;
    //それは通常のマッチングによるURLですか?
    if(regex.test(url)){
        if(options.reload){
            {url}を返します;
        }
        それ以外 {
            //キャッシュにあるかどうかを確認します(ローカルディスクキャッシュ)
            var cache = cachemanager.cachedfiles.get(url);
            if(cache){
                incache = true;
                url = cache.url;
            }
        }
    }
    それ以外 {
        inlocal = true;
    }
    return {url、inlocal、incache};
}

ダウンロードFileは、ネイティブプラットフォームのJSB_DownLoaderに電話してリソースをダウンロードし、ローカルディスクに保存します。

downloadfile(remoteurl、filepath、header、onprogress、oncomplete){
  download.add(remoteurl、{onprogress、oncomplete});
  var StoragePath = filepath;
  if(!StoragePath)StoragePath = tempdir + '/' + performance.now() + cc.path.extname(remoteurl);
  jsb_downloader.createdownloadfiletask(remoteurl、storagepath、header);
},

3.3ファイル解析

LoadOneasSetPipelineでは、リソースは2つのパイプラインを介して処理されます。 Parse Methodでは、Parser.Parseがファイルの内容を渡し、対応するアセットオブジェクトに解析して返すように呼び出されます。

3.3.1 Webプラットフォーム分析

Webプラットフォームの下のParser.Parseは、主に解析されたファイルを管理し、複製された解析を避けるために解析および解析されたファイルのリストを維持します。同時に、パーサーの後のコールバックリストが維持され、実際の解析方法はパーサーアレイにあります。

parse(id、file、type、options、oncomplete){
  Parsedasset、解析、Parsehandlerを使用します。
  if(parsedasset = parsed.get(id)){
      oncomplete(null、parsedasset);
  }
  else if(parsing = _parsing.get(id)){
      parsing.push(oncomplete);
  }
  else if(parsehandler = parsers [type]){
      _parsing.add(id、[oncomplete]);
      parsehandler(file、options、function(err、data){
          もし(エラー){
              files.remove(id);
          } 
          else if(!isscene(data)){
              parsed.add(id、data);
          }
          let callbacks = _parsing.remove(id);
          for(i = 0、l = callbacks.length; i <l; i ++){
              callbacks [i](err、data);
          }
      });
  }
  それ以外 {
      oncomplete(null、file);
  }
}

パーサーは、さまざまなタイプのファイルの解析方法をマップします。

注:Parseimportメソッドでは、脱審設定方法は、リソースの依存関係を資産に依存させます。たとえば、プレハブリソースには2つのノードがあり、どちらも同じリソースを参照しています。

//画像形式を解析方法にマッピングするvar parsers = {
  '.png':parser.parseimage、
  '.jpg':parser.parseimage、
  '.bmp':parser.parseimage、
  '.jpeg':parser.parseimage、
  '.gif':parser.parseimage、
  '.ico':parser.parseimage、
  '.tiff':parser.parseimage、
  '.webp':parser.parseimage、
  '.image':parser.parseimage、
  '.pvr':parser.parsepvrtex、
  '.pkm':parser.parsepkmtex、
  //オーディオ
  '.mp3':parser.parseaudio、
  '.ogg':parser.parseaudio、
  '.wav':parser.parseaudio、
  '.m4a':parser.parseaudio、

  // Plist
  '.plist':parser.parseplist、
  「インポート」:parser.parseimport
};

//画像はアセットオブジェクトに解析されませんが、parseimage(file、options、oncomplete)になります{
  if(capabilities.imagebitmap && file instanceof blob){
      ImageOptions = {};
      imageoptions.imageorientation = option .__ flipy__: 'none';
      imageoptions.premultiplyalpha = option .__ premultiplyalpha__: 'none';
      createImageBitMap(file、imageoptions).then(function(result){
          result.flipy = !! options .__ flipy__;
          result.premultiplyalpha = !! options .__ premultiplyalpha__;
          oncomplete && oncomplete(null、result);
      }、function(err){
          oncomplete && oncomplete(err、null);
      });
  }
  それ以外 {
      oncomplete && oncomplete(null、file);
  }
},

//アセットオブジェクトの分析は、一般化を介して実装されます。一般的なプロセスは、対応するクラスを解析し、対応するクラスの_deserializeメソッドを呼び出して、データをコピーし、変数を初期化し、依存関係リソースを資産に配置することです。
parseimport(file、options、oncomplete){
  if(!file)return oncomplete && oncomplete(new error( 'json is empty'));
  var result、err = null;
  試す {
      result = Deserialize(file、options);
  }
  キャッチ(e){
      err = e;
  }
  oncomplete && oncomplete(err、result);
},

3.3.2ネイティブプラットフォームの分析

ネイティブプラットフォームでは、さまざまなリソースの解析方法がjsb-loader.jsに再登録されています。

parser.register({
    '.png':downloader.downloaddomimage、
    '.binary':parsearraybuffer、
    '.txt':parsetext、
    '.plist':パーセプリスト、
    '.font':loadfont、
    '.exportjson':Parsejson、
    ...
});

画像を解析する方法は、実際にはDownloader.DownLoadDomimageですか?ネイティブプラットフォームをデバッグしましたが、このメソッドはイメージオブジェクトを作成し、この方法で画像をロードすることもできますが、テクスチャオブジェクトはどのように作成されていますか? Texture2Dに対応するJSONファイルを介して、作成者は本物のネイティブテクスチャをロードした後、Texture2Dオブジェクトの_nativeassetに設定されます。

var texture2d = cc.class({
    名前: 'cc.texture2d'、
    拡張:require( '../ assets/ccasset')、
    ミキシン:[EventTarget]、

    プロパティ:
        _nativeasset:{
            得る () {
                //多分Webglのプールに戻った
                this._imageを返します。
            },
            set(data){
                if(data._data){
                    this.initwithdata(data._data、this._format、data.width、data.height);
                }
                それ以外 {
                    this.initwithelement(data);
                }
            },
            オーバーライド:本当です
        },

Parsejson、Parsetext、Parsearraybufferなどの実装については、ここでファイルシステムを呼び出してファイルを読み取るだけです。たとえば、ファイルコンテンツを取得した後に使用するためにさらに解析する必要があるリソースはありますか?たとえば、モデルや骨などのリソースはバイナリモデルデータに依存しています。そうです、上記のTexture2dのように、それらは対応する資産リソース自体に配置されます。

// jsb-loader.jsファイルの関数parsetext(url、options、oncomplete){
    readtext(url、oncomplete);
}

関数parsearraybuffer(url、options、oncomplete){
    ReadArrayBuffer(url、oncomplete);
}

function parsejson(url、options、oncomplete){
    readjson(url、oncomplete);
}

// jsb-fs-utils.js file {in readtext(filepath、oncomplete){
        fsutils.readfile(filepath、 'utf8'、oncomplete);
    },

    ReadArrayBuffer(Filepath、oncomplete){
        fsutils.readfile(filepath、 ''、oncomplete);
    },

    readjson(filepath、oncomplete){
        fsutils.readfile(filepath、 'utf8'、function(err、text){
            var out = null;
            もしエラーが起きたら
                試す {
                    out = json.parse(text);
                }
                キャッチ(e){
                    cc.warn( 'json failed:' + e.message);
                    err = new Error(e.message);
                }
            }
            oncomplete && oncomplete(err、out);
        });
    },

写真アルバムやプレハブなどのリソースはどのように初期化されていますか?作成者は、これらのリソースの対応するタイプがこのタイプにimportする解析機能を無効にしないため、Parseimportメソッドを解析に使用します。

3.4依存関係の読み込み

作成者は、リソースを2つのカテゴリに分割します。通常のリソースと、CC.SpriteFrame、CC.Texture2d、CC.Prefabなどのサブクラスには含まれます。ネイティブリソースには、さまざまな形式のテクスチャ、音楽、フォント、その他のファイルが含まれます。これらのネイティブリソースを直接使用することはできませんが、使用する前に作成者が対応するCC.Assetオブジェクトに変換する必要があります。

作成者では、プレハブは多くのリソースに依存する場合があり、これらの依存関係は、通常の_parseDepsFromJson関係とネイティブ_parseNativeDepFromJsonの依存関係に分割することもできます。 LoadDependsは、GetDependsメソッドを介してリソース依存関係を収集します。

LoadDependsは、依存関係リソースのロードを担当するサブタスクを作成し、実際にロードを実行して、依存関係が完了した後に実行されます。

  • 資産の初期化:依存関係がロードされたら、依存関係リソースをAssetの対応する属性に割り当て、Asset.onloadを呼び出します
  • リソースに対応するファイルと解析されたキャッシュを削除し、リソースを資産にキャッシュします(シナリオの場合、キャッシュされません)
  • repeatItem.callbacksリストでコールバックを実行します(LoadDependsの開始時に構築され、DONEメソッドに記録されたデフォルトのデフォルト)
//指定された資産関数の依存関係をロードしますloaddepends(タスク、アセット、完了、init){

    var item = task.input、progress = task.progress;
    var {uuid、id、options、config} = item;
    var {__asyncloadassets__、cacheasset} = options;

    var依存= [];
    //依存関係の負荷中にリリースがリリースされないように参照カウントを増やします。
    getDepends(uuid、asset、object.create(null)、依存、false、__asyncloadassets__、config);
    task.dispatch( 'progress'、++ progress.finish、progress.total += depents.length、item);

    var RepeatItem = task.options .__除外__ [uuid] = {content:asset、finide:false、callbacks:[{done、item}]};

    let subtask = task.create({ 
        入力:依存します、 
        オプション:task.options、 
        onprogress:task.onprogress、 
        onerror:task.prototype.recycle、 
        進捗、 
        oncomplete:function(err){
            // asset.decref && asset.decref(false);
            Asset .__ AsyncloadAssets___ = __AsyncLoadAssets__;
            Repeatitem.finish = true;
            Repeatitem.err = err;

            もしエラーが起きたら
                var assets = array.isarray(subtask.output)?
                //マップを構築して、UUIDからAssetにマップを記録して、var map = object.create(null);
                for(i = 0、l = assets.length; i <l; i ++){
                    var Depenasset = assets [i];
                    depenasset &&(map [depenasset instanceof cc.asset?depenasset._uuid + '@import':uuid + '@native'] = depenasset);
                }

                // SetPropertiesを呼び出して、対応する依存関係リソースを資産のメンバー変数に設定するif(!init){
                    if(asset .__ nativedepend__ &&!asset._nativeasset){
                        var MissingAsset = setProperties(uuid、asset、map);
                        if(!MissingAsset){
                            試す {
                                asset.onload && asset.onload();
                            }
                            キャッチ(e){
                                cc.error(e.message、e.stack);
                            }
                        }
                    }
                }
                それ以外 {
                    var MissingAsset = setProperties(uuid、asset、map);
                    if(!MissingAsset){
                        試す {
                            asset.onload && asset.onload();
                        }
                        キャッチ(e){
                            cc.error(e.message、e.stack);
                        }
                    }
                    files.remove(id);
                    parsed.remove(id);
                    キャッシュ(uuid、asset、cacheasset!== undefined?cacheasset:cc.assetmanager.cacheasset); 
                }
                subtask.recycle();
            }
            
            //ロードされたこのrepeectItemには多くの場所がある可能性があり、すべてのコールバックを通知する必要がありますvarコールバック= RepeatItem.callbacksを完了する必要があります。
            for(var i = 0、l = callbacks.length; i <l; i ++){
                var cb = callbacks [i];
                asset.addref && asset.addref();
                cb.item.content = asset;
                cb.done(err);
            }
            callbacks.length = 0;
        }
    });

    pipeline.async(subtask);
}

3.4.1依存関係分析

getDepends(uuid、data、explude、depend、preload、asyncloadassets、config){
  var err = null;
  試す {
      var info = Depentutil.parse(uuid、data);
      var includenative = true;
      if(data instance of cc.asset &&(!data .__ nativedepend__ || data._nativeasset))includenative = false; 
      if(!preload){
          asyncloadassets =!cc_editor &&(!! asyncloadassets ||(asyncloadassets &&!info.preventdeferredloaddependents));
          for(i = 0、l = info.deps.length; i <l; i ++){
              dep = info.deps [i];
              if(!(dep in exclude)){
                  除外[dep] = true;
                  Depens.push({uuid:dep、__ asyncloadassets__:asyncloadassets、bundle:config && config.name});
              }
          }

          if(includenative &&!asyncloadassets &&!info.preventpreloadnativeobject && info.nativepep){
              config &&(info.nativedep.bundle = config.name);
              Depens.push(info.nativepep);
          }
          
      } それ以外 {
          for(i = 0、l = info.deps.length; i <l; i ++){
              dep = info.deps [i];
              if(!(dep in exclude)){
                  除外[dep] = true;
                  Depens.push({uuid:dep、bundle:config && config.name});
              }
          }
          if(includenative && info.nativepep){
              config &&(info.nativedep.bundle = config.name);
              Depens.push(info.nativepep);
          }
      }
  }
  キャッチ(e){
      err = e;
  }
  エラーを返します。
},

Depentilは、依存関係リストを制御するシングルトンです。

  • DEPS依存性資産リソース
  • Nativedpep依存関係のネイティブリソース
  • PreventPreloAdnativeObjectネイティブオブジェクトのプリロードを禁止すると、この値はデフォルトで偽です
  • PreventDeferredLoadDependentsは、怠zyな負荷の依存関係を禁止します。デフォルトは偽で、Skeletonアニメーション、TileDMAP、その他のリソースに当てはまります。
  • parsedfromexistassetはasset.__depends__

Depentilは、依存関係のクエリの重複を避けるために_ -Depends Cacheを維持します。

// JSON情報に基づいてリソース依存関係リストを取得します。
  var out = null;
  //シーンまたはプレハブの場合、データは配列、シーン、またはプレハブになります
  if(array.isarray(json)){
      //それが解析され、_dependsに依存関係リストがある場合、それは(this._depends.has(uuid))this._depends.get(uuid)を返す場合に直接返されます
      out = {
          //プレハブまたはシナリオについては、_parsedepsfromjsonメソッドを使用して直接deps:cc.asset._parsedepsfromjson(json)、
          asyncloadassets:json [0] .asyncloadassets
      };
  }
  // __Type__が含まれている場合、そのコンストラクターを取得し、JSONから依存関係リソースを探します。
  //実際のテストでは、プリロードされたリソースは次のブランチに移動します。
      if(this._depends.has(uuid))this._depends.get(uuid);
      var ctor = js._getclassbyid(json .__ type__);
      //一部のリソースは、_parsedepsfromjsonと_ parsenativedepfromjsonメソッドを書き直しました//たとえば、cc.texture2d
      out = {
          PreventPreloAdnativeObject:ctor.preventpreloadnativeObject、
          PreventDeferredLoadDependents:CTOR.PREVENTDEREDLOADDEPENDENTS、
          deps:ctor._parsedepsfromjson(json)、
          nativedep:ctor._parsenativedepfromjson(json)
      };
      out.nativedep &&(out.nativedep.uuid = uuid);
  }
  //既存の資産からDEPを取得します 
  // __Type__フィールドがない場合、対応するCTORを見つけることができません。
      if(!cc_editor &&(out = this._depends.get(uuid))&& out.parsedfromexistasset)return;
      var asset = json;
      out = {
          deps:[]、
          parsedfromexistasset:true、
          PreventPreloAdnativeObject:asset.constructor.preventpreloadnativeObject、
          PreventDeferredLoadDependents:Asset.Constructor.PreventDeredLoadDependents
      };
      deps = asset .__依存関係__;
      for(var i = 0、l = deps.length; i <l; i ++){
          var dep = deps [i] .uuid;
          out.deps.push(dep);
      }
  
      if(asset .__ nativeDepend__){
          // asset._nativedepはこのようなオブジェクトを返します{__isnative__:true、uuid:this._uuid、ext:this._native}
          out.nativedep = asset._nativedep;
      }
  }
  //依存関係を初めて見つけたら、_dependsリスト、キャッシュ依存関係リストに直接配置します
  this._depends.add(uuid、out);
  戻る;
}

デフォルトの_parseDepsFromJson_parseNativeDepFromJson ccassetの実装は、 _parseDepsFromJson JSONのすべての__uuid__再依存的に呼び出します。 Texture2d、ttffont、audioclipの実装は空の配列を直接返しますが、spriteframeの実装はcc.assetManager.utils.decodeUuid(json.content.texture)を返します。

_parseNativeDepFromJson資産の_nativeに値を持っている場合、 { __isNative__: true, ext: json._native}を返します。実際、ほとんどのネイティブリソースは_nativeDepです。このプロパティのGETメソッド{__isNative__: true, uuid: this._uuid, ext: this._native}このようなものを含むオブジェクトを返します。

_parsedepsfromjson(json){
      var依存= [];
      parsedependivilly(json、依存);
      返品は異なります。
},

_parsenativedepfromjson(json){
if(json._native)return {__isnative__:true、ext:json._native};
      null を返します。
}

3.5リソースリリース

このセクションでは、作成者のリソースをリリースする3つの方法とその背後にある実装に焦点を当て、最後にプロジェクトのリソースリークのトラブルシューティング方法を紹介します。

3.5.1 Creator Resource Release

クリエーターは、リソースをリリースする次の3つの方法をサポートしています。

リリース方法リリース効果
チェック:シーン - >プロパティインスペクター - >自動リソースリリースシーンの切り替え後、新しいシーンで使用されないリソースは自動的にリリースされます
参照カウントリリースRes.Decref addRefとdedrefを使用して参照カウントを維持し、decref後の参照カウントが0の場合に自動的に解放されます
CC.AssetManager.releaseasset(テクスチャ)を手動でリリースします。手動でリソースをリリースし、リリースを強制します

3.5.2シーンの自動リリース

新しいシーンが実行されている場合、director.runsceneimmediateメソッドが実行されます。

runsceneimmediate:function(seen、onbeforeloadscene、onlaunched){
  //コードを省略します...
  var oldscene = this._scene;
  if(!cc_editor){
      //リソースを自動的にリリースcc_build && cc_debug && console.time( 'autorelease');
      cc.assetmanager._releasemanager._autoreLease(oldscene、seenc、festingnodelist);
      cc_build && cc_debug && console.timeend( 'autorelease');
  }

  //シーンをアンロードします
  cc_build && cc_debug && console.time( 'Destroy');
  if(cc.isvalid(oldscene)){
      oldscene.destroy();
  }
  //コードを省略します...
},

_autorleaseの最新バージョンの実装は、古いシーンから永続的なノードの参照を移行し、リソースがリリースされるかどうかを直接呼び出します。

//自動リリースを行います
_autoreLease(oldscene、newscene、staveNodes){ 
  //永続的なノードによって依存するすべてのリソースは自動的に追加され、scenedeps.persistdepsに記録されます(i = 0、l = festiveNodes.length; i <l; i ++){
      var node = stavesnodes [i];
      var scenedeps = Dependutil._Depends.get(NewsCene._ID);
      var deps = _persistnodedeps.get(node.uuid);
      for(i = 0、l = deps.length; i <l; i ++){
          var Depenasset = assets.get(deps [i]);
          if(depenasset){
              depenasset.addref();
          }
      }
      if(scenedeps){
          !scenedeps.persistdeps &&(scenedeps.persistdeps = []);
          scenedeps.persistdeps.push.apply(scenedeps.persistdeps、deps);
      }
  }

  //古いシーンの依存関係をリリースします(oldscene){
      var children = depentil.getDeps(oldscene._id);
      for(i = 0、l = childrens.length; i <l; i ++){
          asset = assets.get(Childs [i]);
          Asset && asset.decref(cc_test || oldscene.autorleaseassets);
      }
      var依存関係= Dependutil._depends.get(oldscene._id);
      if(dependencies && dependencies.persistdeps){
          var persistdeps = dependencies.persistdeps;
          for(i = 0、l = persipdeps.length; i <l; i ++){
              asset = assets.get(persistdeps [i]);
              Asset && asset.decref(cc_test || oldscene.autorleaseassets);
          }
      }
      Depentil.Remove(oldscene._id);
  }
},

3.5.3リファレンスカウントと手動リソースリリース

リソースをリリースする2つの方法があります。それらは、リソースリリースを実現するためにリリースマンガーと呼ばれます。リソースリリースの完全なプロセスは、以下の図に大まかに示されています。

// ccasset.jsリファレンスdecref(autorelease){
  this._ref--;
  autorelease!== false && cc.assetmanager._releasemanager.tryrelease(this);
  これを返します。
}

// ccassetmanager.js手動でリソースリリースセット(asset){
  lireasemanager.tryrelease(asset、true);
},

TryReleaseは、リリースの遅延と強制的なパラメーターがTRUEである場合、 EVENT_AFTER_DRAWプロセスに直接入ります。いずれにせよ、この方法を処理するために、リソースが_ Freeメソッドに渡されます。

  • _todeleteから取り外します
  • 非フォースがリリースされたら、他の参照があるかどうかを確認する必要があります。
  • アセットキャッシュから削除します
  • 従属リソースを自動的にリリースします
  • リソースの破壊方法を呼び出してリソースを破壊します
  • Dependutilからリソースの依存レコードを削除します

Checkcircularreferenceの返品値は、リソースが他の場所で参照されていることを意味し、この方法は最初に資産を記録し、その後、リソースBとCの両方のリソースの両方で、リソースBとCの両方のリソースの両方で同等です。 Aが実際にAで解放されます。AがBとCで参照されると、カウントは0ではなく、リリースできません。リソースの参照が1を超える内部参照番号によって差し引かれた場合、それは他の場所でまだ参照されており、解放できないことを意味します。

tryrelease(asset、force){
  if(!(asset instanceof cc.asset))return;
  if(force){
      lireasemanager._free(asset、force);
  }
  それ以外 {
      _todelete.add(asset._uuid、asset);
      //次のディレクターの描画が完了したら、フリーエージェットを実行します
      if(!eventListener){
          eventListener = true;
          cc.director.once(cc.director.event_after_draw、freassets);
      }
  }
}

// free resource_free(asset、force){
  _todelete.remove(asset._uuid);

  if(!cc.isvalid(asset、true))return;

  if(!force){
      if(asset.refcount> 0){
          //リソース内の循環参照を確認します(checkcircularreference(asset)> 0)return; 
      }
  }

  // assets.remove(asset._uuid);
  var depend = Dependutil.getDeps(asset._uuid);
  for(i = 0、l = depens.length; i <l; i ++){
      var Depenasset = assets.get(依存者[i]);
      if(depenasset){
          Depenasset.decref(false);
          releasemanager._free(depenasset、false);
      }
  }
  asset.destroy();
  Depentil.Remove(asset._uuid);
},

// _todeleteでリソースをリリースし、機能をクリアしますfreeassets(){
  eventlistener = false;
  _todelete.foreach(function(asset){
      lireasemanager._free(asset);
  });
  _todelete.clear();
}

asset.destroyは何をしますか?リソースオブジェクトはどのようにリリースされますか?テクスチャやサウンドなどのリソースはどのようにリリースされますか? Assetオブジェクト自体には破壊方法がありませんが、Assetオブジェクトが実装するCCOBJECTオブジェクトは、リリースする配列にオブジェクトを配置し、それをToDestroyでマークするだけです。ディレクターは、リソースリリースのために_destroyImmediate実行するためにすべてのフレーム_destruct deferreddestroy _onPreDestroy呼び出します。

prototype.destroy = function(){
    if(this._objflags&destroy){
        cc.warnid(5000);
        false を返します。
    }
    if(this._objflags&todestroy){
        false を返します。
    }
    this._objflags | = todestroy;
    objectStodestroy.push(this);

    if(cc_editor && deferreddestroytimer === null && cc.engine &&!cc.engine._isupdating){
        // deferredDestroytimerは、編集モード= setimmediate(deferredDestroy)ですぐに破壊できます。
    }
    true を返します。
};

//ディレクターはこの方法を呼び出しますすべてのフレーム関数deferredDestroy(){{
    var deletecount = objectStodestroy.length;
    for(var i = 0; i <deletecount; ++ i){
        var obj = objectStodestroy [i];
        if(!(obj._objflags&Destroyed)){
            obj._destroyimmediate();
        }
    }
    // a.ondestroyのb.destroyを呼び出すと、ObjectStodestroyアレイのサイズが変更されます。
        objectStodestroy.length = 0;
    }
    それ以外 {
        objectStodestroy.splice(0、deletecount);
    }

    if(cc_editor){
        DeferredDestroytimer = null;
    }
}

//実際のリソースリリースprototype._destroyimmediate = function(){
    if(this._objflags&destroy){
        cc.errorid(5000);
        戻る;
    }
    //コールバックを実行するif(this._onpredestroy){
        this._onpredestroy();
    }

    if((cc_test?(/* cc_editor mockable*/ function)( 'return!cc_editor'))():!cc_editor)|| cc.engine._isplaying){
        this._destruct();
    }

    this._objflags | =破壊。
};

在這里_destruct做的事情就是將對象的屬性清空,比如將object類型的屬性置為null,將string類型的屬性置為'',compileDestruct方法會返回一個該類的析構函數,compileDestruct先收集了普通object和cc.Class這兩種類型下的所有屬性,并根據類型構建了一個propsToReset用來清空屬性,支持JIT的情況下會根據要清空的屬性生成一個類似這樣的函數返回function(o) {oa='';ob=null;o.['c']=undefined...} ,而非JIT情況下會返回一個根據propsToReset遍歷處理的函數,前者占用更多內存,但效率更高。

prototype._destruct = function () {
    var ctor = this.constructor;
    var destruct = ctor.__destruct__;
    if (!destruct) {
        destruct = compileDestruct(this, ctor);
        js.value(ctor, '__destruct__', destruct, true);
    }
    destruct(this);
};

function compileDestruct (obj, ctor) {
    var shouldSkipId = obj instanceof cc._BaseNode || obj instanceof cc.Component;
    var idToSkip = shouldSkipId ? '_id' : null;

    var key, propsToReset = {};
    for (キー in obj) {
        obj.hasOwnProperty(キー) の場合 {
            if (key === idToSkip) {
                続く;
            }
            switch (typeof obj[key]) {
                ケース '文字列':
                    propsToReset[key] = '';
                    壊す;
                case 'object':
                ケース '関数':
                    propsToReset[key] = null;
                    壊す;
            }
        }
    }
    // Overwrite propsToReset according to Class
    if (cc.Class._isCCClass(ctor)) {
        var attrs = cc.Class.Attr.getClassAttrs(ctor);
        var propList = ctor.__props__;
        for (var i = 0; i < propList.length; i++) {
            key = propList[i];
            var attrKey = key + cc.Class.Attr.DELIMETER + 'default';
            if (attrKey in attrs) {
                if (shouldSkipId && key === '_id') {
                    続く;
                }
                switch (typeof attrs[attrKey]) {
                    ケース '文字列':
                        propsToReset[key] = '';
                        壊す;
                    case 'object':
                    ケース '関数':
                        propsToReset[key] = null;
                        壊す;
                    case 'undefined':
                        propsToReset[key] = undefined;
                        壊す;
                }
            }
        }
    }

    if (CC_SUPPORT_JIT) {
        // compile code
        var func = '';
        for (key in propsToReset) {
            var statement;
            if (CCClass.IDENTIFIER_RE.test(key)) {
                statement = 'o.' + key + '=';
            }
            それ以外 {
                statement = 'o[' + CCClass.escapeForJS(key) + ']=';
            }
            var val = propsToReset[key];
            if (val === '') {
                val = '""';
            }
            func += (statement + val + ';\n');
        }
        return Function('o', func);
    }
    それ以外 {
        return function (o) {
            for (var key in propsToReset) {
                o[key] = propsToReset[key];
            }
        };
    }
}

那么_onPreDestroy又做了什么呢?主要是將各種事件、定時器進行注銷,對子節點、組件等進行刪除,詳情可以看下面這段代碼。

// Node的_onPreDestroy
_onPreDestroy () {
  // 調用_onPreDestroyBase方法,實際是調用BaseNode.prototype._onPreDestroy,這個方法下面介紹var destroyByParent = this._onPreDestroyBase();

  // 注銷Actions
  if (ActionManagerExist) {
      cc.director.getActionManager().removeAllActionsFromTarget(this);
  }

  // 移除_currentHovered
  if (_currentHovered === this) {
      _currentHovered = null;
  }

  this._bubblingListeners && this._bubblingListeners.clear();
  this._capturingListeners && this._capturingListeners.clear();

  // 移除所有觸摸和鼠標事件監聽if (this._touchListener || this._mouseListener) {
      eventManager.removeListeners(this);
      if (this._touchListener) {
          this._touchListener.owner = null;
          this._touchListener.mask = null;
          this._touchListener = null;
      }
      if (this._mouseListener) {
          this._mouseListener.owner = null;
          this._mouseListener.mask = null;
          this._mouseListener = null;
      }
  }

  if (CC_JSB && CC_NATIVERENDERER) {
      this._proxy.destroy();
      this._proxy = null;
  }

  // 回收到對象池中this._backDataIntoPool();

  if (this._reorderChildDirty) {
      cc.director.__fastOff(cc.Director.EVENT_AFTER_UPDATE, this.sortAllChildren, this);
  }

  if (!destroyByParent) {
      if (CC_EDITOR) {
          // 確保編輯模式下的,節點的被刪除后可以通過ctrl+z撤銷(重新添加到原來的父節點)
          this._parent = null;
      }
  }
},

// BaseNode的_onPreDestroy
_onPreDestroy () {
  var i, len;

  // 加上Destroying標記this._objFlags |= Destroying;
  var parent = this._parent;
  
  // 根據檢測父節點的標記判斷是不是由父節點的destroy發起的釋放var destroyByParent = parent && (parent._objFlags & Destroying);
  if (!destroyByParent && (CC_EDITOR || CC_TEST)) {
      // 從編輯器中移除this._registerIfAttached(false);
  }

  // 把所有子節點進行釋放,它們的_onPreDestroy也會被執行var children = this._children;
  for (i = 0, len = children.length; i < len; ++i) {
      children[i]._destroyImmediate();
  }

  // 把所有的組件進行釋放,它們的_onPreDestroy也會被執行for (i = 0, len = this._components.length; i < len; ++i) {
      var component = this._components[i];
      component._destroyImmediate();
  }

  // 注銷事件監聽,比如otherNode.on(type, callback, thisNode) 注冊了事件// thisNode被釋放時,需要注銷otherNode身上的監聽,避免事件回調到已銷毀的對象上var eventTargets = this.__eventTargets;
  for (i = 0, len = eventTargets.length; i < len; ++i) {
      var target = eventTargets[i];
      target && target.targetOff(this);
  }
  eventTargets.length = 0;

  // 如果自己是常駐節點,則從常駐節點列表中移除if (this._persistNode) {
      cc.game.removePersistRootNode(this);
  }

  // 如果是自己釋放的自己,而不是從父節點釋放的,要通知父節點,把這個失效的子節點移除掉if (!destroyByParent) {
      if (parent) {
          var childIndex = parent._children.indexOf(this);
          parent._children.splice(childIndex, 1);
          parent.emit && parent.emit('child-removed', this);
      }
  }

  return destroyByParent;
},

// Component的_onPreDestroy
_onPreDestroy () {
  // 移除ActionManagerExist和schedule
  if (ActionManagerExist) {
      cc.director.getActionManager().removeAllActionsFromTarget(this);
  }
  this.unscheduleAllCallbacks();

  // 移除所有的監聽var eventTargets = this.__eventTargets;
  for (var i = eventTargets.length - 1; i >= 0; --i) {
      var target = eventTargets[i];
      target && target.targetOff(this);
  }
  eventTargets.length = 0;

  // 編輯器模式下停止監控if (CC_EDITOR && !CC_TEST) {
      _Scene.AssetsWatcher.stop(this);
  }

  // destroyComp的實現為調用組件的onDestroy回調,各個組件會在回調中銷毀自身的資源// 比如RigidBody3D組件會調用body的destroy方法,而Animation組件會調用stop方法cc.director._nodeActivator.destroyComp(this);

  // 將組件從節點身上移除this.node._removeComponent(this);
},

3.5.4 資源釋放的問題

最后我們來聊一聊資源釋放的問題與定位,在加入引用計數后,最常見的問題還是沒有正確增減引用計數導致的內存泄露(循環引用、少調用了decRef或多調用了addRef),以及正在使用的資源被釋放的問題(和內存泄露相反,資源被提前釋放了)。

從目前的代碼來看,如果正確使用了引用計數,新的資源底層是可以避免內存泄露等問題的

この問題を解決するにはどうすればいいでしょうか?首先是定位出哪些資源出了問題,如果是被提前釋放,我們可以直接定位到這個資源,如果是內存泄露,當我們發現問題時程序往往已經占用了大量的內存,這種情況下可以切換到一個空場景,并清理資源,把資源清理完后,可以檢查assets中殘留的資源是否有未被釋放的資源。

要了解資源為什么會泄露,可以通過跟蹤addRef和decRef的調用得到,下面提供了一個示例方法,用于跟蹤某資源的addRef和decRef調用,然后調用資源的dump方法打印出所有調用的堆棧:

public static traceObject(obj : cc.Asset) {
  let addRefFunc = obj.addRef;
  let decRefFunc = obj.decRef;
  let traceMap = new Map();

  obj.addRef = function() : cc.Asset {
      let stack = ResUtil.getCallStack(1);
      let cnt = traceMap.has(stack) ? traceMap.get(stack) + 1 : 1;
      traceMap.set(stack, cnt);
      return addRefFunc.apply(obj, arguments);
  }

  obj.decRef = function() : cc.Asset {
      let stack = ResUtil.getCallStack(1);
      let cnt = traceMap.has(stack) ? traceMap.get(stack) + 1 : 1;
      traceMap.set(stack, cnt);
      return decRefFunc.apply(obj, arguments);
  }

  obj['dump'] = function() {
      console.log(traceMap);
  }
}

以上就是剖析CocosCreator新資源管理系統的詳細內容,更多關于CococCreator的資料,請關注123WORDPRESS.COM其他相關文章!

以下もご興味があるかもしれません:
  • Unity3Dはカメラレンズの動きを実現し、角度を制限する
  • CocosCreatorで複数のタイマーを使用する方法の詳細な説明
  • CocosCreator 学習モジュールスクリプト
  • CocosCreator で物理エンジン ジョイントを使用する方法
  • CocosCreatorでJSZip圧縮を使用する方法
  • CocosCreator 入門チュートリアル: TS で初めてのゲームを作る
  • CocosCreator ソースコードの解釈: エンジンの起動とメインループ
  • CocosCreator 一般的なフレームワーク設計リソース管理
  • CocosCreatorでリストを作成する方法
  • CocosCreator で http と WebSocket を使用する方法
  • CocosCreator でカメラトラッキングに cc.follow を使用する方法

<<:  MySQL で戻り値ありと戻り値なしのストアド プロシージャを書く 2 つの方法

>>:  Dockerが独自のローカルイメージリポジトリを構築するための手順

推薦する

Vue スキャフォールディング プロジェクトを作成するための詳細な手順

vue スキャフォールディング -> vue.cli大規模で完全に機能する Vue プロジェク...

Tableとdivの簡単な紹介と使い方

ウェブフロントエンド1学生証名前性別年01張三男20 02李思女性21総人数60フォームのコンポーネ...

Docker はキューとタスクのスケジューリングを実現するために Laravel アプリケーションをデプロイします

前回の記事では、Docker を使用して Laravel アプリケーションをデプロイする方法について...

選択ドロップダウンボックスの値をIDに渡してコードを実装する方法

完全なコードは次のとおりです。 HTMLコード:コードをコピーコードは次のとおりです。 <!-...

SQLデータベースの14の事例の紹介

データシート /* Navicat SQLite データ転送 ソースサーバー: school ソース...

Linux で固定 IP を設定する方法 (テスト済みで効果的)

まず、仮想マシンを開きます xshell5 を開いて仮想マシンに接続します (より便利です。Linu...

MySQL インデックスの正しい使い方とインデックスの原理の詳細な説明

1. はじめになぜインデックスが必要なのでしょうか?一般的なアプリケーション システムでは、読み取り...

モバイルウェブ画面適応(rem)

序文最近、フロントエンドの学習に関する以前のメモを整理したところ、モバイル Web 画面の適応 (r...

Dockerfile における ENV 命令の具体的な使用法の詳細な説明

1. Dockerfile 内の ENV 命令は、イメージの環境変数を定義するために使用されます。次...

VMware仮想マシンブリッジによるインターネット相互接続を実現する方法

VMware をインストールして新しい仮想マシンを作成したら、オプション バーの [編集] - [仮...

CentOS 7.4 に MySQL 5.7 を手動でインストールする方法

MySQL データベースは、特に JAVA プログラマーの間で広く使用されています。クラウド データ...

Nexus を使用して jar パッケージをプライベート サーバーに追加する方法

なぜ Nexus プライベート サーバーを構築する必要があるのでしょうか。その理由は非常に簡単です。...

Windows10でmysql8.0.17を置き換える詳細なチュートリアル

この記事では、Windows10でmysql8.0.17を置き換える具体的な手順を参考までに紹介しま...

フラットスタイルを使用してウェブサイトをデザインする方法

フラットなウェブサイト構造の本質はシンプルさです。コンテンツの重要なポイントを強調し、ページの装飾効...

sqlalchemy に基づいて MySQL で追加、削除、変更、クエリ操作を実装する

需要シナリオ:上司は、クロ​​ーラーを使用してMySQLデータベースにデータを書き込んだり更新したり...