Pixi.js: Dose pixi.js pode fornecer um carregador paralelo assíncrono?

Criado em 1 fev. 2018  ·  28Comentários  ·  Fonte: pixijs/pixi.js

Olá a todos.
Sou um novo usuário no pixijs.

Aprendi que pixi.loaders.Loader não pode carregar recursos enquanto os outros estão carregando. e se um recurso for carregado, carregar duas vezes lançará uma exceção que não pode ser detectada.

Portanto, ele é capaz de fornecer um carregador paralelo assíncrono que pode armazenar em cache a tarefa de carregamento paralelo e, em seguida, serializar e executá-la?


a seguir está minha implementação simples e feia em um projeto de demonstração.

### implementação da classe resourcesLoader ###

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

  }

}

a seguir está o uso:

paralelamente crie a solicitação de carregamento e serialize, carregue-a e async chame a promessa "then" callback no recurso carregado imediatamente.
e se um recurso for carregado, o "resourcesLoader" resolverá rapidamente essa solicitação sem esperar.
e se um recurso estiver carregando ou aguardando, a mesma solicitação será mesclada com a tarefa de carregamento ou espera.

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

// ........

verificar e ler / carregar recursos rapidamente

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

este simples implemento acima só pode carregar url agora. mas acho que pode ser atualizado para semelhante à API do carregador original.

Então, o pixi.js oficial pode fornecer um carregador paralelo assíncrono como este?

Comentários muito úteis

Sim
Se você deseja usar a versão PIXI com seu próprio middleware, então você deve usar

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

Os carregadores são muito leves, então não se preocupe em ter vários deles.

A única coisa que eu estaria ciente é que os carregadores permitem definir quantos ativos podem ser baixados simultaneamente, o que tem um padrão de 10. Se você tivesse 3 carregadores, poderia efetivamente tentar carregar até 30 recursos em uma vez (embora eu tenha certeza que o navegador limitaria menor do que isso). Então, talvez você crie cada carregador com um limite inferior? Ou talvez seu carregador primário tenha um grande limite para baixar os ativos iniciais, mas seus carregadores de ativos de streaming no jogo têm um limite inferior para se concentrar em baixar cada ativo individual mais rapidamente.

Todos 28 comentários

PixiJS usa um de nossos principais projetos de estimação como contribuidores https://github.com/englercj/resource-loader/

Vamos dar uma olhada nas fontes:

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, vamos dar uma olhada nos documentos, pode ser que o pixi não incluiu os documentos para aquela coisa porque está em outro módulo: http://pixijs.download/dev/docs/PIXI.loaders.Loader.html , aqui está, simultaneidade.

De qualquer forma, há muitas partes do pixi que podem ser feitas melhor para um projeto específico e eu encorajo as pessoas a usarem suas próprias implementações, quando possível, que se adequem ao seu projeto. Eu o abençoo por usar o carregador personalizado, por favor, transforme-o em um plug-in e faremos referência a ele na lista de plug-ins.

Aprendi que o pixi.loaders.Loader não pode carregar recursos enquanto o outro está carregando

Não tenho certeza de onde você aprendeu isso, ele absolutamente pode (e faz) carregar recursos em paralelo. Seu wrapper em torno do carregador de recursos apenas adiciona uma segunda fila assíncrona em cima daquela já usada dentro do carregador de recursos. Ele ainda tem um parâmetro de construtor para configurar quantos recursos carregar simultaneamente de cada vez.

se um recurso for carregado, carregar duas vezes lançará uma exceção que não pode ser detectada.

Você pode detectar o erro, mas não precisa. O objetivo desse erro é dizer que você está usando o carregador de maneira incorreta. O que você deve fazer para o cache é armazenar recursos em algum lugar fora do carregador e usar um middleware .pre() para pular o carregamento dos recursos que estão em cache. Há um exemplo de middleware de cache de memória simples no repo e um exemplo de uso como um middleware .pre() no readme .

Se você planeja usar uma instância do carregador várias vezes, deve chamar .reset() antes de usá-lo novamente. Isso limpará o estado do carregador e ele estará pronto para carregar mais dados. Ele também limpa o objeto .resources (sem exclusão, apenas elimina as referências), portanto, certifique-se de usar ou armazenar os recursos carregados em outro lugar antes de chamar .reset() .

@englercj

