Pixi.js: Dose pixi.js kann einen asynchronen parallelen Loader bereitstellen?

Erstellt am 1. Feb. 2018  ·  28Kommentare  ·  Quelle: pixijs/pixi.js

Hallo zusammen.
Ich bin ein neuer Benutzer auf pixijs.

Ich habe gelernt, dass die pixi.loaders.Loader Ressourcen nicht laden können, wenn die anderen geladen werden. und wenn eine Ressource geladen wird, wird durch zweimaliges Laden eine Ausnahme ausgelöst, die nicht abgefangen werden kann.

Ist es also in der Lage, einen asynchronen parallelen Loader bereitzustellen, der die parallele Ladeaufgabe zwischenspeichern und dann serialisieren kann, um sie auszuführen?


Folgen ist mein einfaches und hässliches Implementieren auf ein Demo-Projekt.

###die RessourcenLoader-Klasse implementiert###

import * as pixi from "pixi.js";
import * as _ from "lodash";

class ResourcesLoaderParams {
  loaderParams: string | any | any[];
  promises: Promise<PIXI.loaders.Resource>[] = [];
  resolves: ((value?: PIXI.loaders.Resource | PromiseLike<PIXI.loaders.Resource>) => void)[] = [];
  rejects: ((reason?: any) => void)[] = [];
}

export class resourcesLoader {
  constructor(public loader: pixi.loaders.Loader) {
  }

  loaderOptions: pixi.loaders.LoaderOptions;

  waitingList: ResourcesLoaderParams[] = [];
  loadingList: ResourcesLoaderParams[] = [];

  checkExist(urls: string): boolean {
    return !_.isNil(this.loader.resources[urls]);
  }

  get(urls: string) {
    return this.loader.resources[urls];
  }

  checkAndGet(urls: string): Promise<PIXI.loaders.Resource> {
    if (this.checkExist(urls)) {
      return Promise.resolve(this.get(urls));
    }
    return Promise.reject(this.get(urls));
  }

  loadResources(urls: string): Promise<PIXI.loaders.Resource> {
    // trim existed
    if (this.checkExist(urls)) {
      return this.checkAndGet(urls);
    }
    // check is in loading or waiting, then,  merge task
    // otherwise, create new loading task
    let Li = this.waitingList.find(T => T.loaderParams == urls);
    if (_.isNil(Li)) {
      Li = this.loadingList.find(T => T.loaderParams == urls);
    }
    let thisPromise = undefined;
    if (!_.isNil(Li)) {
      thisPromise = new Promise<PIXI.loaders.Resource>((resolve, reject) => {
        Li.resolves.push(resolve);
        Li.rejects.push(reject);
      });
    } else {
      let p = new ResourcesLoaderParams();

      p.loaderParams = urls;
      thisPromise = new Promise<PIXI.loaders.Resource>((resolve, reject) => {
        p.resolves.push(resolve);
        p.rejects.push(reject);
      });
      p.promises.push(thisPromise);

      if (this.waitingList.length == 0 && this.loadingList.length == 0) {
        this.waitingList.push(p);
        this.emitLoader();

      } else {
        this.waitingList.push(p);
      }
    }

    return thisPromise;
  }

  private emitLoader() {
    if (this.waitingList.length === 0) {
      return;
    }

    let list: ResourcesLoaderParams[] = [];

    let tempList = [];
    if (_.isArray(this.waitingList[0].loaderParams)) {
      list = [this.waitingList[0]];
      for (let i = 1; i != this.waitingList.length; ++i) {
        tempList.push(this.waitingList[i]);
      }
    } else {
      // first item confident not array
      let flag = false;
      for (let i = 0; i != this.waitingList.length; ++i) {
        if (!flag) {
          if (_.isArray(this.waitingList[i].loaderParams)) {
            --i;
            flag = true;
            continue;
          }
          list.push(this.waitingList[i]);
        } else {
          tempList.push(this.waitingList[i]);
        }
      }
    }
    this.waitingList = tempList;
    this.loadingList = list;

    // trim the loaded item
    this.loadingList = this.loadingList.filter(T => {
      if (this.checkExist(T.loaderParams)) {
        T.resolves.forEach(Tr => Tr(this.get(T.loaderParams)));
        return false;
      }
      return true;
    });

    if (this.loadingList.length === 0) {
      if (this.waitingList.length !== 0) {
        this.emitLoader();
      }
      return;
    }
    let param: any;
    if (this.loadingList.length === 1) {
      param = this.loadingList[0].loaderParams;
    } else {
      param = this.loadingList.map(T => T.loaderParams);
    }
    let loadingLoader = this.loader.add(param, this.loaderOptions).load(() => {
      this.loadingList.forEach(T => {
        console.log(T.loaderParams);
        T.resolves.forEach(Tr => Tr(this.loader.resources[T.loaderParams]));
      });
      this.loadingList = [];
      this.emitLoader();
    });
    // try catch error,  example double load
    // but seemingly cannot catch it
    loadingLoader.on("error", () => {
      this.loadingList.forEach(T => {
        console.log(T.loaderParams);
        T.rejects.forEach(Tr => Tr(T.loaderParams));
      });
      this.loadingList = [];
      this.emitLoader();
    });
    loadingLoader.onError.once(() => {
      this.loadingList.forEach(T => {
        console.log(T.loaderParams);
        T.rejects.forEach(Tr => Tr(T.loaderParams));
      });
      this.loadingList = [];
      this.emitLoader();
    });

  }

}

