Analyse des neuen Ressourcenmanagementsystems von CocosCreator

Analyse des neuen Ressourcenmanagementsystems von CocosCreator

1. Ressourcen und Konstruktion

1.1 Grundlagen zur Creator-Ressourcendatei

Bevor wir verstehen, wie die Engine Ressourcen analysiert und lädt, sollten wir zunächst die Regeln dieser Ressourcendateien (Bilder, Prefabs, Animationen usw.) verstehen. Unter dem Erstellerprojektverzeichnis befinden sich mehrere ressourcenbezogene Verzeichnisse:

  • Assets Das Gesamtverzeichnis aller Ressourcen, entsprechend dem Ressourcenmanager des Erstellers
  • Bibliothek lokale Ressourcenbibliothek, das Verzeichnis, das bei der Vorschau des Projekts verwendet wird
  • build Das Standardverzeichnis des Projekts nach dem Erstellen

Im Assets-Verzeichnis generiert der Ersteller für jede Ressourcendatei und jedes Ressourcenverzeichnis eine .meta-Datei mit demselben Namen. Die Metadatei ist eine JSON-Datei, die die Ressourcenversion, die UUID und verschiedene benutzerdefinierte Informationen (festgelegt im屬性檢查器des Editors) aufzeichnet. Beispielsweise zeichnet die Prefab-Metadatei Eigenschaften wie OptimizationPolicy und AsyncLoadAssets auf, die wir im Editor ändern können.

{
  "version": "1.2.7",
  "uuid": "a8accd2e-6622-4c31-8a1e-4db5f2b568b5",
  "optimizationPolicy": "AUTO", // Optimierungsstrategie für die Prefab-Erstellung "asyncLoadAssets": false, // Ob das Laden verzögert werden soll "readonly": false,
  "subMetas": {}
}

Im Importverzeichnis unter dem Bibliotheksverzeichnis wird der Ressourcendateiname in eine UUID umgewandelt und die ersten beiden Zeichen der UUID werden verwendet, um das Verzeichnis für die Speicherung zu gruppieren. Der Ersteller fügt die Zuordnungsbeziehung zwischen der UUID aller Ressourcen und dem Assets-Verzeichnis sowie den zuletzt aktualisierten Zeitstempel der Ressourcen und Metadaten in eine Datei mit dem Namen uuid-to-mtime.json ein, wie unten gezeigt.

{
  "9836134e-b892-4283-b6b2-78b5acf3ed45": {
    "Vermögenswert": 1594351233259,
    "Meta": 1594351616611,
    "relativerPfad": "Effekte"
  },
  "430eccbf-bf2c-4e6e-8c0c-884bbb487f32": {
    "Vermögenswert": 1594351233254,
    "Meta": 1594351616643,
    "relativer Pfad": "effects\__builtin-editor-gizmo-line.effect"
  },
  ...
}

Im Vergleich zu den Ressourcen im Assets-Verzeichnis führen die Ressourcen im Bibliotheksverzeichnis die Informationen der Metadatei zusammen. Das Dateiverzeichnis wird nur in uuid-to-mtime.json aufgezeichnet und das Bibliotheksverzeichnis generiert nichts für das Verzeichnis.

1.2 Ressourcenkonstruktion

Nachdem das Projekt erstellt wurde, werden die Ressourcen aus dem Bibliotheksverzeichnis in das Build-Verzeichnis der Build-Ausgabe verschoben. Grundsätzlich werden nur die am Build beteiligten Szenen und die Ressourcen im Ressourcenverzeichnis sowie die Ressourcen, auf die sie verweisen, exportiert. Skriptressourcen werden aus mehreren JS-Skripten zu einem JS zusammengeführt und verschiedene JSON-Dateien werden ebenfalls gemäß bestimmten Regeln verpackt. Wir können Bundles und Projekte in der Bundle-Konfigurationsschnittstelle und der Projekterstellungsschnittstelle einrichten

1.2.1 Bilder, Atlanten und automatische Atlanten

  • 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

Für jedes in den Editor importierte Bild wird eine JSON-Datei generiert, die die Texturinformationen beschreibt, wie unten gezeigt. Standardmäßig werden alle Texture2D-JSON-Dateien im Projekt zu einer komprimiert. Wenn無壓縮ausgewählt ist, wird für jedes Bild eine Texture2D-JSON-Datei generiert.

{
  "__type__": "cc.Texture2D",
  "Inhalt": "0,9729,9729,33071,33071,0,0,1"
}

Wenn Sie die Type-Eigenschaft der Textur auf Sprite setzen, generiert Creator auch automatisch eine JSON-Datei vom Typ „SpriteFrame“.
Neben dem Bild entspricht die Atlasressource auch einem Atlas-JSON, der die cc.SpriteAtlas-Informationen und die SpriteFrame-Informationen jedes Fragments enthält. Der automatische Atlas enthält standardmäßig nur die cc.SpriteAtlas-Informationen. Wenn alle SpriteFrames eingebettet sind, werden alle SpriteFrames zusammengeführt.

1.2.2 Prefab und Szene

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

Szenenressourcen sind Prefab-Ressourcen sehr ähnlich. Sie sind beide JSON-Dateien, die alle Knoten, Komponenten und andere Informationen beschreiben. Wenn內聯所有SpriteFrame aktiviert ist, werden die vom Prefab referenzierten SpriteFrames in die JSON-Datei integriert, in der sich das Prefab befindet. Wenn ein SpriteFrame von mehreren Prefabs referenziert wird, enthält die JSON-Datei jedes Prefabs die Informationen des SpriteFrames. Wenn Sie內聯所有SpriteFrame nicht aktivieren, wird das SpriteFrame als separate JSON-Datei gespeichert.

1.2.3 Regeln zum Zusammenführen von Ressourcendateien

Wenn Creator mehrere Ressourcen in einer JSON-Datei zusammenführt, finden wir die Informationen打包Ressource im Feld „packs“ in config.json. Eine Ressource kann wiederholt in mehrere JSON-Dateien gepackt werden. Hier ist ein Beispiel, das die Konstruktionsregeln des Erstellers unter verschiedenen Optionen zeigt:

  • a.png Ein einzelnes Bild vom Typ Sprite
  • dir/b.png, c.png, AutoAtlas Das Verzeichnis dir enthält 2 Bilder und einen AutoAtlas
  • d.png, d.plist normaler Atlas
  • e.prefab verweist auf das Prefab von SpriteFrame a und b
  • f.prefab verweist auf das Prefab von SpriteFrame b

Nachfolgend sind die nach unterschiedlichen Regeln erstellten Dateien aufgeführt. Sie können sehen, dass die Anzahl der ohne Komprimierung generierten Dateien am größten ist. Es gibt mehr Nicht-Inline-Dateien als Inline-Dateien, aber Inline-Dateien können dazu führen, dass dieselbe Datei wiederholt eingefügt wird. Beispielsweise verweisen die beiden Prefabs e und f auf dasselbe Bild, und das SpriteFrame.json dieses Bilds wird wiederholt eingefügt. Wenn sie zu einem JSON zusammengeführt werden, wird nur eine Datei generiert.

Ressourcendateien Keine Komprimierung Standard (nicht inline) Standard (inline) JSON zusammenführen
ein.png eine.texture.json + eine.spriteframe.json ein.spriteframe.json
./dir/b.png b.texture.json + b.spriteframe.json b.spriteframe.json
./dir/c.png c.texture.json + c.spriteframe.json c.spriteframe.json c.spriteframe.json
./dir/AutoAtlas autoatlas.json autoatlas.json autoatlas.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.vorgefertigt e.prefab.json e.prefab.json e.prefab.json(Paket a+b)
f. Fertighaus f.prefab.json f.prefab.json f.prefab.json(Paket b)
g.allTexture.json g.allTexture.json alle.json

Die Standardoption ist in den meisten Fällen eine gute Wahl. Wenn es sich um eine Webplattform handelt, wird empfohlen,內聯所有SpriteFrame zu aktivieren, da dies die Netzwerk-E/A reduzieren und die Leistung verbessern kann. Für native Plattformen wird dies nicht empfohlen, da dies die Paketgröße und den bei Hot Updates herunterzuladenden Inhalt erhöhen kann. Bei einigen kompakten Paketen (zum Beispiel erfordert das Laden des Pakets alle darin enthaltenen Ressourcen) können wir es so konfigurieren, dass das gesamte JSON zusammengeführt wird.

2. Asset Bundle verstehen und verwenden

2.1 Erstellen eines Bundles

Asset Bundle ist eine Ressourcenverwaltungslösung nach Creator 2.4. Einfach ausgedrückt plant es Ressourcen über Verzeichnisse, legt verschiedene Ressourcen je nach Projektanforderungen in unterschiedliche Verzeichnisse und konfiguriert die Verzeichnisse in Asset Bundles. Es kann die folgenden Rollen spielen:

  • Beschleunigen Sie die Startzeit des Spiels
  • Reduzieren Sie die Größe des ersten Pakets
  • Ressourcen projektübergreifend wiederverwenden
  • Bequeme Implementierung von Unterspielen
  • Heißes Update in Bundle-Einheiten

Das Erstellen eines Asset-Bundles ist sehr einfach. Aktivieren Sie einfach das Kontrollkästchen im屬性檢查器des Verzeichnisses, um配置為bundle . Die offizielle Dokumentation enthält eine detaillierte Einführung in die Optionen.

Das Dokument enthält keine detaillierte Beschreibung der Komprimierung. Mit Komprimierung ist hier keine Komprimierung wie Zip gemeint, sondern das Zusammenführen mehrerer JSON-Ressourcendateien zu einer einzigen über packAssets, um die io zu reduzieren.

Es ist ganz einfach, die Optionen zu prüfen. Der eigentliche Schlüssel liegt in der Planung des Bundles. Die Planungsprinzipien bestehen darin, die Paketgröße zu reduzieren, den Start zu beschleunigen und Ressourcen wiederzuverwenden. Es empfiehlt sich, die Ressourcen entsprechend den Modulen des Spiels zu planen, etwa nach Unterspielen, Levelkopien oder Systemfunktionen.

