tileCache.js

/**
 * This class implements a simple cache for tile objects.  Each tile is
 * stored in cache object keyed by a configurable hashing function.  Another
 * array keeps track of last access times for each tile to purge old tiles
 * once the maximum cache size is reached.
 *
 * @class
 * @alias geo.tileCache
 *
 * @param {object} [options] A configuration object for the cache.
 * @param {number} [options.size=64] The maximum number of tiles to store.
 */
var tileCache = function (options) {
  if (!(this instanceof tileCache)) {
    return new tileCache(options);
  }
  options = options || {};
  this._size = options.size || 64;

  /**
   * Get/set the maximum cache size.
   * @property {number} size The maximum cache size.
   * @name geo.tileCache#size
   */
  Object.defineProperty(this, 'size', {
    get: function () { return this._size; },
    set: function (n) {
      while (this._atime.length > n) {
        this.remove(this._atime[this._atime.length - 1]);
      }
      this._size = n;
    }
  });

  /**
   * Get the current cache size.  Read only.
   * @property {number} length The current cache size.
   * @name geo.tileCache#length
   */
  Object.defineProperty(this, 'length', {
    get: function () { return this._atime.length; }
  });

  /**
   * Get the position of the tile in the access queue.
   *
   * @param {string} hash The tile's hash value.
   * @returns {number} The position in the queue or -1.
   */
  this._access = function (hash) {
    return this._atime.indexOf(hash);
  };

  /**
   * Remove a tile from the cache.
   *
   * @param {string|geo.tile} tile The tile or its hash.
   * @returns {boolean} `true` if a tile was removed.
   */
  this.remove = function (tile) {
    var hash = typeof tile === 'string' ? tile : tile.toString();

    // if the tile is not in the cache
    if (!(hash in this._cache)) {
      return false;
    }

    // Remove the tile from the access queue
    this._atime.splice(this._access(hash), 1);

    // Remove the tile from the cache
    delete this._cache[hash];
    return true;
  };

  /**
   * Remove all tiles from the cache.
   *
   * @returns {this}
   */
  this.clear = function () {
    this._cache = {};  // The hash -> tile mapping
    this._atime = [];  // The access queue (the hashes are stored)
    return this;
  };

  /**
   * Get a tile from the cache if it exists, otherwise return `null`.  This
   * method also moves the tile to the front of the access queue.
   *
   * @param {string|geo.tile} hash The tile or the tile hash value.
   * @param {boolean} [noMove] If truthy, don't move the tile to the front of
   *   the access queue.
   * @returns {geo.tile|null}
   */
  this.get = function (hash, noMove) {
    hash = typeof hash === 'string' ? hash : hash.toString();
    if (!(hash in this._cache)) {
      return null;
    }

    if (!noMove) {
      this._atime.splice(this._access(hash), 1);
      this._atime.unshift(hash);
    }
    return this._cache[hash];
  };

  /**
   * Add a tile to the cache.
   *
   * @param {geo.tile} tile The tile to add.
   * @param {function} removeFunc If specified and tiles must be purged from
   *      the cache, call this function on each tile before purging.
   * @param {boolean} noPurge if true, don't purge tiles.
   */
  this.add = function (tile, removeFunc, noPurge) {
    // remove any existing tiles with the same hash
    this.remove(tile);
    var hash = tile.toString();

    // add the tile
    this._cache[hash] = tile;
    this._atime.unshift(hash);

    if (!noPurge) {
      this.purge(removeFunc);
    }
  };

  /**
   * Purge tiles from the cache if it is full.
   *
   * @param {function} removeFunc If specified and tiles must be purged from
   *      the cache, call this function on each tile before purging.
   */
  this.purge = function (removeFunc) {
    var hash;
    while (this._atime.length > this.size) {
      hash = this._atime.pop();
      var tile = this._cache[hash];
      if (removeFunc) {
        removeFunc(tile);
      }
      delete this._cache[hash];
    }
  };

  this.clear();
  return this;
};

module.exports = tileCache;