Pixi.js: Может ли pixi.js предоставить асинхронный параллельный загрузчик?

Созданный на 1 февр. 2018  ·  28Комментарии  ·  Источник: pixijs/pixi.js

Всем привет.
Я новый пользователь pixijs.

Я узнал, что pixi.loaders.Loader не может загружать ресурсы, когда загружается другой. и если один ресурс загружен, загрузка дважды вызовет исключение, которое не может быть перехвачено.

Итак, может ли он предоставить асинхронный параллельный загрузчик, который может кэшировать задачу параллельной загрузки, а затем сериализовать ее выполнить?


Вот моя простая и уродливая реализация на демонстрационном проекте.

### реализация класса 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();
    });

  }

}

Ниже приводится использование:

параллельное создание запроса загрузки и сериализация загрузки, затем асинхронный вызов обещания «затем» обратного вызова для ресурса, загруженного немедленно.
и если ресурс будет загружен, «resourcesLoader» быстро разрешит этот запрос без ожидания.
и если ресурс загружается или ожидает, новый запрос будет объединен с задачей загрузки или ожидания.

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

// ........

проверка и быстрое чтение / загрузка ресурсов

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

это простое устройство, показанное выше, может загружать только url-адрес. но я думаю, что его можно обновить до аналогичного исходного API загрузчика.

Итак, может ли pixi.js официально предоставить асинхронный параллельный загрузчик, подобный этому?

Самый полезный комментарий

Yeps
Если вы хотите использовать версию PIXI с собственным промежуточным программным обеспечением, вы должны использовать

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

Загрузчики очень легкие, поэтому не беспокойтесь о том, что их будет несколько.

Единственное, о чем я должен знать, это то, что загрузчики позволяют вам установить, сколько ресурсов они могут загружать одновременно, что по умолчанию равно 10. Если бы у вас было 3 загрузчика, вы могли бы эффективно пытаться загрузить до 30 ресурсов на один раз (хотя я уверен, что браузер ограничит меньше этого). Так, может быть, вы бы создали каждый загрузчик с более низким пределом? Или, может быть, у вашего основного загрузчика есть большой лимит на снижение начальных активов, но у ваших загрузчиков потоковой передачи в игре есть более низкий лимит, чтобы сосредоточиться на более быстром снижении каждого отдельного актива.

Все 28 Комментарий

PixiJS использует один из наших любимых проектов основных участников https://github.com/englercj/resource-loader/

Заглянем в источники:

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

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

Хорошо, давайте посмотрим в документации, возможно, pixi не включил документацию для этой вещи, потому что она находится в другом модуле: http://pixijs.download/dev/docs/PIXI.loaders.Loader.html , вот оно, параллелизм.

В любом случае, есть много частей pixi, которые можно улучшить для конкретного проекта, и я призываю людей использовать свои собственные реализации, когда это возможно, которые подходят их проекту. Благословляю вас за использование пользовательского загрузчика, пожалуйста, превратите его в плагин, и мы будем ссылаться на него в списке плагинов.

Я узнал, что pixi.loaders.Loader не может загружать ресурсы, когда загружается другой

Не уверен, где вы это узнали, он абсолютно может (и загружает) ресурсы параллельно. Ваша оболочка вокруг загрузчика ресурсов просто добавляет вторую асинхронную очередь поверх той, которая уже используется внутри загрузчика ресурсов. У него даже есть параметр конструктора, чтобы настроить, сколько ресурсов загружать одновременно.

если загружен один ресурс, загрузка дважды вызовет исключение, которое невозможно перехватить.

Вы можете поймать ошибку, но вам это не нужно. Смысл этой ошибки в том, чтобы сообщить, что вы неправильно используете загрузчик. Что вам следует делать для кеширования, так это хранить ресурсы где-то вне загрузчика и использовать промежуточное ПО .pre() чтобы пропустить загрузку кэшированных ресурсов. Существует пример кэширования промежуточного простой памяти в репо и пример его использования в качестве .pre() промежуточного слоя в риом .

Если вы планируете использовать экземпляр загрузчика несколько раз, вы должны вызвать .reset() прежде чем использовать его снова. Это очистит состояние загрузчика, и он будет готов к загрузке дополнительных данных. Он также очищает объект .resources (без удаления, просто отбрасывает ссылки), поэтому убедитесь, что вы использовали или сохранили загруженные ресурсы в другом месте, прежде чем вызывать .reset() .