Bundle packt automatisch die Ressourcen im Ordner sowie die Ressourcen in anderen Ordnern, auf die der Ordner verweist (sofern diese Ressourcen nicht in anderen Bundles enthalten sind). Wenn wir Ressourcen nach Modulen planen, können mehrere Bundles problemlos eine Ressource gemeinsam nutzen. Sie können gemeinsame Ressourcen in ein Paket extrahieren oder einem Paket eine höhere Priorität zuweisen und Paketabhängigkeiten erstellen. Andernfalls werden diese Ressourcen gleichzeitig in mehreren Paketen platziert (wenn es sich um ein lokales Paket handelt, erhöht sich dadurch die Paketgröße).

2.2 Verwenden von Bundle

  • Informationen zum Laden von Ressourcen https://docs.cocos.com/creator/manual/zh/scripting/load-assets.html
  • Informationen zur Freigabe von Ressourcen https://docs.cocos.com/creator/manual/zh/asset-manager/release-manager.html

Die Verwendung von Bundle ist ebenfalls sehr einfach. Wenn es sich um eine Ressource im Ressourcenverzeichnis handelt, können Sie sie direkt mit cc.resources.load laden.

cc.resources.load("Test-Assets/Prefab", Funktion (err, Prefab) {
    var neuerNode = cc.instantiate(prefab);
    cc.director.getScene().addChild(neuer Knoten);
});

Wenn es sich um andere benutzerdefinierte Bundles handelt (lokale Bundles oder Remote-Bundles können unter Verwendung des Bundle-Namens geladen werden), können Sie cc.assetManager.loadBundle verwenden, um das Bundle zu laden, und dann das geladene Bundle-Objekt verwenden, um die Ressourcen im Bundle zu laden. Wenn das Bundle für native Plattformen als Remote-Paket konfiguriert ist, müssen Sie während des Erstellens die Adresse des Ressourcenservers in das Build-Release-Panel eingeben.

cc.assetManager.loadBundle('01_graphics', (err, bundle) => {
    bündeln.laden('xxx');
});

Auf nativen oder Minispielplattformen können wir Bundles auch wie folgt verwenden:

  • Wenn Sie ein Remote-Bundle aus einem anderen Projekt laden möchten, müssen Sie zum Laden die URL verwenden (anderes Projekt verweist auf ein anderes Cocos-Projekt).
  • Wenn Sie den Download und den Cache des Pakets selbst verwalten möchten, können Sie es in einem lokalen beschreibbaren Pfad ablegen und den Pfad zum Laden dieser Pakete übergeben.
// Bei der Wiederverwendung des Asset Bundles anderer Projekte cc.assetManager.loadBundle('https://othergame.com/remote/01_graphics', (err, bundle) => {
    bündeln.laden('xxx');
});

// Native Plattform cc.assetManager.loadBundle(jsb.fileUtils.getWritablePath() + '/pathToBundle/bundleName', (err, bundle) => {
    // ...
});

// WeChat Mini-Spieleplattform cc.assetManager.loadBundle(wx.env.USER_DATA_PATH + '/pathToBundle/bundleName', (err, bundle) => {
    // ...
});

Sonstige Hinweise:

  • Beim Laden eines Bundles werden nur die Konfiguration und die Skripte des Bundles geladen. Andere Ressourcen im Bundle müssen ebenfalls separat geladen werden.
  • Derzeit unterstützt das native Bundle keine Zip-Verpackung. Die Remote-Paket-Download-Methode besteht darin, Dateien einzeln herunterzuladen. Der Vorteil ist die einfache Bedienung und bequeme Aktualisierung. Der Nachteil besteht darin, dass viele IOs vorhanden sind und viel Datenverkehr verbraucht wird.
  • Verwenden Sie nicht den gleichen Namen für Skriptdateien in verschiedenen Paketen
  • Ein Bündel A ist von einem anderen Bündel B abhängig. Wenn B nicht geladen ist, wird beim Laden von A nicht automatisch B geladen. Stattdessen wird beim Laden der Ressource, von der A von B abhängt, ein Fehler gemeldet.

3. Analyse des neuen Ressourcenrahmens

Der neue Framework-Code nach der Refaktorierung von v2.4 ist prägnanter und klarer. Wir können das gesamte Ressourcen-Framework zunächst aus einer Makroperspektive verstehen. Die Ressourcen-Pipeline ist der Kern des gesamten Frameworks. Sie standardisiert den gesamten Ressourcen-Ladeprozess und unterstützt die Anpassung der Pipeline.

Öffentliche Dokumente

  • helper.js definiert eine Reihe gängiger Funktionen wie decodeUuid, getUuidFromURL, getUrlWithUuid usw.
  • utilities.js definiert eine Reihe gängiger Funktionen wie getDepends, forEach, parseLoadResArgs usw.
  • deserialize.js definiert die Deserialisierungsmethode, die das JSON-Objekt in ein Asset-Objekt deserialisiert und dessen __depends__ Eigenschaft festlegt.
  • depend-util.js steuert die Abhängigkeitsliste der Ressourcen. Alle Abhängigkeiten jeder Ressource werden in der Membervariable _depends abgelegt.
  • cache.js ist eine allgemeine Cache-Klasse, die einen einfachen Schlüssel-Wert-Paar-Container kapselt
  • shared.js definiert einige globale Objekte, hauptsächlich Cache- und Pipeline-Objekte, wie geladene Assets, heruntergeladene Dateien und Bundles usw.

Bündeln

  • config.js Bundle-Konfigurationsobjekt, verantwortlich für die Analyse der Bundle-Konfigurationsdatei
  • Die Bundle-Klasse bundle.js kapselt die Konfiguration und zugehörige Schnittstellen zum Laden und Entladen von Ressourcen im Bundle.
  • builtins.js ist ein Paket integrierter Bundle-Ressourcen, auf die über cc.assetManager.builtins zugegriffen werden kann.

Rohrleitungsteil

CCAssetManager.js verwaltet die Pipeline und bietet eine einheitliche Lade- und Entladeschnittstelle

Pipeline-Rahmenwerk

  • pipeline.js implementiert grundlegende Funktionen wie Pipeline-Kombination und Flow
  • task.js definiert die grundlegenden Eigenschaften einer Aufgabe und bietet eine einfache Aufgabenpoolfunktion
  • request-item.js definiert die grundlegenden Eigenschaften eines Ressourcen-Download-Elements. Eine Aufgabe kann mehrere Download-Elemente generieren.

Vorbehandlungspipeline

  • urlTransformer.js parse konvertiert Anforderungsparameter in RequestItem-Objekte (und fragt zugehörige Ressourcenkonfigurationen ab), und combine ist für die Konvertierung der tatsächlichen URL verantwortlich
  • preprocess.js filtert Ressourcen heraus, die in URLs konvertiert werden müssen, und ruft transformPipeline auf.

Pipeline herunterladen

  • download-dom-audio.js bietet eine Methode zum Herunterladen von Audioeffekten. Dabei wird das Audio-Tag zum Herunterladen verwendet.
  • download-dom-image.js bietet eine Methode zum Herunterladen von Bildern mithilfe des Image-Tags
  • download-file.js bietet eine Methode zum Herunterladen von Dateien mit XMLHttpRequest
  • download-script.js bietet eine Methode zum Herunterladen von Skripten. Dabei wird das Skript-Tag zum Herunterladen verwendet.
  • downloader.js unterstützt das Herunterladen aller Dateiformate, die gleichzeitige Steuerung und einen erneuten Versuch bei einem Fehler.

Analysepipeline

  • factory.js erstellt Fabriken für Bundle, Asset, Texture2D und andere Objekte
  • fetch.js ruft packManager auf, um Ressourcen herunterzuladen und Abhängigkeiten aufzulösen
  • parser.js analysiert die heruntergeladenen Dateien

andere

  • releaseManager.js bietet eine Schnittstelle zur Ressourcenfreigabe, die für die Freigabe abhängiger Ressourcen und die Freigabe von Ressourcen beim Szenenwechsel verantwortlich ist
  • cache-manager.d.ts wird verwendet, um alle vom Server auf Nicht-WEB-Plattformen heruntergeladenen Caches zu verwalten.
  • pack-manager.js verarbeitet gepackte Ressourcen, einschließlich Entpacken, Laden, Zwischenspeichern usw.

3.1 Verladeleitung

Creator verwendet Pipelines, um den gesamten Ressourcenladevorgang abzuwickeln. Der Vorteil dabei ist, dass der Ressourcenverarbeitungsprozess entkoppelt und jeder Schritt in eine separate Pipeline aufgeteilt wird. Die Pipeline kann problemlos wiederverwendet und kombiniert werden, und es ist für uns bequem, den gesamten Ladevorgang anzupassen. Wir können einige unserer eigenen Pipelines erstellen und sie der Pipeline hinzufügen, z. B. die Ressourcenverschlüsselung.

AssetManager verfügt über drei integrierte Pipelines: die normale Ladepipeline, die Vorladepipeline und die Ressourcenpfadkonvertierungspipeline. Die letzte Pipeline bedient die ersten beiden Pipelines.

// Normales Laden this.pipeline = pipeline.append(preprocess).append(load);
// Dies vorladen.fetchPipeline = fetchPipeline.append(preprocess).append(fetch);
// Ressourcenpfad konvertieren this.transformPipeline = transformPipeline.append(parse).append(combine);

3.1.1 Starten Sie die Ladepipeline [Ladeschnittstelle]

Als nächstes schauen wir uns an, wie eine allgemeine Ressource geladen wird, beispielsweise das einfachste cc.resource.load. In der Methode bundle.load wird cc.assetManager.loadAny aufgerufen. In der Methode loadAny wird eine neue Aufgabe erstellt und die asynchrone Methode der normalen Ladepipeline wird aufgerufen, um die Aufgabe auszuführen.

Beachten Sie, dass der zu ladende Ressourcenpfad in task.input platziert wird und options ein Objekt ist, das Felder wie type, bundle und __requestType__ enthält.

// Die Lademethode der Bundle-Klasse load (paths, type, onProgress, onComplete) {
  var { Typ, bei Fortschritt, bei Abschluss } = parseLoadResArgs(Typ, bei Fortschritt, bei Abschluss);
  cc.assetManager.loadAny(Pfade, { __requestType__: RequestType.PATH, Typ: Typ, Bundle: this.name }, bei Fortschritt, bei Abschluss);
},

// loadAny-Methode des AssetManagers loadAny (Anfragen, Optionen, bei Fortschritt, bei Abschluss) {
  var { Optionen, bei Fortschritt, bei Abschluss } = parseParameters(Optionen, bei Fortschritt, bei Abschluss);
  
  Optionen.Voreinstellung = Optionen.Voreinstellung || „Standard“;
  let task = neue Task({Eingabe: Anfragen, bei Fortschritt, bei Abschluss: asyncify(bei Abschluss), Optionen});
  pipeline.async(Aufgabe);
},

