Show:
/**
 * @module  uid
 */
/*
 * UIDGenerator for multi-instance Entity Component System
 * Generate numeric unique ids for ECS entities. The requirements are:
 *  * generate Numbers for fast comparaison, low storage and bandwidth usage
 *  * generators can be salted so you can use multiple generators with 
 *  uniqueness guaranty
 *  * each salted generator can generate reasonable amount of unique ids
 */

// maximum number of salted generators that can run concurently, once the
// number of allowed generators has been reached the salt of the next 
// generator is silently reset to 0
const MAX_SALTS = 10000;

const MAX_ENTITY_PER_GENERATOR = Math.floor(Number.MAX_SAFE_INTEGER / 
  MAX_SALTS) - 1;
let currentSalt = 0;

/**
 * Generate unique sequences of Numbers. Can be salted (up to 9999 salts)
 * to generate differents ids.
 *
 * To work properly, ECS needs to associate an unique id with each entity. But
 * to preserve efficiency, the unique id must be a Number (more exactly a safe 
 * integer).
 *
 * The basic implementation would be an incremented Number to generate a unique
 * sequence, but this fails when several ecs instances are running and creating
 * entities concurrently (e.g. in a multiplayer networked game). To work around
 * this problem, ecs provide UIDGenerator class which allow you to salt your 
 * generated ids sequence. Two generators with different salts will NEVER 
 * generate the same ids.
 *
 * Currently, there is a maxumum of 9999 salts and about 900719925473 uid per
 * salt. These limits are hard-coded, but I plan to expose these settings in
 * the future.
 * 
 * @class  UIDGenerator
 */
class UIDGenerator {
  /**
   * @constructor
   * @class  UIDGenerator
   * @param  {Number} [salt=0] The salt to use for this generator. Number 
   * between 0 and 9999 (inclusive).
   */
  constructor(salt = 0) {
    /**
     * The salt of this generator.
     * @property {Number} salt
     */
    this.salt = salt;

    /**
     * The counter used to generate unique sequence.
     * @property {Number} uidCount
     */
    this.uidCounter = 0;
  }
  /**
   * Create a new unique id.
   * 
   * @return {Number} An unique id.
   */
  next() {
    let nextUid = this.salt + this.uidCounter * MAX_SALTS;

    // if we exceed the number of maximum entities (which is
    // very high) reset the counter.
    if (++this.uidCounter >= MAX_ENTITY_PER_GENERATOR) {
      this.uidCounter = 0;
    }

    return nextUid;
  }
}

/**
 * @class UID
 */
const UID = {
  /**
   * A reference to UIDGenerator class.
   * 
   * @property {class} UIDGenerator
   */
  UIDGenerator,
  /**
   * The default generator to use if an entity is created without id or generator instance.
   * 
   * @property {UIDGenerator} DefaultUIDGenerator
   */
  DefaultUIDGenerator: new UIDGenerator(currentSalt++),
  /**
   * Return true if the entity id was salted by given salt
   * 
   * @param  {String} entityId Entity id to test
   * @param  {String} salt     Salt to test
   * @return {Boolean}         true if the id was generated by the salt, false
   * otherwise
   */
  isSaltedBy: (entityId, salt) => entityId % MAX_SALTS === salt,
  /**
   * Return the next unique salt.
   *
   * @method  nextSalt
   * @return {Number} A unique salt.
   */
  nextSalt: () => {
    let salt = currentSalt;

    // if we exceed the number of maximum salts, silently reset
    // to 1 (since 0 will always be the default generator)
    if (++currentSalt > MAX_SALTS - 1) {
      currentSalt = 1;
    }

    return salt;
  },
  /**
   * Create a new generator with unique salt.
   *
   * @method  nextGenerator
   * @return {UIDGenerator} The created UIDGenerator.
   */
  nextGenerator: () => new UIDGenerator(UID.nextSalt())
};

export default UID;