@englercj

но если вы используете следующий код для загрузки ресурсов:

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

это будет:

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

и я понимаю причину, по которой # 4100, загрузчик не может параллельно загружать его на корневые ресурсы.
Итак, я думаю, что у него может быть способ параллельного создания задачи загрузки на корневых ресурсах.

в любом случае, спасибо за ваш вклад.

@ivanpopelyshev спасибо ~
Я рад сделать плагин, и мне нужно время, чтобы научиться это делать.
Итак, где можно найти документы о том, как создать плагин PIXI?

Есть документы о плагинах рендерера, но для всего остального правило таково: «делайте все, что хотите, предоставьте пользователям JS-файл, который будет включен после ванильного файла pixi.js».

Pixi построен с классами, в скрытых контекстах нет скрытых переменных.

@ivanpopelyshev спасибо ~

@ Lyoko-Jeremie, вы неправильно используете загрузчик. Правильное использование - добавить все ресурсы, которые вы хотите загрузить, _ затем_ вы вызываете функцию load (). В настоящее время вы вызываете load после каждого добавления.

@themoonrat, но в моем случае, какие ресурсы нуждаются в загрузке, не могу знать до того, как им понадобится загрузка.
Пишу демку вроде карт. в этом случае загрузка изображения зависит от пользователя, которому нужно видеть где.
пользователь может двигаться быстрее скорости загрузки, что приведет к возникновению проблемы Cannot add resources while the loader is running. .
и я не могу предварительно загрузить все изображение в браузер, потому что все изображение очень большое.

@ Lyoko-Jeremie Вы можете злоупотреблять механизмом зависимых ресурсов.

Сделайте ресурс, который на самом деле никогда не загружается (промежуточное ПО работает вечно), добавьте к нему потомков.

Загрузчик Spine ожидает загрузки двух дополнительных дочерних ресурсов: https://github.com/pixijs/pixi-spine/blob/master/src/loaders.ts#L7 , вы можете создать промежуточное ПО, которое ждет вечно.

В качестве другого варианта вы можете взять ресурс и очередь из загрузчика ресурсов, но создать свой собственный класс Loader, который хранит ресурсы по-другому.

@ivanpopelyshev, как сделать ресурс, который никогда не загружается по факту?
Я не нахожу эту информацию из другого места, я думаю, что это предупреждение нужно написать в guid, чтобы новый пользователь мог знать об этом. потому что я думаю, что это антисознательная особенность.

@ Лиоко-Джереми

Если вам нужен кастомный загрузчик для вашей игры - вам придется его написать самостоятельно. Лучше, если вы спасете части, которые в порядке, или взломаете существующий код, чтобы сэкономить время, но для этого вам нужно изучить весь код из 1) репозитория загрузчика ресурсов 2) всего промежуточного программного обеспечения pixi 3) расширенного промежуточного программного обеспечения (Spine).

Я могу ответить на ваши вопросы после того, как вы потратите несколько часов на изучение всего этого кода.

В качестве альтернативы вы можете посмотреть, как работает fromImage , это проще, он использует кеш, и вы можете сделать что-то в этом роде.

@ivanpopelyshev Я люблю это делать, но, может быть, мне некогда дать это сделать.
в настоящее время для этой демонстрации достаточно этой оболочки resourcesLoader .
а в свободное время постараюсь прочитать код загрузчика.
большое спасибо.

В качестве идеи для других, у которых есть эта проблема, одним из вариантов может быть пул загрузчиков ресурсов. Если вам нужна загрузка ресурса, но существующие загрузчики уже заняты, создайте новый загрузчик и загрузите ресурс таким образом. Если существующий загрузчик закончил работу, сбросьте его и используйте повторно. Затем создайте свой собственный класс-оболочку, чтобы иметь единственную функцию «загрузить этот актив», и она управляет загрузчиками ресурсов в фоновом режиме.

@themoonrat Хорошая идея !!!
Итак, можно ли создать еще загрузчик ресурсов без каких-либо других ограничений? и не нужно связываться с каким-либо компоментом pixi?

Yeps
Если вы хотите использовать версию PIXI с собственным промежуточным программным обеспечением, вы должны использовать

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

Загрузчики очень легкие, поэтому не беспокойтесь о том, что их будет несколько.