Die Pipeline besteht aus zwei Teilen: Vorverarbeitung und Laden. preprocess besteht aus den folgenden Pipelines: preprocess, transformPipeline { parse, combine }. Preprocess erstellt eigentlich nur eine Unteraufgabe, die dann von transformPipeline ausgeführt wird. Beim Laden einer normalen Ressource sind die Eingaben und Optionen der Unteraufgabe dieselben wie bei der übergeordneten Aufgabe.

let subTask = Task.create({Eingabe: task.input, Optionen: subOptions});
task.output = task.source = transformPipeline.sync(Untertask);

3.1.2 transformPipeline-Pipeline [Vorbereitungsphase]

transformPipeline besteht aus zwei Pipelines, parse und combine. Die Aufgabe von parse besteht darin, für jede zu ladende Ressource ein RequestItem-Objekt zu generieren und deren Ressourceninformationen (AssetInfo, UUID, Konfiguration usw.) zu initialisieren:

Konvertieren Sie zunächst die Eingabe in ein Array für die Durchquerung. Wenn Sie Ressourcen in Batches laden, generiert jedes Add-In ein RequestItem

Wenn das Eingabeelement ein Objekt ist, kopieren Sie zuerst die Optionen in das Element (tatsächlich ist jedes Element ein Objekt. Wenn es eine Zeichenfolge ist, wird sie im ersten Schritt in ein Objekt konvertiert).

  • Überprüfen Sie bei UUID-Typelementen zuerst das Bundle und extrahieren Sie die AssetInfo aus dem Bundle. Rufen Sie bei Umleitungstypressourcen die AssetInfo aus dem abhängigen Bundle ab. Wenn das Bundle nicht gefunden werden kann, wird ein Fehler gemeldet.
  • Die Verarbeitung des PATH-Typs, des SCENE-Typs und des UUID-Typs ist grundsätzlich ähnlich und dient alle dazu, detaillierte Informationen zur Ressource zu erhalten.
  • Der Typ DIR extrahiert die Informationen des angegebenen Pfads aus dem Paket und hängt sie stapelweise an das Ende der Eingabe an (wodurch zusätzliche Add-Ons generiert werden).
  • Der URL-Typ ist ein Remote-Ressourcentyp und erfordert keine spezielle Verarbeitung
Funktion analysieren (Aufgabe) {
    //Eingabe in ein Array umwandeln var input = task.input, options = task.options;
    Eingabe = Array.isArray(Eingabe)? Eingabe: [Eingabe];

    task.ausgabe = [];
    für (var i = 0; i < Eingabelänge; i ++ ) {
        var Element = Eingabe[i];
        var out = RequestItem.create();
        wenn (Typ des Elements === 'Zeichenfolge') {
            //Erstellen Sie zuerst das Objekt
            Element = Objekt.erstellen(null);
            Element[Optionen.__Anforderungstyp__ || Anforderungstyp.UUID] = Eingabe[i];
        }
        wenn (Typ des Elements === 'Objekt') {
            // Lokale Optionen überlappen globale Optionen
            //Kopiere die Eigenschaften der Optionen in das Element. Das Add-on kopiert die Eigenschaften, die in den Optionen, aber nicht im Element sind. cc.js.addon(Element, Optionen);
            wenn (Artikel.Vorgabe) {
                cc.js.addon(Element, cc.assetManager.presets[Element.preset]);
            }
            für (var Schlüssel im Element) {
                Schalter (Schlüssel) {
                    // Ressource vom Typ UUID, detaillierte Informationen zur Ressource aus dem Bundle-Fall RequestType.UUID abrufen: 
                        var uuid = out.uuid = decodeUuid(item.uuid);
                        wenn (bundles.has(item.bundle)) {
                            var config = bundles.get(item.bundle)._config;
                            var info = config.getAssetInfo(uuid);
                            wenn (info && info.weiterleitung) {
                                if (!bundles.has(info.redirect)) throw new Error(`Bitte laden Sie zuerst das Bundle ${info.redirect}`);
                                config = bundles.get(info.redirect)._config;
                                info = config.getAssetInfo(uuid);
                            }
                            out.config = Konfiguration;
                            out.info = Info;
                        }
                        out.ext = Element.ext || '.json';
                        brechen;
                    Fall '__requestType__':
                    Fall 'ext': 
                    Fall 'Bündel':
                    Fall 'Vorgabe':
                    Fall 'Typ': Unterbrechung;
                    Fall RequestType.DIR: 
                        // Nach dem Entpacken dynamisch an das Ende der Eingabeliste anfügen. Nachfolgende Schleifen werden diese Ressourcen automatisch analysieren, wenn (bundles.has(item.bundle)) {
                            var infos = [];
                            bundles.get(item.bundle)._config.getDirWithPath(item.dir, item.type, infos);
                            für (sei i = 0, l = infos.Länge; i < l; i++) {
                                var info = infos[i];
                                input.push({uuid: info.uuid, __isNative__: false, ext: '.json', bundle: item.bundle});
                            }
                        }
                        raus.recyceln();
                        aus = null;
                        brechen;
                    Fall RequestType.PATH: 
                        // Eine Ressource vom Typ PATH ruft detaillierte Informationen über die Ressource basierend auf dem Pfad und Typ ab, wenn (bundles.has(item.bundle)) {
                            var config = bundles.get(item.bundle)._config;
                            var info = config.getInfoWithPath(item.path, item.type);
                            
                            wenn (info && info.weiterleitung) {
                                if (!bundles.has(info.redirect)) throw new Error(`Sie müssen zuerst das Bundle ${info.redirect} laden`);
                                config = bundles.get(info.redirect)._config;
                                info = config.getAssetInfo(info.uuid);
                            }

                            wenn (!info) {
                                raus.recyceln();
                                neuen Fehler werfen („Bundle ${item.bundle} enthält nicht ${item.path}“);
                            }
                            out.config = Konfiguration; 
                            out.uuid = info.uuid;
                            out.info = Info;
                        }
                        out.ext = Element.ext || '.json';
                        brechen;
                    Fall RequestType.SCENE:
                        // Szenentyp, rufe getSceneInfo aus der Konfiguration im Paket auf, um detaillierte Informationen über die Szene zu erhalten, wenn (bundles.has(item.bundle)) {
                            var config = bundles.get(item.bundle)._config;
                            var info = config.getSceneInfo(item.scene);
                            
                            wenn (info && info.weiterleitung) {
                                if (!bundles.has(info.redirect)) throw new Error(`Sie müssen zuerst das Bundle ${info.redirect} laden`);
                                config = bundles.get(info.redirect)._config;
                                info = config.getAssetInfo(info.uuid);
                            }
                            wenn (!info) {
                                raus.recyceln();
                                throw new Error(`Bundle ${config.name} enthält nicht Szene ${item.scene}`);
                            }
                            out.config = Konfiguration; 
                            out.uuid = info.uuid;
                            out.info = Info;
                        }
                        brechen;
                    Fall '__isNative__': 
                        out.isNative = Element.__isNative__;
                        brechen;
                    Fall RequestType.URL: 
                        out.url = Artikel.url;
                        out.uuid = Artikel.uuid || Artikel.URL;
                        out.ext = item.ext || cc.Pfad.extname(item.url);
                        out.isNative = item.__isNative__ !== undefiniert ? item.__isNative__ : wahr;
                        brechen;
                    Standard: out.options[Schlüssel] = item[Schlüssel];
                }
                wenn (!out) abbrechen;
            }
        }
        wenn (!out) weitermachen;
        Aufgabe.Ausgabe.Push(raus);
        if (!out.uuid && !out.url) throw new Error('unbekannte Eingabe:' + item.toString());
    }
    gibt null zurück;
}

Die anfänglichen Informationen von RequestItem werden vom Bundle-Objekt abgefragt, und die Bundle-Informationen werden aus der mit dem Bundle gelieferten Datei config.json initialisiert. Wenn das Bundle verpackt wird, werden die Ressourceninformationen im Bundle in config.json geschrieben.

Nach der Verarbeitung durch die Parse-Methode erhalten wir eine Reihe von RequestItems, und viele RequestItems enthalten Informationen wie AssetInfo und UUID. Die Combine-Methode erstellt für jedes RequestItem einen echten Ladepfad, und dieser Ladepfad wird schließlich in item.url konvertiert.

Funktion kombinieren (Aufgabe) {
    var Eingabe = Aufgabe.Ausgabe = Aufgabe.Eingabe;
    für (var i = 0; i < Eingabelänge; i++) {
        var Element = Eingabe[i];
        // Wenn das Element bereits eine URL enthält, überspringen Sie diesen Schritt und verwenden Sie direkt die URL des Elements.
        wenn (item.url) fortfahren;

        var url = '', Basis = '';
        var config = item.config;
        // Bestimmen Sie das Verzeichnispräfix, wenn (item.isNative) {
            Basis = (Konfiguration und Konfiguration.nativeBase)? (Konfiguration.Basis + Konfiguration.nativeBase): cc.assetManager.generalNativeBase;
        } 
        anders {
            Basis = (Konfiguration und Konfiguration.ImportBase)? (Konfiguration.Basis + Konfiguration.ImportBase): cc.assetManager.generalImportBase;
        }

        lass uuid = item.uuid;
            
        var ver = '';
        wenn (Artikelinfo) {
            wenn (item.isNative) {
                ver = item.info.nativeVer ? ('.' + item.info.nativeVer) : '';
            }
            anders {
                ver = item.info.ver ? ('.' + item.info.ver) : '';
            }
        }

        // Verketten Sie die endgültige URL
        // hässlicher Hack, WeChat unterstützt das Laden von Schriftarten wie „myfont.dw213.ttf“ nicht. Hängen Sie also einen Hash an das Verzeichnis an
        wenn (item.ext === '.ttf') {
            url = `${base}/${uuid.slice(0, 2)}/${uuid}${ver}/${item.options.__nativeName__}`;
        }
        anders {
            URL = `${base}/${uuid.slice(0, 2)}/${uuid}${ver}${item.ext}`;
        }
        
        Artikel.URL = URL;
    }
    gibt null zurück;
}

3.1.3 Ladepipeline [Ladevorgang]

Die Lademethode ist sehr einfach. Sie erstellt im Grunde nur eine neue Aufgabe und führt jede Unteraufgabe in loadOneAssetPipeline aus.

Funktion laden (Aufgabe, erledigt) {
    wenn (!task.fortschritt) {
        task.progress = {Fertig: 0, Gesamt: task.input.length};
    }
    
    var Optionen = Task.Optionen, Fortschritt = Task.Fortschritt;
    Optionen.__exclude__ = Optionen.__exclude__ || Objekt.erstellen(null);
    task.ausgabe = [];
    fürJedes(task.input, Funktion (item, cb) {
        // Für jedes Eingabeelement eine Unteraufgabe erstellen und zur Ausführung loadOneAssetPipeline zuweisen let subTask = Task.create({ 
            Eingabe: Artikel, 
            beim Fortschritt: task.onProgress, 
            Optionen, 
            Fortschritt, 
            beiAbgeschlossen: Funktion (Fehler, Element) {
                wenn (err && !task.isFinish && !cc.assetManager.force) fertig(err);
                Aufgabe.Ausgabe.Push(Element);
                subTask.recycle();
                cb();
            }
        });
        // Unteraufgaben ausführen. loadOneAssetPipeline besteht aus Fetch und Parse. loadOneAssetPipeline.async(subTask);
    }, Funktion () {
        // Nachdem jede Eingabe ausgeführt wurde, wird die Funktion zuletzt ausgeführt options.__exclude__ = null;
        wenn (task.isFinish) {
            löschen (Aufgabe, wahr);
            returniere task.dispatch('Fehler');
        }
        Sammeln Sie Assets (Aufgabe).
        löschen (Aufgabe, wahr);
        Erledigt();
    });
}

Wie der Funktionsname schon sagt, ist loadOneAssetPipeline eine Pipeline zum Laden eines Assets. Sie ist in zwei Schritte unterteilt: Abrufen und Analysieren:

Die Fetch-Methode wird zum Herunterladen von Ressourcendateien verwendet. PackManager ist für die Download-Implementierung verantwortlich. Fetch legt die heruntergeladenen Dateidaten in item.file ab.

Mit der Parse-Methode wird die geladene Ressourcendatei in ein Ressourcenobjekt konvertiert, das wir verwenden können.

Rufen Sie für native Ressourcen zum Parsen parser.parse auf. Diese Methode ruft je nach Ressourcentyp unterschiedliche Parsingmethoden auf.

  • Importressourcen rufen die Methode parseImport auf, deserialisieren das Asset-Objekt entsprechend den JSON-Daten und legen es in Assets ab
  • Die Bildressource ruft die Methode parseImage, parsePVRTex oder parsePKMTex auf, um das Bildformat zu analysieren (erstellt jedoch kein Texturobjekt).
  • Die Soundeffektressource ruft die Methode parseAudio zum Parsen auf.
  • Die plist-Ressource ruft die parsePlist-Methode auf, um

Für andere Ressourcen

Wenn die UUID in task.options.__exclude__ steht, wird sie als abgeschlossen markiert und der Referenzzähler hinzugefügt. Andernfalls werden einige komplexe Bedingungen verwendet, um zu bestimmen, ob die Ressourcenabhängigkeit geladen werden soll.

var loadOneAssetPipeline = neue Pipeline('loadOneAsset', [
    Funktion fetch (Aufgabe, erledigt) {
        var item = task.ausgabe = task.eingabe;
        var { Optionen, isNative, UUID, Datei } = Element;
        var { neu laden } = Optionen;
        // Wenn die Ressource in Assets geladen wurde, vervollständigen Sie sie direkt, wenn (file || (!reload && !isNative && asset.has(uuid))) return done();
        // Laden Sie die Datei herunter. Dies ist ein asynchroner Prozess. Nachdem die Datei heruntergeladen wurde, wird sie in item.file abgelegt und die fertige Treiberpipeline wird ausgeführt packManager.load(item, task.options, function (err, data) {
            wenn (Fehler) {
                wenn (cc.assetManager.force) {
                    err = null;
                } anders {
                    cc.error(Fehlernachricht, Fehlerstapel);
                }
                Daten = null;
            }
            item.file = Daten;
            fertig (Fehler);
        });
    },
    // Der Prozess der Konvertierung von Ressourcendateien in Ressourcenobjekte Funktion parse (task, done) {
        var item = task.output = task.input, Fortschritt = task.fortschritt, ausschließen = task.optionen.__exclude__;
        var { id, Datei, Optionen } = Element;

        wenn (item.isNative) {
            // Rufen Sie für native Ressourcen parser.parse auf, um sie zu verarbeiten, fügen Sie die verarbeiteten Ressourcen in item.content ein und beenden Sie den Prozess parser.parse(id, file, item.ext, options, function (err, asset) {
                wenn (Fehler) {
                    wenn (!cc.assetManager.force) {
                        cc.error(Fehlernachricht, Fehlerstapel);
                        Rückgabe erledigt (Fehler);
                    }
                }
                Artikel.Inhalt = Vermögenswert;
                task.dispatch('Fortschritt', ++Fortschritt.fertig, Fortschritt.gesamt, Element);
                Dateien.entfernen(id);
                analysiert.entfernen(id);
                Erledigt();
            });
        } anders {
            var { uuid } = Artikel;
            // Nicht native Ressourcen, wenn in task.options.__exclude__, direkt beenden, wenn (uuid in exclude) {
                var { Fertigstellen, Inhalt, Fehler, Rückrufe} = Ausschließen[uuid];
                task.dispatch('Fortschritt', ++Fortschritt.fertig, Fortschritt.gesamt, Element);
    
                wenn (fertig || checkCircleReference(uuid, uuid, ausschließen) ) {
                    Inhalt && Inhalt.addRef();
                    item.content = Inhalt;
                    fertig (Fehler);
                } anders {
                    callbacks.push({ erledigt, Element });
                }
            } anders {
                // Wenn es nicht neu geladen wird und das Asset die UUID enthält
                wenn (!optionen.reload && asset.has(uuid)) {
                    var asset = asset.get(uuid);
                    // Wenn options.__asyncLoadAssets__ aktiviert ist oder asset.__asyncLoadAssets__ false ist, wird der Prozess beendet, ohne Abhängigkeiten zu laden, if (options.__asyncLoadAssets__ || !asset.__asyncLoadAssets__) {
                        Element.Inhalt = Asset.addRef();
                        task.dispatch('Fortschritt', ++Fortschritt.fertig, Fortschritt.gesamt, Element);
                        Erledigt();
                    }
                    anders {
                        loadDepends(Aufgabe, Vermögenswert, erledigt, falsch);
                    }
                } anders {
                    // Wenn es neu geladen wird oder nicht in den Assets ist, analysiere es und lade die Abhängigkeiten.parse(id, file, 'import', options, function (err, asset) {
                        wenn (Fehler) {
                            wenn (cc.assetManager.force) {
                                err = null;
                            }
                            anders {
                                cc.error(Fehlernachricht, Fehlerstapel);
                            }
                            Rückgabe erledigt (Fehler);
                        }
                        
                        asset._uuid = uuid;
                        loadDepends(Aufgabe, Vermögenswert, erledigt, wahr);
                    });
                }
            }
        }
    }
]);

3.2 Dateidownload

Der Ersteller verwendet packManager.load , um den Downloadvorgang abzuschließen. Beim Herunterladen einer Datei müssen zwei Aspekte berücksichtigt werden:

  • Ob die Datei gepackt ist, zum Beispiel weil alle SpriteFrames inline sind, wird die SpriteFrame-JSON-Datei in das Prefab integriert
  • Die aktuelle Plattform ist eine native Plattform oder eine Webplattform. Einige lokale Ressourcen muss die native Plattform von der Festplatte lesen.
// Implementierung von packManager.load load (item, options, onComplete) {
  // Wenn die Ressource nicht gepackt ist, rufen Sie downloader.download direkt auf, um sie herunterzuladen (der Download beurteilt auch, ob sie heruntergeladen wurde oder geladen wird).
  wenn (item.isNative || !item.info || !item.info.packs) return downloader.download(item.id, item.url, item.ext, item.options, beiAbgeschlossen);
  // Wenn die Datei heruntergeladen wurde, direkt zurückkehren, if (files.has(item.id)) return onComplete(null, files.get(item.id));

  var packs = item.info.packs;
  // Wenn das Paket bereits geladen wird, fügen Sie den Rückruf zur Warteschlange „_loading“ hinzu und lösen Sie den Rückruf aus, nachdem der Ladevorgang abgeschlossen ist. var pack = packs.find(isLoading);
  Wenn (pack) return _loading.get(pack.uuid).push({ bei Abschluss, id: item.id });

  // Laden Sie ein neues Paket herunter
  Packung = Packungen[0];
  _loading.add(pack.uuid, [{ bei Abschluss, id: item.id }]);
  let url = cc.assetManager._transform(pack.uuid, {ext: pack.ext, bundle: item.config.name});
  // Das Paket herunterladen und entpacken,
  downloader.download(pack.uuid, url, pack.ext, item.options, function (err, data) {
      Dateien.entfernen(pack.uuid);
      wenn (Fehler) {
          cc.error(Fehlernachricht, Fehlerstapel);
      }
      // Paket entpacken, die interne Implementierung umfasst zwei Arten des Entpackens, eine für die Segmentierung und Entpackung von JSON-Arrays wie Prefab und Atlas und die andere für den Inhalt von Texture2D packManager.unpack(pack.packs, data, pack.ext, item.options, function (err, result) {
          wenn (!err) {
              für (var id im Ergebnis) {
                  Dateien.Hinzufügen(ID, Ergebnis[ID]);
              }
          }
          var Rückrufe = _loading.remove(pack.uuid);
          für (var i = 0, l = callbacks.length; i < l; i++) {
              var cb = Rückrufe[i];
              wenn (Fehler) {
                  cb.onComplete(er);
                  weitermachen;
              }

              var Daten = Ergebnis[cb.id];
              wenn (!data) {
                  cb.onComplete(neuer Fehler('Daten können nicht aus Paket abgerufen werden'));
              }
              anders {
                  cb.onComplete(null, Daten);
              }
          }
      });
  });
}

3.2.1 Herunterladen von der Web-Plattform