folgen ist die verwendung:

parallel Ladeanforderung erstellen und serialisieren Lade es dann asynchron aufrufen Rufen Sie das Promise "dann" Callback bei der sofort geladenen Ressource auf.
und wenn eine Ressource geladen wird, wird der "resourcesLoader" diese Anfrage schnell auflösen, ohne zu warten.
und wenn eine Ressource geladen wird oder wartet, wird dieselbe neue Anforderung mit der Lade- oder Warteaufgabe zusammengeführt.

this.loader = new resourcesLoader(pixi.loader);
this.loader.loadResources("/api/static/1.png").then(T => {
      this.image1 = new pixi.Sprite(T.texture);
    // ......
});
this.loader.loadResources("/api/static/2.png").then(T => {
      this.image2 = new pixi.Sprite(T.texture);
    // ......
});
this.loader.loadResources("/api/static/3.png").then(T => {
      this.image3 = new pixi.Sprite(T.texture);
    // ......
});

// ........

Ressourcen prüfen und schnell lesen/laden

  updateImage(imageUrl: string) {
    this.loader.checkAndGet(imageUrl).catch(E => {
      return this.loader.loadResources(imageUrl);
    }).then(T => {
      this.image = new pixi.Sprite(T.texture);
      // .......
    });
  }

Dieses einfache Gerät oben kann jetzt nur die URL laden. aber ich denke, es kann ähnlich wie die ursprüngliche Loader-API aktualisiert werden.

Kann pixi.js offical also einen asynchronen parallelen Loader wie diesen bereitstellen?

Hilfreichster Kommentar

Ja
Wenn Sie die PIXI-Version mit eigener Middleware verwenden möchten, verwenden Sie

const loader = new PIXI.loaders.Loader();

Lader sind sehr leicht, machen Sie sich also keine Sorgen, mehrere davon zu haben.

Das einzige, was mir bewusst ist, ist, dass Sie mit den Loadern festlegen können, wie viele Assets gleichzeitig heruntergeladen werden können, was einen Standardwert von 10 hat. Wenn Sie 3 Loader hätten, könnten Sie effektiv versuchen, bis zu 30 Ressourcen zu laden einmal (obwohl ich sicher bin, dass der Browser niedriger begrenzen würde). Vielleicht würden Sie also jeden Lader mit einem unteren Limit erstellen? Oder vielleicht hat Ihr primärer Loader ein großes Limit, um die anfänglichen Assets herunterzuladen, aber Ihre 'In-Game-Streaming-Asset-Loader haben ein niedrigeres Limit, um sich darauf zu konzentrieren, jedes einzelne Asset schneller herunterzuladen.

Alle 28 Kommentare

PixiJS verwendet eines unserer wichtigsten Mitwirkenden-Haustierprojekte https://github.com/englercj/resource-loader/

Schauen wir in die Quellen:

https://github.com/englercj/resource-loader/blob/master/src/Loader.js#L20

https://github.com/englercj/resource-loader/blob/master/src/Loader.js#L446

Ok, schauen wir in die Dokumentation, möglicherweise hat pixi keine Dokumentation für dieses Ding eingefügt, weil es in einem anderen Modul ist: http://pixijs.download/dev/docs/PIXI.loaders.Loader.html , hier ist es, Parallelität.

