Написание собственного парсера

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

Стандартные парсеры покрывают большинство сценариев: текстуры, спрайт-листы, шрифты, видео, аудио. Однако при работе со специфическими форматами (кастомные уровни, собственные бинарные структуры, проприетарные форматы анимации) требуется написание собственного парсера.

Парсер интегрируется в пайплайн загрузки и автоматически применяется к файлам подходящего типа.


Архитектура системы загрузки

В версии 7 и выше PixiJS использует модуль Assets, пришедший на смену устаревшему Loader. Система состоит из нескольких этапов:

  1. Определение типа ресурса
  2. Загрузка сырого файла
  3. Выбор подходящего парсера
  4. Преобразование данных
  5. Кеширование результата

Парсер представляет собой объект со строго определённым интерфейсом:

{
  extension: { type, priority },
  test(url),
  load(url, options),
  parse(asset, options, loader)
}

Каждый метод выполняет отдельную задачу.


Структура парсера

1. Свойство extension

Описывает тип расширения и приоритет.

extension: {
  type: 'load-parser',
  priority: 10
}
  • type — тип расширения (load-parser, resolve-parser и т.д.)
  • priority — порядок выполнения (чем выше, тем раньше вызывается)

2. Метод test(url)

Определяет, подходит ли парсер для конкретного файла.

test(url)
{
  return url.endsWith('.lvl');
}

Метод должен возвращать true, если файл должен быть обработан этим парсером.


3. Метод load(url, options)

Отвечает за загрузку сырого файла. Можно использовать fetch:

async load(url)
{
  const response = await fetch(url);
  return response.arrayBuffer();
}

Метод возвращает данные в “сыром” виде — строку, JSON, ArrayBuffer и т.д.


4. Метод parse(asset, options, loader)

Преобразует загруженные данные в объект, пригодный для использования в приложении.

async parse(asset)
{
  const view = new DataView(asset);
  
  const width = view.getUint16(0);
  const height = view.getUint16(2);
  
  return {
    width,
    height
  };
}

Возвращаемое значение кешируется и становится результатом Assets.load().


Регистрация парсера

Парсер необходимо зарегистрировать:

import { extensions } from 'pixi.js';

extensions.add(CustomLevelParser);

После регистрации система автоматически будет применять его к подходящим ресурсам.


Пример: парсер пользовательского формата уровня

Предположим, существует бинарный формат .lvl, содержащий:

Смещение Тип Описание
0 Uint16 ширина
2 Uint16 высота
4 Uint8[] массив тайлов

Реализация парсера

const LevelParser = {
  extension: {
    type: 'load-parser',
    priority: 10
  },

  test(url)
  {
    return url.endsWith('.lvl');
  },

  async load(url)
  {
    const response = await fetch(url);
    return response.arrayBuffer();
  },

  async parse(buffer)
  {
    const view = new DataView(buffer);

    const width = view.getUint16(0, true);
    const height = view.getUint16(2, true);

    const tiles = new Uint8Array(buffer, 4);

    return {
      width,
      height,
      tiles
    };
  }
};

Регистрация:

extensions.add(LevelParser);

Загрузка:

const level = await Assets.load('level1.lvl');

console.log(level.width);

Работа с JSON-форматами

Если требуется обработка нестандартного JSON:

const CustomJsonParser = {
  extension: {
    type: 'load-parser',
    priority: 10
  },

  test(url)
  {
    return url.endsWith('.scene.json');
  },

  async load(url)
  {
    const response = await fetch(url);
    return response.json();
  },

  async parse(data)
  {
    return {
      background: data.bg,
      objects: data.entities
    };
  }
};

Такой подход позволяет трансформировать структуру данных под внутренние нужды проекта.


Приоритет парсеров

PixiJS выбирает парсер по:

  1. Результату test
  2. Приоритету priority

Если несколько парсеров возвращают true, применяется тот, у которого выше приоритет.

Это позволяет:

  • переопределять стандартные парсеры
  • расширять существующее поведение
  • внедрять дополнительную обработку

Асинхронная обработка

Метод parse может быть асинхронным:

async parse(asset)
{
  const texture = await Assets.load(asset.texture);
  
  return {
    texture,
    meta: asset.meta
  };
}

Это позволяет строить сложные зависимости между ресурсами.


Доступ к кешу и лоадеру

Метод parse получает третий аргумент — ссылку на загрузчик:

async parse(asset, options, loader)
{
  const cached = loader.resources.get('sharedData');
  
  return {
    asset,
    shared: cached
  };
}

Это полезно при работе со связанными ресурсами (например, несколько файлов, описывающих один спрайт-лист).


Работа с текстурами внутри парсера

Создание текстуры вручную:

import { Texture, BaseTexture } from 'pixi.js';

async parse(buffer)
{
  const blob = new Blob([buffer]);
  const imageBitmap = await createImageBitmap(blob);

  const baseTexture = BaseTexture.from(imageBitmap);
  return new Texture(baseTexture);
}

Парсер может возвращать любой объект, включая:

  • Texture
  • Container
  • пользовательские классы
  • сложные структуры

Кеширование

Результат parse автоматически сохраняется в кеше Assets. Повторный вызов Assets.load() вернёт уже готовый объект.

Удаление из кеша:

Assets.unload('level1.lvl');

Обработка ошибок

Ошибки следует выбрасывать внутри load или parse:

async parse(asset)
{
  if (!asset.signature)
  {
    throw new Error('Invalid level format');
  }

  return asset;
}

PixiJS корректно пробросит исключение в вызывающий код.


Расширение существующих форматов

Возможна обёртка стандартного парсера:

import { spritesheetAsset } from 'pixi.js';

const ExtendedSpritesheetParser = {
  ...spritesheetAsset,

  async parse(asset, options, loader)
  {
    const sheet = await spritesheetAsset.parse(asset, options, loader);
    
    sheet.customData = asset.meta.customField;
    
    return sheet;
  }
};

Регистрация с более высоким приоритетом позволит перехватить стандартное поведение.


Оптимизация

При разработке парсера необходимо учитывать:

  • минимизацию копирования данных
  • использование TypedArray
  • корректную работу с endianness
  • освобождение временных объектов
  • контроль за асинхронными зависимостями

Особенно это критично при загрузке больших бинарных файлов или атласов текстур.


Интеграция с системой бандлов

PixiJS поддерживает группировку ресурсов:

Assets.addBundle('levels', {
  level1: 'level1.lvl',
  level2: 'level2.lvl'
});

При загрузке бандла парсер автоматически применяется к каждому ресурсу.

await Assets.loadBundle('levels');

Тестирование парсера

Рекомендуется проверять:

  • корректность определения формата (test)
  • устойчивость к повреждённым данным
  • поведение при повторной загрузке
  • совместимость с кешированием
  • асинхронные сценарии

Логирование внутри parse упрощает отладку.


Сценарии применения

Собственные парсеры используются для:

  • загрузки карт уровней
  • обработки пользовательских форматов анимации
  • чтения бинарных данных WebGL
  • интеграции с внешними редакторами
  • декодирования сжатых данных

Гибкость архитектуры PixiJS позволяет встроить практически любую систему хранения данных в стандартный пайплайн загрузки, сохраняя единый механизм работы с ресурсами во всём проекте.