Die Download-Implementierung der Webplattform ist wie folgt:

  • Verwenden Sie ein Downloader-Array, um die Downloadmethoden für verschiedene Ressourcentypen zu verwalten
  • Verwenden Sie den Dateicache, um doppelte Downloads zu vermeiden
  • Verwenden Sie die _downloading-Warteschlange, um Rückrufe zu verarbeiten, wenn dieselbe Ressource gleichzeitig heruntergeladen wird, und stellen Sie das Timing sicher
  • Unterstützt Downloadpriorität, Wiederholung und andere Logik
Download (ID, URL, Typ, Optionen, bei Abschluss) {
  // Den entsprechenden Typ des Download-Callbacks in Downloadern abrufen let func = Downloader[Typ] || Downloader['Standard'];
  lass self = dies;
  // Wiederholte Downloads vermeiden let file, downloadCallbacks;
  wenn (Datei = Dateien.get(id)) {
      beiAbgeschlossen(null, Datei);
  }
  // Beim Herunterladen zur Warteschlange hinzufügen, sonst wenn (downloadCallbacks = _downloading.get(id)) {
      downloadCallbacks.push(beiAbgeschlossen);
      für (lass i = 0, l = _Warteschlangenlänge; i < l; i++) {
          var item = _queue[i];
          wenn (item.id === id) {
              var Priorität = Optionen.Priorität || 0;
              if (item.priority < priority) {
                  item.priority = Priorität;
                  _queueDirty = wahr;
              } 
              zurückkehren;
          }
      } 
  }
  anders {
      // Herunterladen und Wiederholungsversuche für fehlgeschlagene Downloads einrichten var maxRetryCount = options.maxRetryCount || this.maxRetryCount;
      var maxConcurrency = Optionen.maxConcurrency || diese.maxConcurrency;
      var maxRequestsPerFrame = Optionen.maxRequestsPerFrame || diese.maxRequestsPerFrame;

      Funktionsprozess (Index, Rückruf) {
          wenn (Index === 0) {
              _downloading.add(id, [beiAbgeschlossen]);
          }
          if (! self.Limited) return func (urlAppendTimestamp (URL), Optionen, Rückruf);
          UpdateTime ();

          Funktion invoke () {
              func (urlAppendTimestamp (URL), Optionen, Funktion () {
                  // Beim Herunterladen _totalnum aktualisieren
                  _totalnum--;
                  if (! _checknextPeriod && _queue.length> 0) {
                      CallInnextTick (Handle, MaxConcurrency, MaxRequestSPerFrame);
                      _ChecknextPeriod = true;
                  }
                  Callback.Apply (dies, Argumente);
              });
          }

          if (_totalnum <maxconcurrency && _totalnumthisperiode <maxRequestSPerFrame) {
              aufrufen();
              _totalnum ++;
              _totalnumthisperioD ++;
          }
          anders {
              // Wenn die Anzahl der Anfragen Einschränkung erreicht, zwischen dem Rest zwischenstrichen
              _queue.push ({id, priorität: options.priority || 0, aufgerufen});
              _queuedirty = true;

              if (! _checknextPeriod && _totalnum <maxconcurrency) {
                  CallInnextTick (Handle, MaxConcurrency, MaxRequestSPerFrame);
                  _ChecknextPeriod = true;
              }
          }
      }

      // Nach Abschluss der Datei den Dateien -Cache hinzufügen, sie aus der Warteschlange _download entfernen und Rückrufe ausführen.
      Funktionsfinale (Err, Ergebnis) {
          if (! err) Dateien.add (ID, Ergebnis);
          var callbacks = _downloading.remove (id);
          für (sei i = 0, l = callbacks.length; i <l; i ++) {
              Rückrufe [i] (err, Ergebnis);
          }
      }

      Wiederholung (Prozess, MaxRetryCount, this.retryInterval, Finale);
  }
}

Downloader ist eine Karte, die die Download -Methoden, die verschiedenen Ressourcentypen entsprechen, auf der Webplattform entsprechen.

Bildklasse DownloadImage

  • DownloadDomImage Verwenden Sie das Bildelement von HTML und geben Sie das SRC -Attribut zum Herunterladen an
  • DownloadBlob laden Sie das Bild als Datei herunter

Dateiklasse, die in Binärdateien, JSON -Dateien und Textdateien unterteilt werden kann

  • DownloadArrayBuffer gibt den ArrayBuffer -Typ an, der DownloadFile zum Herunterladen von Dateien wie SKEL, BIN, PVR usw. anrufen soll.
  • DownloadText gibt den Texttyp an, das DownloadFile aufgerufen hat, der zum Herunterladen von Dateien wie ATLAS, TMX, XML und VSH verwendet wird.
  • DownloadJSON gibt den JSON -Typ an, um DownloadFile anzurufen, und analysiert den JSON nach dem Download, der zum Herunterladen von Dateien wie Plist und JSON verwendet wird

Font Class Loadfont baut CSS -Stil und gibt die URL zum Herunterladen an

Sound Class Downloadaudio

  • Downloaddomaudio erstellt ein HTML -Audioelement und gibt sein SRC -Attribut zum Download an
  • DownloadBlob laden Sie den Soundeffekt als Datei herunter

Der Videoklassen -DownloadVideo -Web -Client kehrt direkt zurück

Das Skript -DownloadScript erstellt ein HTML -Skriptelement und gibt sein SRC -Attribut zum Herunterladen und Ausführen an

Bündel DownloadBundle lädt gleichzeitig das JSON und die Skripte des Bundles herunter

DownloadFile verwendet XMLHTTPrequest, um Dateien herunterzuladen.

Funktion DownloadFile (URL, Optionen, Onprogress, OnComplete) {
    var {Optionen, onProgress, onComplete} = Parseparameter (Optionen, On -Progress, OnComplete);
    var xhr = neu xmlhttprequest (), errinfo = 'Download fehlgeschlagen:' + url + ', Status:';
    xhr.open('GET', URL, wahr);
    
    if (options.responsetype!
    if (options.withcredentials! == undefiniert) xhr.withcredentials = options
    if (options.mimetype!
    if (options.timeout! == undefined) xhr.timeout = options.timeout;

    if (options.Header) {
        für (var header in options.Header) {
            xhr.setRequestHeader (Header, Optionen.Header [Header]);
        }
    }

    xhr.onload = Funktion () {
        if (xhr.status === 200 || xhr.status === 0) {
            onComplete && onComplete (null, xhr.response);
        } anders {
            OnComplete && onComplete (neuer Fehler (errinfo + xhr.status + '(keine Antwort)'));
        }

    };

    if (onProgress) {
        xhr.onprogress = function (e) {
            if (e.LengthComputable) {
                onProgress (e.Laded, e.total);
            }
        };
    }

    xhr.onError = function () {
        OnComplete && onComplete (neuer Fehler (Errinfo + xhr.status + '(error)'));
    };
    xhr.ontimeout = function () {
        OnComplete && onComplete (neuer Fehler (errinfo + xhr.status + '(Zeitausgang)'));
    };
    xhr.onabort = function () {
        OnComplete && onComplete (neuer Fehler (errinfo + xhr.status + '(abort)'));
    };

    xhr.send (null);
    Rückgabe xhr;
}

3.2.2 Native Plattform Download

Die in Engine-bezogenen Dateien der nativen Plattform finden Sie im resources/builtin/jsb-adapter/engine .

Downloader.register ({{
    // JS
    '.js': downloadScript,
    '.jsc': downloadScript,

    // Bilder
    '.png': downloadasset,
    '.jpg': downloadasset,
    ...
});

Auf nativen Plattformen werden Methoden wie DownloadAset Download anrufen, um Ressourcen herunterzuladen. Es müssen nur Netzwerkressourcen heruntergeladen werden, die nicht heruntergeladen wurden. Dateien, die nicht heruntergeladen werden müssen, werden direkt gelesen, wo die Datei analysiert wird.

// Func übergibt die Verarbeitung nach Abschluss des Downloads.
// Wenn Sie eine JSON -Ressource herunterladen möchten, ist der übergebene Func nicht mehr, was bedeutet, direkt die OnComplete -Methode -Funktion Download (URL, Func, Optionen, OnFileProgress, OnComplete) {
    var result = transformurl (URL, Optionen);
    // Wenn es sich um eine lokale Datei handelt, zeigen Sie direkt auf Func
    if (result.inlocal) {
        func (result.url, option, onComplete);
    }
    // Wenn im Cache die letzte gebrauchte Zeit (LRU) der Ressource aktualisieren, aktualisieren Sie
    sonst if (result.incache) {
        CACHEMANATER.UPDATELASTTIME (URL)
        func (result.Url, Optionen, Funktion (Err, Daten) {
            wenn (Fehler) {
                CacheManager.Removecache (URL);
            }
            onComplete (err, data);
        });
    }
    anders {
        // Für Netzwerkressourcen, die nicht heruntergeladen wurden, rufen Sie DownloadFile an, um var time = date.now () herunterzuladen;
        var StoragePath = '';
        if (Optionen .__ CacheBundleroot__) {
            StoragePath = `$ {CACHEMANAGER.CacheDir}/$ {Optionen .__ CacheBundleroot __}/$ {time} $ {Suffix ++} $ {cc.path.extName (url)}`;
        }
        anders {
            StoragePath = `$ {CACHEMANAGER.CacheDir}/$ {Zeit} $ {Suffix ++} $ {cc.path.extname (url)}`;
        }
        // Download und Cache mit DownloadFile DownloadFile (URL, StoragePath, Optionen.Header, OnFileProgress, Funktion (Err, Path) {
            wenn (Fehler) {
                onComplete (err, null);
                zurückkehren;
            }
            Func (Pfad, Optionen, Funktion (Err, Daten) {
                wenn (!err) {
                    CacheManager.CacheFile (URL, StoragePath, Optionen .__ CacheBundleroot__);
                }
                onComplete (err, data);
            });
        });
    }
}

Funktionstransformurl (URL, Optionen) {
    var inlocal = false;
    var Incache = false;
    // Überprüfen Sie, ob es sich um eine URL nach regulärem Ausdruck handelt
    if (regex.test (url)) {
        if (options.reload) {
            return {url};
        }
        anders {
            // Überprüfen Sie, ob es sich im Cache befindet (lokaler Festplatten -Cache)
            var cache = cacheManager.cachedFiles.get (URL);
            if (cache) {
                Incache = true;
                url = cache.url;
            }
        }
    }
    anders {
        inlocal = true;
    }
    return {url, inlocal, Incache};
}