Wie auch immer, es gibt viele Teile von pixi, die für ein bestimmtes Projekt besser gemacht werden können, und ich ermutige die Leute, wenn möglich ihre eigenen Implementierungen zu verwenden, die zu ihrem Projekt passen. Ich segne Sie für die Verwendung des benutzerdefinierten Loaders, bitte machen Sie daraus ein Plugin und wir werden es in der Plugin-Liste referenzieren.

Ich habe erfahren, dass der pixi.loaders.Loader keine Ressourcen laden kann, wenn die anderen laden

Ich bin mir nicht sicher, wo Sie dies gelernt haben, es kann (und tut) absolut Ressourcen parallel laden. Ihr Wrapper um den Ressourcenlader fügt einfach eine zweite asynchrone Warteschlange über die bereits im Ressourcenlader verwendete Warteschlange hinzu. Es hat sogar einen Konstruktorparameter, um zu konfigurieren, wie viele Ressourcen gleichzeitig geladen werden sollen.

Wenn eine Ressource geladen ist, wird durch zweimaliges Laden eine Ausnahme ausgelöst, die nicht abgefangen werden kann.

Sie können den Fehler abfangen, müssen es aber nicht. Der Sinn dieses Fehlers besteht darin, dass Sie den Loader falsch verwenden. Was Sie für das Caching tun sollten, ist, Ressourcen irgendwo außerhalb des Loaders zu speichern und eine .pre() Middleware zu verwenden, um das Laden von zwischengespeicherten Ressourcen zu überspringen. Es gibt ein Beispiel für eine einfache Memory-Caching-Middleware im Repository und ein Beispiel für die Verwendung als .pre() Middleware in der Readme-Datei .

Wenn Sie eine Loader-Instanz mehrmals verwenden möchten, müssen Sie .reset() aufrufen, bevor Sie sie erneut verwenden. Dadurch wird der Status des Loaders gelöscht und er ist bereit, weitere Daten zu laden. Es löscht auch das .resources Objekt (kein Löschen, es werden nur Referenzen gelöscht). Stellen Sie also sicher, dass Sie die geladenen Ressourcen woanders verwendet oder gespeichert haben, bevor Sie .reset() aufrufen.

@englercj

aber wenn Sie folgenden Code verwenden, um Ressourcen zu laden:

    pixi.loader.add("/api/static/1.png")
      .load(() => {
        console.log("/api/static/1.png");
      });
    pixi.loader.add("/api/static/2.png")
      .load(() => {
        console.log("/api/static/2.png");
      });
    pixi.loader.add("/api/static/3.png")
      .load(() => {
        console.log("/api/static/3.png");
      });

es wird der Fall sein:

Error: Uncaught (in promise): Error: Cannot add resources while the loader is running.

und ich bekomme den Grund von #4100 , der Loader kann es nicht parallel auf Root-Ressourcen laden.
Also, ich denke, es kann vielleicht eine Möglichkeit geben, Ladeaufgaben für Root-Ressourcen parallel zu erstellen.

trotzdem danke ihr beitrag.

@ivanpopelyshev danke~
Ich freue mich, ein Plugin zu erstellen, und brauche einige Zeit, um zu lernen, wie es geht.
Also, wo finden Sie die Dokumente zum Erstellen eines PIXI-Plugins?

Es gibt Dokumentationen über Renderer-Plugins, aber für alles andere lautet die Regel "tun Sie, was Sie wollen, geben Sie den Benutzern eine JS-Datei, die nach der Vanilla pixi.js-Datei eingefügt werden soll".

Pixi ist mit Klassen aufgebaut, es gibt keine versteckten Variablen in versteckten Kontexten.

@ivanpopelyshev danke~

@Lyoko-Jeremie Sie verwenden den Loader falsch. Die richtige Verwendung besteht darin, alle Ressourcen hinzuzufügen, die Sie laden möchten, _dann_ rufen Sie die Funktion load() auf. Derzeit rufen Sie Load nach jedem Hinzufügen auf.

@themoonrat, aber in meinem Fall können die Ressourcen, die geladen werden müssen, nicht wissen, bevor sie geladen werden müssen.
Ich schreibe eine Demo wie Karten. Laden Sie in diesem Fall das Bild, das vom Benutzer abhängig ist, der wo sehen muss.
Benutzer bewegen sich möglicherweise schneller als die Ladegeschwindigkeit, die das Problem Cannot add resources while the loader is running. .
und ich kann nicht alle Bilder in den Browser laden, weil das ganze Bild so groß ist.

@Lyoko-Jeremie Sie können abhängige Ressourcenmechanismen missbrauchen.