Единственное, о чем я должен знать, это то, что загрузчики позволяют вам установить, сколько ресурсов они могут загружать одновременно, что по умолчанию равно 10. Если бы у вас было 3 загрузчика, вы могли бы эффективно пытаться загрузить до 30 ресурсов на один раз (хотя я уверен, что браузер ограничит меньше этого). Так, может быть, вы бы создали каждый загрузчик с более низким пределом? Или, может быть, у вашего основного загрузчика есть большой лимит на снижение начальных активов, но у ваших загрузчиков потоковой передачи в игре есть более низкий лимит, чтобы сосредоточиться на более быстром снижении каждого отдельного актива.

@themoonrat, спасибо за объяснение ~~ Я люблю тебя ~

Кажется, на это есть ответ, спасибо всем за ваш ответ.

Я думаю, что сначала я изменю загрузчик ресурсов на версию пула, и каждый загрузчик будет загружать один запрос. Это не нужно много раз.

Но у меня все еще вопрос, как отловить ошибку ресурса с именем «…» уже существует?

Убедитесь, что ресурс с именем "..." уже существует в том же loader.resources прежде чем вы его добавите.

Опять же, учитывая характер вашего проекта (БОЛЬШАЯ КАРТА), я советую вам изменить отношение, помедитировать некоторое время и подготовиться к сотням подобных вопросов.

Возможно, через несколько дней вы начнете поиск по форуму о том, «как показать большую карту» и наткнетесь на мои бесчисленные сообщения о pixi-tilemap, графике, сетках и т. Д.

UPD. готовы поделиться опытом и ответить на очень сложные вопросы

о ~ хороший форум. спасибо ~

Думаю, мой случай может немного отличаться от игры.
все картинки - это реальные спутниковые изображения, они имеют большой размер (20 МБ на пиксель) и неправильную форму с множеством полупрозрачных, дырчатых, вращающихся и перекрывающихся частей.
Вы можете представить, что это поддельные карты Google, с изображением, отличным от плитки, потому что по какой-то причине он не может сшивать и сегментировать на стороне сервера.

Итак, у меня будет другой вопрос по этому поводу, но я постараюсь найти решения с очень тяжелой работой. (не зная, плакать или смеяться ..)

не зная, плакать или смеяться ..

Да! Это дух!

все картинки - это реальные спутниковые изображения, имеют большой размер (20 МБ на пиксель),

Целевой мобильный телефон или ПК? Для ПК вы можете использовать сжатые текстуры (dds + static gzip на сервере + pixi-compressed-textures).

На мобильных устройствах у вас будет серьезная задержка, когда pixi загрузит это в графический процессор, у нас пока нет прогрессивного загрузчика. Есть вопросы по этому поводу.

По сути, вам нужен двукратный обзор камеры, и когда камера касается края «подготовленного» прямоугольника, вы добавляете больше объектов, а старые помещаете в какую-то очередь. Прямо сейчас pixi gc выгружает из видеопамяти любую текстуру, которая не рендерится за 4 минуты или около того. ( PIXI.settings.GC_MODE , renderer.texture_gc.mode ). В любом случае вам нужна собственная очередь, потому что вы храните в них загрузчики и исходные загруженные данные, которые будут повторно использоваться, если пользователь перемещает камеру назад.

спасибо за ваше уведомление ~

теперь его нет на мобильных устройствах, но кто знает непредсказуемое будущее?

в другую сторону. По какой-то причине многие будущие пользователи могут использовать более старый браузер, не поддерживающий WebGL. (по этой причине вы можете не знать, если никогда не разработаете программу для китайцев.)
Итак, мне также нужно протестировать производительность холста, когда демонстрация в порядке, и принять решение в то время, следует ли показывать запрещенную страницу старым пользователям brwser и позволить им установить новый браузер. (Беспомощно и заглох руки ...)

и у меня есть функция нефрагментированного масштабирования, это означает неограниченное масштабирование. (Лицо дожа ~)

Если вы уверены в своей целевой аудитории, сделайте это для холста и проверьте его для холста. Если реально измерить аудиторию, если вы знаете свой источник трафика, вы можете оценить, сколько людей включили webgl в своих браузерах. Если его 99,5%, используйте только webgl. ЕСЛИ это win-xp и IE10, то ... ну ... удачи :)

Этот поток был автоматически заблокирован, поскольку после его закрытия в последнее время не было никаких действий. Пожалуйста, откройте новую проблему для связанных ошибок.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги