Home Reference Source

src/asset-loaders/PackAsset.js

import Asset from '../systems/AssetSystem/Asset';
import AssetSystem from '../systems/AssetSystem';

/**
 * Pack asset loader.
 * It serves also as fetch engine (it's subassets container).
 */
export default class PackAsset extends Asset {

  /**
   * Asset factory.
   *
   * @param {*}	args - Factory parameters.
   *
   * @return {PackAsset} Asset instance.
   *
   * @example
   * system.registerProtocol('pack', PackAsset.factory);
   */
  static factory(...args) {
    return new PackAsset(...args);
  }

  /**
   * @override
   */
  constructor(...args) {
    super(...args);

    this._baseOffset = 0;
    this._descriptor = null;
    this._raw = null;
    this._binaryAsset = null;
  }

  /**
   * @override
   */
  dispose() {
    super.dispose();

    const { _binaryAsset } = this;

    if (!!_binaryAsset) {
      _binaryAsset.dispose();
    }

    this._baseOffset = 0;
    this._descriptor = null;
    this._raw = null;
    this._binaryAsset = null;
  }

  /**
   * @override
   */
  load() {
    const { filename, owner } = this;

    return owner.load(`binary://${filename}`)
      .then(asset => {
        const { data } = asset;
        const view = new DataView(data);
        const descriptorSize = view.getUint32(0);
        const descriptorText = String.fromCharCode.apply(
          null,
          new Uint8Array(data, 4, descriptorSize)
        );
        const descriptor = JSON.parse(descriptorText);

        this._descriptor = descriptor;
        this._baseOffset = descriptorSize + 4;
        this._raw = data;
        this._binaryAsset = asset;
        this.data = {
          descriptor,
          raw: data
        };

        return this;
      });
  }

  /**
   * @override
   */
  fetchSubAsset(path, options, fallbackEngine) {
    try {
      const view = this.entryView(path, true);
      return AssetSystem.fetchArrayView(view, path, options, fallbackEngine);
    } catch(error) {
      return Promise.reject(error);
    }
  }

  /**
   * Tells if pack has subasset of given path.
   *
   * @param {string}	path - Subasset path.
   *
   * @return {boolean} True if entry exists, false otherwise.
   */
  hasEntry(path) {
    return !!this._findEntry(path, this._descriptor);
  }

  /**
   * Gets Array buffer view of given subasset.
   *
   * @param {string}	path - Subasset path.
   * @param {boolean}	noThrow - Tells if function should not throw exceptions on fail.
   *
   * @return {ArrayBufferView|null} Subasset array view if exists or null otherwise.
   */
  entryView(path, noThrow = false) {
    const entry = this._findEntry(path, this._descriptor);
    if (!entry) {
      if (!!noThrow) {
        return null;
      } else {
        throw new Error(`Cannot find entry: ${path}`);
      }
    }
    if (!entry.file) {
      if (!!noThrow) {
        return null;
      } else {
        throw new Error(`Entry is not a file: ${path}`);
      }
    }

    return new Uint8Array(
      this._raw,
      this._baseOffset + entry.offset,
      entry.size
    );
  }

  /**
   * Gets text representation of given subasset.
   *
   * @param {string}	path - Subasset path.
   * @param {boolean}	noThrow - Tells if function should not throw exceptions on fail.
   *
   * @return {string} Text representation of subasset.
   */
  entryText(path, noThrow = false) {
    const view = this.entryView(path, noThrow);
    return String.fromCharCode.apply(null, view);
  }

  _findEntry(path, list) {
    for (const item of list) {
      if (item.path === path) {
        return item;
      } else if (!!item.dir) {
        const found = this._findEntry(path, item.content);

        if (!!found) {
          return found;
        }
      }
    }

    return null;
  }

}