Erstellen Sie eine Ressource, die nie wirklich geladen wird (Middleware läuft ewig), fügen Sie ihr untergeordnete Elemente hinzu.

Spine Loader wartet auf das Laden von zwei zusätzlichen untergeordneten Ressourcen: https://github.com/pixijs/pixi-spine/blob/master/src/loaders.ts#L7 , können Sie eine Middleware erstellen, die ewig wartet.

Als weitere Option können Sie Ressourcen und Warteschlangen von resource-loader übernehmen, aber Ihre eigene Loader-Klasse erstellen, die Ressourcen anders speichert.

@ivanpopelyshev wie
Ich finde diese Informationen nicht an anderer Stelle, ich denke, diese Warnung muss geschrieben werden, um neue Benutzer darüber zu informieren. weil ich denke, es ist ein anti-bewusstes Feature.

@Lyoko-Jeremie

Wenn Sie einen benutzerdefinierten Loader für Ihr Spiel benötigen, müssen Sie ihn selbst programmieren. Es ist besser, wenn Sie Teile retten, die in Ordnung sind, oder vorhandenen Code hacken, um Zeit zu sparen, aber dafür müssen Sie den gesamten Code aus 1) Resource-Loader-Repo 2) allen Pixi-Middlewares 3) fortgeschrittenen Middlewares (Spine) lernen.

Ich kann Ihre Fragen beantworten, nachdem Sie einige Stunden damit verbracht haben, all diesen Code zu studieren.

Alternativ können Sie sich ansehen, wie fromImage funktioniert, es ist einfacher, es verwendet Cache und Sie könnten so etwas tun.

@ivanpopelyshev Ich mache das gerne, aber vielleicht
Heute reicht dieser Wrapper resourcesLoader für diese Demo.
und ich werde versuchen, den Loader-Code in meiner Freizeit zu lesen.
vielen Dank.

Als Idee für andere, die dieses Problem haben, könnte eine Option darin bestehen, einen Pool von Ressourcenladern zu haben. Wenn Sie eine Ressource laden müssen, aber vorhandene Loader bereits ausgelastet sind, erstellen Sie einen neuen Loader und laden Sie die Ressource auf diese Weise. Wenn ein vorhandener Loader beendet wurde, setzen Sie ihn zurück und verwenden Sie ihn erneut. Erstellen Sie dann Ihre eigene Wrapper-Klasse mit einer einzigen Funktion zum Laden dieses Assets, die die Ressourcenlader im Hintergrund verwaltet

@themoonrat Gute Idee !!!
Kann man also ohne weiteres Limit einen weiteren Ressourcenlader erstellen? und müssen Sie nicht an die Komponente eines Pixis binden?

Ja
Wenn Sie die PIXI-Version mit eigener Middleware verwenden möchten, verwenden Sie

const loader = new PIXI.loaders.Loader();

Lader sind sehr leicht, machen Sie sich also keine Sorgen, mehrere davon zu haben.

Das einzige, was mir bewusst ist, ist, dass Sie mit den Loadern festlegen können, wie viele Assets gleichzeitig heruntergeladen werden können, was einen Standardwert von 10 hat. Wenn Sie 3 Loader hätten, könnten Sie effektiv versuchen, bis zu 30 Ressourcen zu laden einmal (obwohl ich sicher bin, dass der Browser niedriger begrenzen würde). Vielleicht würden Sie also jeden Lader mit einem unteren Limit erstellen? Oder vielleicht hat Ihr primärer Loader ein großes Limit, um die anfänglichen Assets herunterzuladen, aber Ihre 'In-Game-Streaming-Asset-Loader haben ein niedrigeres Limit, um sich darauf zu konzentrieren, jedes einzelne Asset schneller herunterzuladen.

@themoonrat danke für deine Erklärung~~ Ich liebe dich~

Dies scheint beantwortet, danke Ihnen allen für Ihre Antwort.

Ich denke, beim ersten werde ich meinen Ressourcenlader auf die Poolversion ändern, und jeder Lader lädt eine Anforderung. Dies braucht nicht viele Male.

Aber ich habe noch eine Frage, wie kann ich den Fehler abfangen, wenn die Ressource mit dem Namen "…" bereits existiert?

Überprüfen Sie, ob die Ressource mit dem Namen "..." bereits im selben loader.resources bevor Sie sie tatsächlich hinzufügen.

Auch hier rate ich Ihnen mit der Art Ihres Projekts (BIG MAP), die Einstellung zu ändern, einige Zeit zu meditieren und sich auf Hunderte solcher Fragen vorzubereiten.

Es könnte , dass Sie in ein paar Tage sein würden beginnen Forum Suche über und Stolpern über meine unzähligen Beiträge über pixi-tilemap „wie groß Karte zeigen“, Grafiken, Maschen, usw.

UPD. Wir sind bereit, Erfahrungen zu teilen und auf sehr schwierige Fragen zu antworten

oh~ gutes Forum. danke~

Ich denke, mein Fall unterscheidet sich möglicherweise von einem Spiel.
das ganze pice ist ein reales satellitenbild wie ein bild, hat eine große größe (20 MB pro pice) und eine unregelmäßige form mit vielen durchscheinenden, löchern, rotierenden und überlappenden teilen.
Sie können sich vorstellen, dass dies ein gefälschtes Google Maps mit Nicht-Kacheln-Bildbild ist, da es aus irgendeinem Grund nicht auf der Serverseite zusammengefügt und segmentiert werden kann.

Ich werde also eine andere Frage dazu haben, aber ich werde versuchen, mit sehr harter Arbeit Lösungen zu finden. (Nicht wissen, ob ich weinen oder lachen soll..)

nicht wissen, ob ich weinen oder lachen soll..

Jawohl! Das ist der Geist!

Das ganze Bild ist ein echtes Satellitenbild, hat eine große Größe (20 MB pro Stück),

Ist das Ziel ein Handy oder ein PC? Für PC können Sie komprimierte Texturen verwenden (dds + statisches gzip auf dem Server + pixi-komprimierte-Texturen).

Auf Mobilgeräten werden Sie ernsthafte Verzögerungen haben, wenn pixi das in die GPU hochlädt. Wir haben noch keinen progressiven Uploader. Es gibt Probleme damit.

Grundsätzlich benötigen Sie eine 2-fache Ansicht der Kamera, und wenn die Kamera den Rand des "vorbereiteten" Rechtecks ​​berührt, fügen Sie weitere Objekte hinzu und fügen die alten in eine Art Warteschlange ein. Im Moment entlädt pixi gc alle Texturen aus dem Videospeicher, die nicht in 4 Minuten oder so gerendert werden. ( PIXI.settings.GC_MODE , renderer.texture_gc.mode ). Sie benötigen sowieso eine eigene Warteschlange, da Sie darin Loader und anfänglich heruntergeladene Daten speichern, die wiederverwendet werden können, wenn der Benutzer die Kamera zurückbewegt.

danke für deinen Hinweis~

jetzt ist es nicht auf dem Handy, aber wer kennt die unvorhersehbare Zukunft?

auf der anderen Seite. Aus irgendeinem Grund verwenden viele Benutzer der Zukunft möglicherweise den älteren Browser, der WebGL nicht unterstützt. (Aus diesem Grund wissen Sie vielleicht nicht, ob Sie nie ein Programm für Chinesen entwickeln.)
Daher muss ich auch die Leistung des Canvas testen, wenn die Demo in Ordnung ist, und zu diesem Zeitpunkt die Entscheidung treffen, ob älteren Browserbenutzern eine gesperrte Seite angezeigt und ein neuer Browser installiert werden soll. (Hilfslos und festhaltende Hände...)

und ich habe eine Unfragmented Zoom-Funktion, das bedeutet Unlimited Zoom. (Hundegesicht ~)

Wenn Sie sich bei Ihrer Zielgruppe sicher sind, machen Sie es für Canvas und testen Sie es für Canvas. Wenn es möglich ist, das Publikum tatsächlich zu messen, wenn Sie Ihre Traffic-Quelle kennen, können Sie schätzen, wie viele Personen Webgl in ihrem Browser aktiviert haben. Wenn es 99,5 % sind, verwenden Sie nur webgl. WENN es Win-Xp und IE10 ist, dann... nun... viel Glück :)

Dieser Thread wurde automatisch gesperrt, da nach dem Schließen in letzter Zeit keine Aktivität stattgefunden hat. Bitte öffnen Sie eine neue Ausgabe für verwandte Fehler.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

YuryKuvetski picture YuryKuvetski  ·  3Kommentare

samueller picture samueller  ·  3Kommentare

neciszhang picture neciszhang  ·  3Kommentare

finscn picture finscn  ·  3Kommentare

courtneyvigo picture courtneyvigo  ·  3Kommentare