DownloadFile ruft den JSB_Downloader der nativen Plattform an, um Ressourcen herunterzuladen und auf der lokalen Festplatte zu speichern

DownloadFile (Remoteurl, Filepath, Header, Onprogress, OnComplete) {
  downloading.add (remoteurl, {onProgress, onComplete});
  var sporingPath = filepath;
  if (! StoragePath) StoragePath = tempdir + '/' + Performance.Now () + cc.Path.extName (remoteurl);
  jsb_downloader.createdownloadFileTask (Remoteurl, StoragePath, Header);
},

3.3 Datei Parsen

In LoadoneasSetPipeline werden Ressourcen von zwei Pipelines verarbeitet: Fetch und Parse ist für das Herunterladen verantwortlich, während Parse für die Parsen von Ressourcen und die Instanziierung von Ressourcenobjekten verantwortlich ist. In der Parse -Methode wird Parser.Parse aufgerufen, um den Dateiinhalt zu übergeben, sie in das entsprechende Anlageobjekt zu analysieren und zurückzugeben.

3.3.1 Webplattformanalyse

Die Hauptfunktion von Parser.Parse auf der Webplattform besteht darin, die analysierten Dateien zu verwalten, eine Liste der analysierten und analysierten Dateien zu verwalten und wiederholtes Parsen zu vermeiden. Gleichzeitig wird eine Liste von Rückrufen nach Abschluss der Parsen beibehalten und die tatsächliche Parsingmethode befindet sich im Parsers -Array.

analysieren (ID, Datei, Typ, Optionen, OnComplete) {
  Lassen Sie ParseDasset, Parsen, Parsehandler;
  if (parseDasset = parsed.get (id)) {
      OnComplete (NULL, PARSEDASSET);
  }
  else if (parsing = _parsing.get (id)) {
      Parsing.push (onComplete);
  }
  else if (ParseHandler = Parsers [Typ]) {
      _Parsing.add (id, [onComplete]);
      ParseHandler (Datei, Optionen, Funktion (Err, Daten) {
          wenn (Fehler) {
              Dateien.Remove (ID);
          } 
          sonst if (! isscene (Daten)) {
              Parsed.add (ID, Daten);
          }
          lass callbacks = _parsing.remove (id);
          für (sei i = 0, l = callbacks.length; i <l; i ++) {
              Rückrufe [i] (Err, Daten);
          }
      });
  }
  anders {
      onComplete (null, Datei);
  }
}

Parsers kartieren die Parsingmethoden verschiedener Arten von Dateien.

HINWEIS: In der Parseemport -Methode wird die Deserialisierungsmethode die Ressourcenabhängigkeiten in Asset einbringt. Die Abhängigkeit von wo ein Array ist. Beispielsweise hat eine Prefab -Ressource zwei Knoten, beide auf dieselbe Ressource.

// Bildformate zu Parsen -Methoden zuordnen 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,
  // Audio
  '.mp3': Parser.Parseaudio,
  '.ogg': Parser.Parseaudio,
  '.wav': Parser.Parseaudio,
  '.m4a': Parser.Parseaudio,

  // plist
  '.plist': Parser.Parseplist,
  'Import': Parser.Parseymport
};

// Das Bild wird nicht in ein Asset -Objekt analysiert, sondern in das entsprechende Bildobjekt Parseimage (Datei, Optionen, OnComplete) {
  if (fähigkeiten.imagebitmap && Dateiinstanz von Blob) {
      lass ImageOptions = {};
      ImageOptions.imageOrientation = Optionen .__ Flipy__?
      ImageOptions.Premultiplyalpha = Optionen .__ Premultiplyalpha__?
      createImageBitMap (Datei, Bildoptionen) .then (Funktion (Ergebnis) {
          result.flipy = !! Optionen .__ Flipy__;
          result.premultiplyalpha = !! Optionen .__ Premultiplyalpha__;
          OnComplete && onComplete (null, Ergebnis);
      }, function (err) {
          onComplete && onComplete (err, null);
      });
  }
  anders {
      OnComplete && onComplete (null, Datei);
  }
},

// Analyse des Vermögensobjekts wird durch Deserialize implementiert.
PARSEIMPORT (Datei, Optionen, OnComplete) {
  if (! file) return onComplete && onComplete (neuer Fehler ('json ist leer'));
  var result, err = null;
  versuchen {
      result = Deserialize (Datei, Optionen);
  }
  fangen (e) {
      err = e;
  }
  onComplete && onComplete (err, Ergebnis);
},

3.3.2 native Plattformanalyse

Auf der nativen Plattform registriert JSB-loader.js die Parsing-Methoden verschiedener Ressourcen:

