Pixi.js: Dose pixi.js peut fournir un chargeur parallèle asynchrone ?

Créé le 1 févr. 2018  ·  28Commentaires  ·  Source: pixijs/pixi.js

Salut ~ tout le monde.
Je suis un nouvel utilisateur sur Pixijs.

J'ai appris que le pixi.loaders.Loader ne peut pas charger les ressources lorsque l'autre se charge. et si une ressource est chargée, charger deux fois lèvera une exception qui ne peut pas être interceptée.

Alors, est-il capable de fournir un chargeur parallèle asynchrone qui peut mettre en cache la tâche de chargement parallèle puis sérialiser l'exécuter ?


suivre est mon implémentation simple et moche sur un projet de démonstration.

###la classe resourcesLoader implémente###

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();
    });

  }

}

suivre est l'utilisation:

parallèle créer une demande de chargement et sérialiser le charger puis appeler async le rappel de promesse "puis" à la ressource chargée immédiatement.
et si une ressource est chargée, le "resourcesLoader" résoudra rapidement cette demande sans attendre.
et si une ressource est en cours de chargement ou d'attente, la nouvelle même demande fusionnera avec la tâche de chargement ou d'attente.

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);
    // ......
});

// ........

vérifier et lire/charger rapidement les ressources

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

cet outil simple ci-dessus ne peut charger l'URL que maintenant. mais je pense qu'il peut être mis à niveau vers une API similaire à celle du chargeur d'origine.

Alors, pixi.js offical peut-il fournir un chargeur parallèle asynchrone comme celui-ci ?

Commentaire le plus utile

Oui
Si vous souhaitez utiliser la version PIXI avec son propre middleware, vous utiliserez

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

Les chargeurs sont très légers, alors ne vous inquiétez pas d'en avoir plusieurs.

La seule chose dont je sache, c'est que les chargeurs vous permettent de définir le nombre d'actifs qu'il peut télécharger simultanément, ce qui a une valeur par défaut de 10. Si vous aviez 3 chargeurs, vous pourriez effectivement essayer de charger jusqu'à 30 ressources à une fois (mais je suis sûr que le navigateur limiterait plus bas que cela). Alors peut-être que vous créeriez chaque chargeur avec une limite inférieure ? Ou peut-être que votre chargeur principal a une grande limite pour récupérer les actifs initiaux, mais vos chargeurs d'actifs de « streaming en jeu » ont une limite inférieure pour se concentrer sur l'obtention plus rapide de chaque actif individuel.

Tous les 28 commentaires

PixiJS utilise l'un de nos principaux contributeurs Pet Project https://github.com/englercj/resource-loader/

Regardons dans les sources :

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, regardons dans la documentation, peut-être que pixi n'a pas inclus de documentation pour cette chose parce que c'est dans un autre module : http://pixijs.download/dev/docs/PIXI.loaders.Loader.html , le voici, la concurrence.

Quoi qu'il en soit, il existe de nombreuses parties de pixi qui peuvent être améliorées pour un projet spécifique et j'encourage les gens à utiliser leurs propres implémentations lorsque cela est possible, qui conviennent à leur projet. Je vous bénis pour l'utilisation du chargeur personnalisé, veuillez en faire un plugin et nous le référencerons dans la liste des plugins.

J'ai appris que le pixi.loaders.Loader ne peut pas charger les ressources lorsque l'autre se charge

Vous ne savez pas où vous avez appris cela, il peut absolument (et le fait) charger des ressources en parallèle. Votre wrapper autour du chargeur de ressources ajoute simplement une deuxième file d'attente asynchrone en plus de celle déjà utilisée dans le chargeur de ressources. Il a même un paramètre de constructeur pour configurer le nombre de ressources à charger simultanément à la fois.

si une ressource est chargée, charger deux fois lèvera une exception qui ne peut pas être interceptée.

Vous pouvez détecter l'erreur, mais vous n'en avez pas besoin. Le but de cette erreur est de dire que vous utilisez le chargeur de manière incorrecte. Ce que vous devez faire pour la mise en cache est de stocker les ressources quelque part en dehors du chargeur et d'utiliser un middleware .pre() pour ignorer le chargement des ressources mises en cache. Il y a un exemple de middleware de mise en cache mémoire simple .pre() dans le readme .

Si vous prévoyez d'utiliser une instance de chargeur plusieurs fois, vous devez appeler .reset() avant de l'utiliser à nouveau. Cela effacera l'état du chargeur et il sera prêt à charger plus de données. Il efface également l'objet .resources (pas de suppression, supprime simplement les références), alors assurez-vous d'avoir utilisé ou stocké les ressources chargées ailleurs avant d'appeler .reset() .

@englercj

mais si vous utilisez le code suivant pour charger les ressources :

    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");
      });

ça va cas :

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

et j'obtiens la raison d'où #4100 , le chargeur ne peut pas le charger en parallèle sur les ressources racine.
donc, je pense qu'il peut peut-être avoir un moyen de créer en parallèle une tâche de chargement sur les ressources racine.

en tout cas merci de ta contribution

@ivanpopelyshev merci ~
Je suis content de faire un plugin, et j'ai besoin de temps pour apprendre à le faire.
alors, où peut-on trouver les documents sur Comment faire un plugin PIXI ?

Il existe des documents sur les plugins de rendu, mais pour tout le reste, la règle est "faites ce que vous voulez, donnez aux utilisateurs le fichier JS à inclure après le fichier vanilla pixi.js".

Pixi est construit avec des classes, il n'y a pas de variables cachées dans des contextes cachés.

@ivanpopelyshev merci ~

@Lyoko-Jeremie vous utilisez mal le loader. L'utilisation correcte est d'ajouter toutes les ressources que vous souhaitez charger, _puis_ vous appelez la fonction load(). Actuellement, vous appelez load après chaque ajout.

@themoonrat mais dans mon cas, quelles ressources doivent être
J'écris une démo comme des cartes. dans ce cas, chargez quelle image de pièce dépend de l'utilisateur qui a besoin de voir où.
l'utilisateur peut se déplacer plus rapidement que la vitesse de chargement qui causera le problème Cannot add resources while the loader is running. .
et je ne peux pas pré-charger toute l'image dans le navigateur car toute l'image est si grande.

@Lyoko-Jeremie Vous pouvez abuser du mécanisme de ressources dépendantes.

Créez une ressource qui ne se charge jamais réellement (le middleware s'exécute indéfiniment), ajoutez-y des enfants.

Spine loader attend le chargement de deux ressources enfants supplémentaires : https://github.com/pixijs/pixi-spine/blob/master/src/loaders.ts#L7 , vous pouvez créer un middleware qui attend éternellement.

Comme autre option, vous pouvez prendre la ressource et la file d'attente du chargeur de ressources, mais créer votre propre classe Loader qui stocke les ressources différemment.

@ivanpopelyshev comment créer une ressource qui ne se charge jamais réellement ?
Je ne trouve pas cette information ailleurs, je pense que cet avertissement doit être écrit au guide pour que le nouvel utilisateur puisse le savoir. parce que je pense que c'est une fonction anti-consciente.

@Lyoko-Jérémie

Si vous avez besoin d'un chargeur personnalisé pour votre jeu, vous devez le coder vous-même. C'est mieux si vous récupérez des pièces qui sont correctes ou si vous piratez le code existant pour gagner du temps, mais pour cela, vous devez apprendre tout le code à partir de 1) le référentiel du chargeur de ressources 2) tous les middlewares pixi 3) les middlewares avancés (Spine).

Je pourrai répondre à vos questions après que vous ayez passé quelques heures à étudier tout ce code.

Alternativement, vous pouvez regarder comment fonctionne fromImage , c'est plus facile, il utilise le cache et vous pouvez faire quelque chose comme ça.

@ivanpopelyshev J'aime faire ça, mais peut-être pas le temps de me le faire.
de nos jours, ce wrapper resourcesLoader est suffisant pour cette démo.
et j'essaierai de lire le code du chargeur pendant mon temps libre.
Merci beaucoup.

Comme idée pour les autres ayant ce problème, une option pourrait être d'avoir un pool de chargeurs de ressources. Si vous avez besoin d'un chargement de ressource, mais que les chargeurs existants sont déjà occupés, créez un nouveau chargeur et chargez la ressource de cette façon. Si un chargeur existant est terminé, réinitialisez-le et réutilisez-le. Ensuite, créez votre propre classe wrapper pour avoir une seule fonction "charger cet actif", et elle gère les chargeurs de ressources en arrière-plan

@themoonrat Bonne idée !!!
alors, peut-on créer plus de chargeur de ressources sans autre limite ? et vous n'avez pas besoin de vous lier à un composant de pixi ?

Oui
Si vous souhaitez utiliser la version PIXI avec son propre middleware, vous utiliserez

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

Les chargeurs sont très légers, alors ne vous inquiétez pas d'en avoir plusieurs.