mas se você usar o código a seguir para carregar recursos:

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

será o caso:

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

e entendi a razão de onde # 4100, o carregador não pode carregá-lo em paralelo nos recursos raiz.
então, eu acho que talvez possa haver uma maneira de criar em paralelo a tarefa de carregamento nos recursos raiz.

de qualquer maneira, obrigado sua contribuição.

@ivanpopelyshev obrigado ~
Fico feliz em fazer um plugin, e preciso de algumas horas para aprender como fazê-lo.
então, onde posso encontrar os documentos sobre como fazer um plug-in PIXI?

Existem documentos sobre plug-ins de renderização, mas para todo o resto a regra é "faça o que quiser, dê aos usuários o arquivo JS que deve ser incluído após o arquivo vanilla pixi.js".

Pixi é construído com classes, não há variáveis ​​ocultas em contextos ocultos.

@ivanpopelyshev obrigado ~

@ Lyoko-Jeremie você está usando o carregador incorretamente. O uso correto é adicionar todos os recursos que você deseja carregar, _então_ você chama a função load (). Atualmente, você está chamando load após cada adição.

@themoonrat, mas no meu caso, quais recursos precisam ser carregados, não podemos saber antes de carregá-los.
Estou escrevendo uma demonstração como mapas. neste caso, carregue quais imagens de pice dependem do usuário que precisa ver onde.
o usuário pode se mover mais rápido do que a velocidade de carregamento que causará o problema Cannot add resources while the loader is running. .
e não consigo pré-carregar todas as imagens no navegador porque todas as imagens são muito grandes.

@ Lyoko-Jeremie Você pode abusar do mecanismo de recursos dependentes.

Crie um recurso que nunca carregue de fato (o middleware é executado para sempre) e adicione filhos a ele.

O carregador do Spine aguarda o carregamento de dois recursos filhos extras: https://github.com/pixijs/pixi-spine/blob/master/src/loaders.ts#L7 , você pode fazer um middleware que espera para sempre.

Como outra opção, você pode obter recursos e filas do carregador de recursos, mas criar sua própria classe Carregador que armazena recursos de maneira diferente.

@ivanpopelyshev como fazer um recurso que nunca carrega de forma aguda?
Não encontrei esta informação em outro lugar, acho que este aviso precisa ser escrito para o guid para que o novo usuário possa saber disso. porque eu acho que é um recurso anti-consciente.

@ Lyoko-Jeremie

Se você precisa de um carregador personalizado para o seu jogo - você mesmo precisa codificá-lo. É melhor se você recuperar partes que estão ok ou hackear o código existente para economizar tempo, mas para isso você tem que aprender todo o código de 1) resource-loader repo 2) todos os pixi middlewares 3) middlewares avançados (Spine).

Posso responder às suas perguntas depois que você passar algumas horas estudando todo esse código.

Alternativamente, você pode ver como fromImage funciona, é mais fácil, ele usa cache e você pode fazer algo assim.

@ivanpopelyshev Eu gosto de fazer isso, mas talvez não haja tempo para fazer isso.
nos dias de hoje, este invólucro resourcesLoader é o suficiente para este demo.
e tentarei ler o código do carregador no meu tempo livre.
Muito obrigado.

Como uma ideia para outras pessoas com esse problema, uma opção poderia ser ter um pool de carregadores de recursos. Se você precisar carregar um recurso, mas os carregadores existentes já estiverem ocupados, crie um novo carregador e carregue o recurso dessa maneira. Se um carregador existente foi concluído, reinicie-o e reutilize. Em seguida, construa sua própria classe de wrapper para ter uma única função de 'carregar este ativo' e gerenciar os carregadores de recursos em segundo plano

@themoonrat Boa ideia !!!
então, pode criar mais qualquer carregador de recursos sem qualquer outro limite? e não precisa se ligar a nenhum componente do pixi?

Sim
Se você deseja usar a versão PIXI com seu próprio middleware, então você deve usar

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

Os carregadores são muito leves, então não se preocupe em ter vários deles.

A única coisa que eu estaria ciente é que os carregadores permitem definir quantos ativos podem ser baixados simultaneamente, o que tem um padrão de 10. Se você tivesse 3 carregadores, poderia efetivamente tentar carregar até 30 recursos em uma vez (embora eu tenha certeza que o navegador limitaria menor do que isso). Então, talvez você crie cada carregador com um limite inferior? Ou talvez seu carregador primário tenha um grande limite para baixar os ativos iniciais, mas seus carregadores de ativos de streaming no jogo têm um limite inferior para se concentrar em baixar cada ativo individual mais rapidamente.

@themoonrat, obrigado por sua explicação ~~ Eu te amo ~

Isso parece respondido, obrigado a todos por sua resposta.

Acho que no primeiro vou mudar meu carregador de recursos para a versão do pool, e cada carregador carrega uma solicitação. Isso não precisa muitas vezes.

Mas ainda tenho uma dúvida, como pegar o erro do Resource com nome "…" já existe?

Verifique se o recurso com o nome "..." já existe no mesmo loader.resources antes de realmente adicioná-lo.

Novamente, com a natureza do seu projeto (GRANDE MAPA), aconselho você a mudar de atitude, meditar por algum tempo e se preparar para centenas de perguntas como essa.

Pode ser que em alguns dias você comece a pesquisar no fórum sobre "como mostrar um grande mapa" e se depare com minhas incontáveis ​​postagens sobre pixi-tilemap, gráficos, malhas, etc.

UPD. estamos prontos para compartilhar experiências e responder a perguntas muito difíceis

oh ~ bom fórum. obrigado ~

Acho que meu caso talvez tenha algo diferente de um jogo.
toda a imagem é semelhante a um satélite do mundo real, tem tamanho grande (20 MB por imagem) e forma irregular com muitas partes translúcidas, furadas, giratórias e sobrepostas.
Você pode imaginar que este é um google maps falso, com imagens que não são do Tiles, porque em algum motivo ele não pode ser costurado e segmentado no lado do servidor.

então, terei algumas perguntas diferentes sobre isso, mas tentarei encontrar soluções com muito trabalho. (sem saber se chorar ou rir ..)

sem saber se chorar ou rir ..

Sim! Esse é o espírito!

todas as imagens são imagens de satélite do mundo real, têm tamanho grande (20 MB por imagem),

O destino é móvel ou PC? Para PC, você pode usar texturas compactadas (dds + gzip estático no servidor + texturas compactadas pixi).

No celular, você terá um grande atraso quando o pixi fizer upload para o gpu, ainda não temos uploader progressivo. Existem questões sobre isso.

Basicamente, você precisa de uma visão 2x da câmera, e quando a câmera toca a borda do retângulo "preparado", você adiciona mais objetos e coloca os antigos em algum tipo de fila. No momento, o pixi gc descarrega da memória de vídeo qualquer textura que não seja renderizada em cerca de 4 minutos. ( PIXI.settings.GC_MODE , renderer.texture_gc.mode ). Você precisa de sua própria fila de qualquer maneira, porque você armazena carregadores e dados iniciais baixados dentro deles, para serem reutilizados se o usuário mover a câmera para trás.

obrigado pelo seu aviso ~

agora não está no celular, mas quem sabe o futuro imprevisível?

no outro lado. Por alguma razão, muitos usuários do futuro talvez usem o navegador mais antigo que não suporta WebGL. (este motivo você pode não saber se nunca desenvolveu um programa para pessoas da China.)
então, eu também preciso testar o desempenho do canvas quando a demonstração estiver OK e tomar a decisão naquele momento de mostrar ou não uma página banida para usuários mais antigos do navegador e permitir que eles instalem um novo navegador. (Desamparado e com as mãos travadas ...)

e eu tenho um recurso de zoom não fragmentado, que significa zoom ilimitado. (Doge Face ~)

Se você tem certeza sobre o seu público-alvo, faça isso para a tela e teste-o para a tela. Se for possível medir a audiência, se você conhece a origem do tráfego, pode estimar quantas pessoas têm o webgl habilitado em seus navegadores. Se for 99,5%, use apenas webgl. SE for win-xp e IE10, então ... bem ... boa sorte :)

Este tópico foi bloqueado automaticamente, pois não houve nenhuma atividade recente depois que foi fechado. Abra um novo problema para bugs relacionados.

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

st3v0 picture st3v0  ·  3Comentários

neciszhang picture neciszhang  ·  3Comentários

YuryKuvetski picture YuryKuvetski  ·  3Comentários

zcr1 picture zcr1  ·  3Comentários

Makio64 picture Makio64  ·  3Comentários