Parser.register ({{{
    '.png': downloader.downloaddomage,,
    '. Binär': ParsearrayBuffer,
    '.txt': paretExt,
    '.plist': Parseplist,
    '.font': lastfont,
    '.Exportjson': Parsejson,
    ...
});

Die Bild -Parsen -Methode ist tatsächlich Downloader.downloaddomage? Nach der Verfolgung der nativen Plattform und der Debugging wird diese Methode in der Tat ein Bildobjekt erstellt und SRC wird angegeben, um das Bild zu laden. Durch die JSON -Datei, die Texture2D entspricht, hat der Ersteller das texture2d -Asset -Objekt bereits erstellt, bevor die native Textur geladen wird.

var texture2d = cc.class ({{
    Name: 'cc.texture2d',
    Erweitert: Erfordernst ('../ Assets/Ccasset'),
    Mixins: [EventTarget],

    Eigenschaften:
        _nativeasSet: {
            erhalten () {
                // Vielleicht zurück zum Pool in WebGL zurückgekehrt
                Gibt dies zurück._image;
            },
            set (Daten) {
                if (data._data) {
                    this.initwithData (data._data, this._format, data.width, data.height);
                }
                anders {
                    this.initwithelement (Daten);
                }
            },
            Override: True
        },

In Bezug auf die Implementierungen von Parsejson, ParsetExt, ParsearrayBuffer usw. rufen sie einfach das Dateisystem auf, um die Datei zu lesen. Was ist mit einigen Ressourcen, die weiter analysieren müssen, bevor sie nach dem Erhalten des Dateiinhalts verwendet werden können? Beispielsweise stützen sich Modelle, Skelette und andere Ressourcen auf binäre Modelldaten. Das ist richtig, genau wie die oben genannte Textur2d werden sie alle in der entsprechenden Asset -Ressource gesetzt.

// In jsb-loader.js Dateifunktion ParsetExt (URL, Optionen, OnComplete) {
    ReadText (URL, OnComplete);
}

Funktion ParsearrayBuffer (URL, Optionen, OnComplete) {
    ReadArrayBuffer (URL, OnComplete);
}

Funktion Parsejson (URL, Optionen, OnComplete) {
    Readjson (URL, OnComplete);
}

// In JSB-FS-UTILS.JS-Datei 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;
            wenn (!err) {
                versuchen {
                    out = json.Parse (Text);
                }
                fangen (e) {
                    cc.warn ('Read JSON fehlgeschlagen:' + e.message);
                    Err = neuer Fehler (E.Message);
                }
            }
            OnComplete && onComplete (err, out);
        });
    },

Wie werden Ressourcen wie Atlasen und Präfrages initialisiert? Der Schöpfer verwendet weiterhin die Parseeimport -Methode zum Parsen, da der Typ, der diesen Ressourcen entspricht, import wird, und die native Plattform umfasst nicht die an dieser Art entsprechende Parsefunktion, und diese Ressourcen werden direkt in nutzbare Asset -Objekte deserialisiert.

3.4 Abhängigkeitslast

Der Schöpfer unterteilt Ressourcen in zwei Kategorien, gemeinsame Ressourcen und häufige Ressourcen. Zu den nativen Ressourcen gehören Texturen, Musik, Schriftarten und andere Dateien in verschiedenen Formaten.

Im _parseNativeDepFromJson _parseDepsFromJson ein Präfit von vielen Ressourcen abhängen. LOADDEPENDS sammelt Ressourcenabhängigkeiten über die GetDepends -Methode.

LOADDEPENDS schafft eine Subtask, um abhängige Ressourcen zu laden und die Pipeline zu rufen, um das Laden durchzuführen.

  • Initialisieren Sie Assset: Nachdem die Abhängigkeiten geladen wurden
  • Entfernen Sie den der Ressource entsprechenden Dateien und an Parsen -Cache und unterbinden Sie die Ressource in Vermögenswerte (wenn es sich um eine Szene handelt, wird es nicht zwischengespeichert).
  • Führen Sie die Rückrufe in der Liste repeatingItem.callbacks aus (konstruiert zu Beginn von LoadDepends, wobei die übergebene in der gemeldeten Methode standardmäßig übergeben wird)
// Laden Sie die Abhängigkeiten der angegebenen Asset -Funktionslastdarsteller (Aufgabe, Vermögenswert, fertiggestellt, init) {laden Sie.

    var item = task.input, progress = task.progress;
    var {uUid, id, Optionen, config} = item;
    var {__asyncloadassets__, CacheasSet} = Optionen;

    var hängt ab = [];
    // Erhöhen Sie die Referenzzahl, um zu vermeiden, dass Ressourcen während des Ladens des Ladens von Abhängigkeiten freigegeben werden, und rufen Sie GetDepends an, um abhängige Ressourcen asset.addref && asset.addref () zu erhalten.
    getDepends (UUID, Asset, Object.create (NULL), hängt davon ab, falsch, __asyncloadassets__, config);
    Task.dispatch ('Fortschritt', ++ progresh.

    var repepeItem = task.options .__ Schließen Sie __ [uUid] = {Inhalt: Asset, Finish: False, Callbacks: [{Done, Element}]};

    lass subtask = task.create ({{ 
        Eingabe: Kommt darauf ab, 
        Optionen: task.options, 
        On -Progress: Task.onprogress, 
        OnError: Task.Prototype.recycle, 
        Fortschritt, 
        onComplete: function (err) {
            // Rückruf Nachdem alle Abhängigkeiten Asset geladen wurden.
            Asset .__ Asyncloadassets__ = __asyncloadassets __;
            repepitem.finish = true;
            repepitem.err = err;

            wenn (!err) {
                var assets = array.isArray (subtask.output)?
                // Erstellen Sie eine Karte, um die Zuordnung von UUID zu Asset var map = Object.create (null) aufzuzeichnen.
                für (sei i = 0, l = assets.length; i <l; i ++) {
                    var abhängig = assets [i];
                    abhängig && (map [abhängige Instanz von cc.asset? abhängig._uuid + '@import': uUid + '@native'] = abhängigset);
                }

                // Call setProperties zum Festlegen der entsprechenden abhängigen Ressourcen auf die Mitgliedsvariablen von assetif (! Init) {
                    if (asset .__ nativedEpend__ &&! asset._nativeasset) {
                        var Misseraset = setProperties (UUID, Asset, MAP);
                        if (! fehlende asset) {
                            versuchen {
                                asset.onload && asset.onload ();
                            }
                            fangen (e) {
                                CC.Error (E.Message, E.Stack);
                            }
                        }
                    }
                }
                anders {
                    var Misseraset = setProperties (UUID, Asset, MAP);
                    if (! fehlende asset) {
                        versuchen {
                            asset.onload && asset.onload ();
                        }
                        fangen (e) {
                            CC.Error (E.Message, E.Stack);
                        }
                    }
                    Dateien.Remove (ID);
                    analysiert.remove (id);
                    Cache (UUID, Asset, CacheasSet! == undefiniert? 
                }
                subtask.recycle ();
            }
            
            // Dieser Wiederholung kann von vielen Orten geladen werden, und alle Rückrufe müssen über die VAR -Rückrufe der Ladeabschluss informiert werden.
            für (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 Abhängigkeitslösung

getDepends (uUid, Daten, ausschließen, hängt davon ab, vorladen, asyncloadassets, config) {
  var err = null;
  versuchen {
      var info = abhängig.Parse (UUID, Daten);
      var includeNative = true;
      if (Dateninstanz von cc.asset && (! data .__ nativedEpend__ || data._nativeasSet)) includeNative = false; 
      if (! vorladen) {
          asyncloadassets =! cc_editor && (!! data.asyncloadassets || (asyncloadassets &&! info.preventDeferred loadDependents));
          für (sei i = 0, l = info.deps.length; i <l; i ++) {
              sei dep = info.deps [i];
              if (! (DEP in Excloud)) {
                  [dep] = true ausschließen;
                  abhängigs.push ({uUid: dep, __asyncloadassets__: asyncloadassets, bündel: config && config.name});
              }
          }

          if (einschließlich &&!
              config && (info.nativedep.bundle = config.name);
              abhängigs.push (info.nativedEp);
          }
          
      } anders {
          für (sei i = 0, l = info.deps.length; i <l; i ++) {
              sei dep = info.deps [i];
              if (! (DEP in Excloud)) {
                  [dep] = true ausschließen;
                  abhängigs.push ({UUID: DEP, Bundle: config && config.name});
              }
          }
          if (includeNative && info.nativedEp) {
              config && (info.nativedep.bundle = config.name);
              abhängigs.push (info.nativedEp);
          }
      }
  }
  fangen (e) {
      err = e;
  }
  Rückgabefehler;
},

Dependutil ist ein Singleton, der die Abhängigkeitsliste steuert.

  • DEPs abhängige Vermögensressourcen
  • nativedep native Ressourcen, von denen abhängig ist
  • Verhindern Sie die Vorladung der Vorladung native Objekte.
  • Verhinderung von Vorladungen verhindert eine verzögerte Belastung von Abhängigkeiten.
  • Parsedfromexistasset, ob es direkt aus asset.__depends__

Dependutil verwaltet auch einen Cache, um wiederholte Abfragen von Abhängigkeiten zu vermeiden.

// Erhalten Sie die Ressourcenabhängigkeitsliste basierend auf den JSON -Informationen.
  var out = null;
  // Wenn es sich um eine Szene oder ein Vorfeld handelt, sind Daten ein Array, eine Szene oder ein Präfabab
  if (array.isarray (json)) {
      // Wenn es analysiert wurde und in _Depends eine Abhängigkeitsliste vorhanden ist, kehren Sie direkt zurück, wenn (this.depends.has (uUid)) diese._Depends.get (uUid) zurückgeben.
      out = {
          // Verwenden Sie für Prefab oder Szene direkt die Methode _parseDepsfromjson, um DEPS zurückzugeben: cc.asset._parsedepsfromjson (JSON),
          Asyncloadassets: JSON [0] .Asyncloadassets
      };
  }
  // Wenn __Type__ enthalten ist, holen Sie sich seinen Konstruktor und finden Sie abhängige Ressourcen von JSON Get Deps von JSON
  // In den tatsächlichen Tests folgen die vorinstallierten Ressourcen dem folgenden Zweig.
      if (this._depends.has (uUid)) return this._depends.get (uUid);
      var ctor = js._getClassbyId (json .__ Typ__);
      // Einige Ressourcen schreiben die _parseDepsfromjson und _parsenativedEpfromjson -Methoden um // beispielsweise cc.texture2d
      out = {
          PREVEPREPRELOADNATIONOBJECT:
          Voraussetzungen für vorgefertigte Ladevorschriften:
          DEPs: ctor._parsedepsfromjson (JSON),
          nativedp: ctor._parsenativedpfromjson (JSON)
      };
      out.nativedEp && (out.nativedEp.uuid = uUid);
  }
  // Holen Sie sich DEPs von einem vorhandenen Vermögenswert 
  // Wenn es kein Feld __Type __ gibt, kann sein entsprechender CTOR nicht gefunden werden, und die Abhängigkeit wird aus dem Feld __DEPEND__ des Vermögenswerts noch {abgenommen {
      if (! cc_editor && (out = this._depends.get (uUid)) && out.parsedfromexistasset) kehren aus;
      var asset = json;
      out = {
          DEPs: [],
          Parsedfromexistasset: Richtig,
          PREVEPRELOADNATIONOBJECT: Asset.Constructor.PreventPreloLeAnativeObject,
          Voraussetzungen für vorgefertigte Angaben:
      };
      Sei DEPS = Asset .__ hängt __ ab;
      für (var i = 0, l = deps.length; i <l; i ++) {
          var dep = dEPs [i] .UUID;
          out.deps.push (DEP);
      }
  
      if (asset .__ nativedEpend__) {
          // asset._nativedep wird ein Objekt wie dieses zurückgeben {__isnative__: true, uUid: this._uuid, ext: this._native}
          out.nativedEp = asset._nativedEp;
      }
  }
  // Wenn eine Abhängigkeit zum ersten Mal gefunden wird, stecken Sie sie direkt in die Liste der Abhängigkeit, Cache -Abhängigkeitsliste
  this._depends.add (uUid, out);
  Rückkehr raus;
}

Die Standardimplementierung von CCassets _parseDepsFromJson und _parseNativeDepFromJson _parseDepsFromJson wie folgt __uuid__ Die Implementierung von Texture2D, TTFFONT und Audioclip besteht darin, ein leeres Array direkt zurückzugeben, während die Implementierung von SpriteFrame cc.assetManager.utils.decodeUuid(json.content.texture) zurückgibt.

_parseNativeDepFromJson wird { __isNative__: true, ext: json._native} zurückgegeben, wenn _native des Vermögenswerts einen Wert hat. Tatsächlich verwenden {__isNative__: true, uuid: this._uuid, ext: this._native} meisten nativen Ressourcen _nativeDep .

_parsedepsfromjson (JSON) {
      var hängt ab = [];
      ParseDePendRecursiv (JSON, hängt davon ab);
      Return hängt davon ab;
},

_parsenativedEpfromjson (JSON) {
if (json._native) return {__isnative__: true, ext: json._native};
      gibt null zurück;
}

3.5 Ressourcenveröffentlichung

Dieser Abschnitt konzentriert sich auf drei Möglichkeiten, Ressourcen im Ersteller und deren Implementierung freizugeben, und führt schließlich vor, wie Ressourcenlecks im Projekt Fehler beheben können.

3.5.1 Creator Resource Release

Der Schöpfer unterstützt die folgenden drei Möglichkeiten zur Veröffentlichung von Ressourcen:

Release -Methode Freisetzungseffekt
Überprüfen Sie: Szene-> Eigenschaften Inspektor-> automatische Release-Ressourcen Nach dem Umschalten der Szene werden Ressourcen, die nicht von der neuen Szene verwendet werden, automatisch veröffentlicht
Referenzzahl Release Res.Decref Verwenden Sie AddRef und Decref, um die Referenzzahl beizubehalten, und lassen Sie sie automatisch frei, wenn die Referenzzahl nach dem Dekref 0 erreicht.
Handbuch Release cc.assetManager.ReleasEsSet (Textur); Ressourcen manuell veröffentlichen, erzwungene Veröffentlichung

3.5.2 Automatische Szenenveröffentlichung

Wenn eine neue Szene ausgeführt wird, wird der Director.RunsceneMediate -Methode ausgeführt.

RunSceneMediate: Funktion (Szene, onBeforeLoadscene, Onlaunted) {
  // Code weggelassen ...
  var oldscene = this._scene;
  if (! cc_editor) {
      // Ressourcen automatisch veröffentlichen cc_build && cc_debug && console.time ('autorelease');
      cc.assetmanager._releasemanager._Autorelease (OldScene, Szene, Persistnodelist);
      Cc_build && cc_debug && console.timeend ('autorelease');
  }

  // Szene entladen
  Cc_build && cc_debug && console.time ('destroy');
  if (cc.isvalid (Oldscene)) {
      OldScene.Destroy ();
  }
  // Code weggelassen ...
},

Die neueste Version von _Autorelease ist sehr präzise und unkompliziert.

// automatische Veröffentlichung durchführen
_Autorelease (Oldscene, Newscene, Persistnodes) { 
  // Alle Ressourcen, von denen anhaltende Knoten abhängen
      var node = persistnodes [i];
      var scedeps = abhängig._Depends.get (newscene._id);
      var deps = _PersistnodEDeps.get (node.uuid);
      für (sei i = 0, l = deps.length; i <l; i ++) {
          var abhängig = assets.get (DEPS [i]);
          if (abhängig) {
              abhängig.addref ();
          }
      }
      if (scedeps) {
          ! scedeps.persistdeps && (scedeps.Persistdeps = []);
          scedeps.persistdeps.push.apply (scedeps.Persistdeps, DEPS);
      }
  }

  // Veröffentlichen Sie die Abhängigkeiten der alten Szene, wenn (Oldscene) {
      var Kinder = abhängig.getDeps (oldscene._id);
      für (sei i = 0, l = Kinder.Length; i <l; i ++) {
          Sei Asset = assets.get (Kinder [i]);
          asset && asset.decref (cc_test || oldscene.Autoreleasasets);
      }
      var abhängig = abhängig._Depends.get (oldscene._id);
      if (Abhängigkeiten && Abhängigkeiten.Persistdeps) {
          var persistDeps = Abhängigkeiten.PersistDeps;
          für (sei i = 0, l = persistdeps.length; i <l; i ++) {
              Sei Asset = assets.get (persistDeps [i]);
              asset && asset.decref (cc_test || oldscene.Autoreleasasets);
          }
      }
      abhängigUtil.remove (oldscene._id);
  }
},

3.5.3 Referenzzählung und manuelle Ressourcenfreigabe

Die verbleibenden zwei Möglichkeiten zur Veröffentlichung von Ressourcen dienen im Wesentlichen, Releasemanager zu rufen. Der vollständige Prozess der Ressourcenfreigabe erfolgt ungefähr wie in der folgenden Abbildung:

// ccasset.js reduzieren Referenz Decref (Autorelease) {
  this._ref--;
  autorelease! == false && cc.assetmanager._releasemanager.tryRelease (this);
  gib dies zurück;
}

// ccassetManager.js freisetzt manuell Ressourcen releaSeasSet (Asset) {
  releasemanager.tryRelease (Asset, true);
},

TryRelease unterstützt zwei Modi: Verspätete Veröffentlichung und erzwungene EVENT_AFTER_DRAW . In beiden Fällen wird die Ressource an die _free -Verarbeitungsmethode übergeben, die Folgendes ausführt.

  • Aus _todelete entfernen
  • Wenn Sie ohne Gewalt freigeben, müssen Sie überprüfen, ob es andere Referenzen gibt, und wenn ja, zurückkehren.
  • Aus dem Assets -Cache entfernen
  • Automatisch abhängige Ressourcen freigeben
  • Rufen Sie die Zerstörungsmethode der Ressource an, um die Ressource zu zerstören
  • Entfernen Sie Ressourcenabhängigkeitsaufzeichnungen von Dependutil

Wenn der Rückgabewert von CheckcircularReference größer als 0 ist, wird die Ressourcen von anderen Orten auf alle Orte bezogen, an denen wir hinzufügen. C sind tatsächlich zusammen mit A freizugeben. Wenn die Redewendung einer Ressource abzüglich der Anzahl der internen Referenzen immer noch größer als 1 ist, bedeutet dies, dass sie noch an einem anderen Ort verwiesen wird und nicht veröffentlicht werden kann.

TryRelease (Asset, Force) {
  if (! (Asset Instance von cc.asset)) return;
  if (Kraft) {
      releasemanager._free (Asset, Force);
  }
  anders {
      _todelete.add (asset._uuid, asset);
      // Nach Abschluss der nächsten Direktor -Zeichnung Freeassets ausführen
      if (! EventListener) {
          EventListener = true;
          cc.director.once (cc.director.event_after_draw, freeassets);
      }
  }
}

// Ressourcen_free (Asset, Force) {veröffentlichen {
  _todelete.remove (asset._uuid);

  if (! cc.isvalid (Asset, true)) Return;

  if (! Kraft) {
      if (asset.refcount> 0) {
          // Überprüfen Sie, ob zirkuläre Referenzen innerhalb des Vermögenswerts (checkcircularReference (Asset)> 0) zurückgeben werden. 
      }
  }

  // aus cache assets.remove (asset._uuid) entfernen;
  var abhängig = abhängig.getDeps (asset._uuid);
  für (sei i = 0, l = abhängigs.length; i <l; i ++) {
      var abhängig = assets.get (Abhängig [i]);
      if (abhängig) {
          abhängig.decref (falsch);
          releasemanager._free (abhängig, false);
      }
  }
  Asset.Destroy ();
  abhängigUtil.remove (asset._uuid);
},

// die Ressourcen in _todelete und löschem Funktion freeassets () {freigeben {
  EventListener = false;
  _todelete.foreach (Funktion (Asset) {
      releasemanager._free (Asset);
  });
  _todelete.clear ();
}

Was macht Asset.Destroy? Wie werden Ressourcenobjekte veröffentlicht? Wie werden Ressourcen wie Texturen und Sounds veröffentlicht? Das Asset -Objekt selbst hat keine Zerstörungsmethode, aber das von dem Asset -Objekt geerbte CCObject -Objekt implementiert die ToDestroy . _onPreDestroy Direktor ruft auf, um _destruct _destroyImmediate in jedem Frame auszuführen.

prototype.destroy = function () {
    if (this._objflags & zerstört) {
        CC.Warnid (5000);
        gibt false zurück;
    }
    if (this._objflags & todestroy) {
        gibt false zurück;
    }
    this._objflags | = Todestroy;
    ObjectStodestroy.push (this);

    if (cc_editor && deferredDestrytimer === null && cc.engine &&! cc.engine._isupdating) {
        // kann sofort im Editor -Modus deperredDestryimer = setImmediate (deferredDestroy) zerstört werden;
    }
    gibt true zurück;
};

// Director ruft diese Methode auf jede Frame -Funktion deferredDestroy () {
    var deleteCount = ObjectStodestroy.length;
    für (var i = 0; i <deleteCount; ++ i) {
        var obj = ObjectStodestroy [i];
        if (! (obj._objflags & zerstört)) {
            obj._destroyimmediate ();
        }
    }
    // Wenn wir in A.Destroy B.Destroy nennen, ändert sich die Größe des ObjectStodestroy -Arrays.
        ObjectStodestroy.length = 0;
    }
    anders {
        ObjectStodestroy.SPLICE (0, DeleteCount);
    }

    if (cc_editor) {
        DeferredDestryTimer = null;
    }
}

// Real Resource Release Prototype._Destroyimmediate = function () {
    if (this._objflags & zerstört) {
        CC.Erorid (5000);
        zurückkehren;
    }
    // Callback durchführen if (this._onpredestroy) {
        this._onpredestroy ();
    }

    if (((cc_test?
        this._Destruct ();
    }

    this._objflags | = zerstört;
};

在這里_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 = {};
    für (Schlüssel in Objekt) {
        wenn (obj.hasOwnProperty(Schlüssel)) {
            if (key === idToSkip) {
                weitermachen;
            }
            switch (typeof obj[key]) {
                Fall 'Zeichenfolge':
                    propsToReset[key] = '';
                    brechen;
                case 'object':
                Fall 'Funktion':
                    propsToReset[key] = null;
                    brechen;
            }
        }
    }
    // 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') {
                    weitermachen;
                }
                switch (typeof attrs[attrKey]) {
                    Fall 'Zeichenfolge':
                        propsToReset[key] = '';
                        brechen;
                    case 'object':
                    Fall 'Funktion':
                        propsToReset[key] = null;
                        brechen;
                    case 'undefined':
                        propsToReset[key] = undefined;
                        brechen;
                }
            }
        }
    }

    if (CC_SUPPORT_JIT) {
        // compile code
        var func = '';
        for (key in propsToReset) {
            var statement;
            if (CCClass.IDENTIFIER_RE.test(key)) {
                statement = 'o.' + key + '=';
            }
            anders {
                statement = 'o[' + CCClass.escapeForJS(key) + ']=';
            }
            var val = propsToReset[key];
            if (val === '') {
                val = '""';
            }
            func += (statement + val + ';\n');
        }
        return Function('o', func);
    }
    anders {
        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),以及正在使用的資源被釋放的問題(和內存泄露相反,資源被提前釋放了)。

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

Wie kann dieses Problem gelöst werden?首先是定位出哪些資源出了問題,如果是被提前釋放,我們可以直接定位到這個資源,如果是內存泄露,當我們發現問題時程序往往已經占用了大量的內存,這種情況下可以切換到一個空場景,并清理資源,把資源清理完后,可以檢查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其他相關文章!

Das könnte Sie auch interessieren:
  • Unity3D realisiert die Bewegung des Kameraobjektivs und begrenzt den Winkel
  • Detaillierte Erklärung zur Verwendung mehrerer Timer in CocosCreator
  • CocosCreator - modulares Lernskript
  • So verwenden Sie Verbindungen der Physik-Engine in CocosCreator
  • So verwenden Sie die JSZip-Komprimierung in CocosCreator
  • CocosCreator-Tutorial für den Einstieg: Erstellen Sie Ihr erstes Spiel mit TS
  • Interpretation des CocosCreator-Quellcodes: Engine-Start und Hauptschleife
  • CocosCreator allgemeines Framework-Design Ressourcenmanagement
  • So erstellen Sie eine Liste in CocosCreator
  • So verwenden Sie http und WebSocket in CocosCreator
  • So verwenden Sie cc.follow zur Kameraverfolgung in CocosCreator

<<:  Zwei Möglichkeiten zum Schreiben gespeicherter Prozeduren in MySQL mit und ohne Rückgabewerte

>>:  Schritte für Docker zum Erstellen eines eigenen lokalen Image-Repositorys

Artikel empfehlen

Vue implementiert Div-Rad zum Vergrößern und Verkleinern

Implementieren Sie das Vergrößern und Verkleinern...

Eine kurze Analyse der Crontab-Aufgabenplanung in Linux

1. Erstellen Sie eine Planungsaufgabe Anweisung c...

So verstehen Sie das Ref-Attribut von React genau

Inhaltsverzeichnis Überblick 1. Erstellen eines R...

Ausführliche Erläuterung der HTML-Grundlagen (Teil 1)

1. Das WEB verstehen Webseiten bestehen hauptsäch...

Anwendungsbeispiele für React Hooks

Inhaltsverzeichnis Ein einfaches Komponentenbeisp...

So verwenden Sie die Vue-Timeline-Komponente

In diesem Artikelbeispiel wird der spezifische Im...

CSS - overflow:hidden in Projektbeispielen

Hier sind einige Beispiele, wie ich diese Eigensch...

Ein verbessertes Screenshot- und Freigabetool für Linux: ScreenCloud

ScreenCloud ist eine tolle kleine App, von der Si...

Lösung zum Einfügen eines Formulars mit einer Leerzeile oben und unten

Ich weiß nicht, ob Ihnen beim Erstellen einer Webs...

Eine "klassische" Falle der MySQL UPDATE-Anweisung

Inhaltsverzeichnis 1. Problematische SQL-Anweisun...

5 Möglichkeiten, Ihre JavaScript-Codebasis sauberer zu machen

Inhaltsverzeichnis 1. Verwenden Sie Standardparam...