La seule chose dont je sache, c'est que les chargeurs vous permettent de définir le nombre d'actifs qu'il peut télécharger simultanément, ce qui a une valeur par défaut de 10. Si vous aviez 3 chargeurs, vous pourriez effectivement essayer de charger jusqu'à 30 ressources à une fois (mais je suis sûr que le navigateur limiterait plus bas que cela). Alors peut-être que vous créeriez chaque chargeur avec une limite inférieure ? Ou peut-être que votre chargeur principal a une grande limite pour récupérer les actifs initiaux, mais vos chargeurs d'actifs de « streaming en jeu » ont une limite inférieure pour se concentrer sur l'obtention plus rapide de chaque actif individuel.

@themoonrat merci pour ton explication~~ Je t'aime~

Cela semble répondu, merci à tous pour votre réponse.

Je pense que sur le premier, je vais changer mon chargeur de ressources en version pool, et chaque chargeur charge une requête. Cela n'a pas besoin de plusieurs fois.

Mais j'ai encore une question, comment attraper l'erreur de Ressource avec le nom "…" existe déjà ?

Vérifiez que la ressource portant le nom "..." existe déjà dans le même loader.resources avant de l'ajouter réellement.

Encore une fois, avec la nature de votre projet (BIG MAP), je vous conseille de changer d'attitude, de méditer un peu et de vous préparer à des centaines de questions comme ça.

Il se peut que dans quelques jours, vous lancez une recherche sur le

UPD. nous sommes prêts à partager notre expérience et à répondre à des questions très difficiles

oh ~ bon forum. merci ~

Je pense que mon cas est peut-être différent d'un jeu.
toute la pièce est une image satellite du monde réel, a une grande taille (20 Mo par pièce) et une forme irrégulière avec de nombreuses parties translucides, trouées, rotatives et se chevauchant.
Vous pouvez imaginer qu'il s'agit d'un faux google maps, avec une image autre que Tiles, car pour une raison quelconque, il ne peut pas assembler et segmenter côté serveur.

donc, j'aurai une question différente à ce sujet, mais je vais essayer de trouver des solutions avec un travail très dur. (ne sachant pas s'il faut pleurer ou rire..)

ne pas savoir s'il faut pleurer ou rire..

Oui! C'est l'idée!

toute la pièce est une image satellite du monde réel, a une grande taille (20 Mo par pièce),

La cible est-elle mobile ou PC ? Pour PC, vous pouvez utiliser des textures compressées (dds + gzip statique sur le serveur + pixi-compressed-textures).

Sur mobile, vous aurez un sérieux décalage lorsque pixi télécharge cela dans gpu, nous n'avons pas encore de téléchargeur progressif. Il y a des problèmes à ce sujet.

Fondamentalement, vous avez besoin d'une vue 2x de la caméra, et lorsque la caméra touche le bord du rectangle "préparé", vous ajoutez plus d'objets et mettez les anciens dans une sorte de file d'attente. À l'heure actuelle, pixi gc décharge de la mémoire vidéo toute texture qui n'est pas rendue en 4 minutes environ. ( PIXI.settings.GC_MODE , renderer.texture_gc.mode ). Vous avez de toute façon besoin de votre propre file d'attente car vous y stockez les chargeurs et les données téléchargées initiales, à réutiliser si l'utilisateur recule la caméra.

merci pour votre avis~

maintenant ce n'est pas sur mobile, mais qui connaît l'avenir imprévisible ?

de l'autre côté. Pour une raison quelconque, de nombreux futurs utilisateurs utilisent peut-être l'ancien navigateur qui ne prend pas en charge WebGL. (c'est pour cette raison que vous ne savez peut-être pas si vous ne développez jamais de programme pour les Chinois.)
donc, je dois également tester les performances du canevas lorsque la démo est OK et prendre la décision à ce moment-là d'afficher ou non une page interdite aux utilisateurs de navigateurs plus anciens et de les laisser installer un nouveau navigateur. (Impuissant et caler les mains...)

et j'ai une fonction de zoom non fragmenté, cela signifie un zoom illimité. (Visage de chien ~)

Si vous êtes sûr de votre public cible, faites-le pour Canvas et testez-le pour Canvas. S'il est possible de mesurer réellement l'audience, si vous connaissez votre source de trafic, vous pouvez estimer combien de personnes ont activé webgl dans leur navigateur. Si c'est 99,5 %, utilisez uniquement webgl. SI c'est win-xp et IE10 alors... eh bien... bonne chance :)

Ce fil a été automatiquement verrouillé car il n'y a eu aucune activité récente après sa fermeture. Veuillez ouvrir un nouveau problème pour les bogues liés.

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

courtneyvigo picture courtneyvigo  ·  3Commentaires

neciszhang picture neciszhang  ·  3Commentaires

madroneropaulo picture madroneropaulo  ·  3Commentaires

st3v0 picture st3v0  ·  3Commentaires

zcr1 picture zcr1  ·  3Commentaires