<commit_before>import indexById from 'common/indexById';

import ROLES from './ROLES';
import PRIMARY_STATS from './PRIMARY_STATS';

const SPECS = {
  ARCANE_MAGE: {
    id: 62,
    className: 'Mage',
    specName: 'Arcane',
    role: ROLES.DPS.RANGED,
    primaryStat: PRIMARY_STATS.INTELLECT,
    masterySpellId: 190740,
    masteryCoefficient: 1.2, // might be 0.6, Wowhead is unclear
    ranking: {
      class: 4,
      spec: 1,
    },
  },
  FIRE_MAGE: {
    id: 63,
    className: 'Mage',
    specName: 'Fire',
    role: ROLES.DPS.RANGED,
    primaryStat: PRIMARY_STATS.INTELLECT,
    masterySpellId: 12846,
    masteryCoefficient: 0.75,
    ranking: {
      class: 4,
      spec: 2,
    },
  },
  FROST_MAGE: {
    id: 64,
    className: 'Mage',
    specName: 'Frost',
    role: ROLES.DPS.RANGED,
    primaryStat: PRIMARY_STATS.INTELLECT,
    masterySpellId: 76613,
    masteryCoefficient: 2.25,
    ranking: {
      class: 4,
      spec: 3,
    },
  },
  HOLY_PALADIN: {
    id: 65,
    className: 'Paladin',
    specName: 'Holy',
    role: ROLES.HEALER,
    primaryStat: PRIMARY_STATS.INTELLECT,
    masterySpellId: 183997,
    masteryCoefficient: 1.5, // confirmed
    ranking: {
      class: 6,
      spec: 1,
    },
  },
  PROTECTION_PALADIN: {
    id: 66,
    className: 'Paladin',
    specName: 'Protection',
    role: ROLES.TANK,
    primaryStat: PRIMARY_STATS.STRENGTH,
    masterySpellId: 76671,
    masteryCoefficient: 1,
    ranking: {
      class: 6,
      spec: 2,
    },
  },
  RETRIBUTION_PALADIN: {
    id: 70,
    className: 'Paladin',
    specName: 'Retribution',
    role: ROLES.DPS.MELEE,
    primaryStat: PRIMARY_STATS.STRENGTH,
    masterySpellId: 76672,
    masteryCoefficient: 1.75,
    ranking: {
      class: 6,
      spec: 3,
    },
  },
  ARMS_WARRIOR: {
    id: 71,
    className: 'Warrior',
    specName: 'Arms',
    role: ROLES.DPS.MELEE,
    primaryStat: PRIMARY_STATS.STRENGTH,
    masterySpellId: 76838,
    masteryCoefficient: 1.6,
    ranking: {
      class: 11,
      spec: 1,
    },
  },
  FURY_WARRIOR: {
    id: 72,
    className: 'Warrior',
    specName: 'Fury',
    role: ROLES.DPS.MELEE,
    primaryStat: PRIMARY_STATS.STRENGTH,
    masterySpellId: 76856,
    masteryCoefficient: 1.4,
    ranking: {
      class: 11,
      spec: 2,
    },
  },
  PROTECTION_WARRIOR: {
    id: 73,
    className: 'Warrior',
    specName: 'Protection',
    role: ROLES.TANK,
    primaryStat: PRIMARY_STATS.STRENGTH,
    masterySpellId: 76857,
    masteryCoefficient: 1, // or 1.5 or 0.5, unclear which the character panel uses
    ranking: {
      class: 11,
      spec: 3,
    },
  },
  BALANCE_DRUID: {
    id: 102,
    className: 'Druid',
    specName: 'Balance',
    role: ROLES.DPS.RANGED,
    primaryStat: PRIMARY_STATS.INTELLECT,
    masterySpellId: 77492,
    masteryCoefficient: 2.25,
    ranking: {
      class: 2,
      spec: 1,
    },
  },
  FERAL_DRUID: {
    id: 103,
    className: 'Druid',
    specName: 'Feral',
    role: ROLES.DPS.MELEE,
    primaryStat: PRIMARY_STATS.AGILITY,
    masterySpellId: 77493,
    masteryCoefficient: 2,
    ranking: {
      class: 2,
      spec: 2,
    },
  },
  GUARDIAN_DRUID: {
    id: 104,
    className: 'Druid',
    specName: 'Guardian',
    role: ROLES.TANK,
    primaryStat: PRIMARY_STATS.AGILITY,
    masterySpellId: 155783,
    masteryCoefficient: 0.5,
    ranking: {
      class: 2,
      spec: 3,
    },
  },
  RESTORATION_DRUID: {
    id: 105,
    className: 'Druid',
    specName: 'Restoration',
    role: ROLES.HEALER,
    primaryStat: PRIMARY_STATS.INTELLECT,
    masterySpellId: 77495,
    masteryCoefficient: 0.6,
    ranking: {
      class: 2,
      spec: 4,
    },
  },
  BLOOD_DEATH_KNIGHT: {
    id: 250,
    className: 'Death Knight',
    specName: 'Blood',
    role: ROLES.TANK,
    primaryStat: PRIMARY_STATS.STRENGTH,
    masterySpellId: 77513,
    masteryCoefficient: 1.5, // confirmed
    ranking: {
      class: 1,
      spec: 1,
    },
  },
  FROST_DEATH_KNIGHT: {
    id: 251,
    className: 'Death Knight',
    specName: 'Frost',
    role: ROLES.DPS.MELEE,
    primaryStat: PRIMARY_STATS.STRENGTH,
    masterySpellId: 77514,
    masteryCoefficient: 1.5,
    ranking: {
      class: 1,
      spec: 2,
    },
  },
  UNHOLY_DEATH_KNIGHT: {
    id: 252,
    className: 'Death Knight',
    specName: 'Unholy',
    role: ROLES.DPS.MELEE,
    primaryStat: PRIMARY_STATS.STRENGTH,
    masterySpellId: 77515,
    masteryCoefficient: 2.25,
    ranking: {
      class: 1,
      spec: 3,
    },
  },
  BEAST_MASTERY_HUNTER: {
    id: 253,
    className: 'Hunter',
    specName: 'Beast Mastery',
    role: ROLES.DPS.RANGED,
    primaryStat: PRIMARY_STATS.AGILITY,
    masterySpellId: 76657,
    masteryCoefficient: 1.9,
    ranking: {
      class: 3,
      spec: 1,
    },
  },
  MARKSMANSHIP_HUNTER: {
    id: 254,
    className: 'Hunter',
    specName: 'Marksmanship',
    role: ROLES.DPS.RANGED,
    primaryStat: PRIMARY_STATS.AGILITY,
    masterySpellId: 193468,
    masteryCoefficient: 0.625,
    ranking: {
      class: 3,
      spec: 2,
    },
  },
  SURVIVAL_HUNTER: {
    id: 255,
    className: 'Hunter',
    specName: 'Survival',
    role: ROLES.DPS.MELEE,
    primaryStat: PRIMARY_STATS.AGILITY,
    masterySpellId: 191334,
    masteryCoefficient: 1.65,
    ranking: {
      class: 3,
      spec: 3,
    },
  },
  DISCIPLINE_PRIEST: {
    id: 256,
    className: 'Priest',
    specName: 'Discipline',
    role: ROLES.HEALER,
    primaryStat: PRIMARY_STATS.INTELLECT,
    masterySpellId: 77484,
    masteryCoefficient: 1.2,
    ranking: {
      class: 7,
      spec: 1,
    },
  },
  HOLY_PRIEST: {
    id: 257,
    className: 'Priest',
    specName: 'Holy',
    role: ROLES.HEALER,
    primaryStat: PRIMARY_STATS.INTELLECT,
    masterySpellId: 77485,
    masteryCoefficient: 1.25,
    ranking: {
      class: 7,
      spec: 2,
    },
  },
  SHADOW_PRIEST: {
    id: 258,
    className: 'Priest',
    specName: 'Shadow',
    role: ROLES.DPS.RANGED,
    primaryStat: PRIMARY_STATS.INTELLECT,
    masterySpellId: 77486,
    masteryCoefficient: 2.5,
    ranking: {
      class: 7,
      spec: 3,
    },
  },
  ASSASSINATION_ROGUE: {
    id: 259,
    className: 'Rogue',
    specName: 'Assassination',
    role: ROLES.DPS.MELEE,
    primaryStat: PRIMARY_STATS.AGILITY,
    masterySpellId: 76803,
    masteryCoefficient: 4, // might be 3.5 or 2, Wowhead is unclear
    ranking: {
      class: 8,
      spec: 1,
    },
  },
  OUTLAW_ROGUE: {
    id: 260,
    className: 'Rogue',
    specName: 'Outlaw',
    role: ROLES.DPS.MELEE,
    primaryStat: PRIMARY_STATS.AGILITY,
    masterySpellId: 76806,
    masteryCoefficient: 2.2,
    ranking: {
      class: 8,
      spec: 4,
    },
  },
  SUBTLETY_ROGUE: {
    id: 261,
    className: 'Rogue',
    specName: 'Subtlety',
    role: ROLES.DPS.MELEE,
    primaryStat: PRIMARY_STATS.AGILITY,
    masterySpellId: 76808,
    masteryCoefficient: 2.76,
    ranking: {
      class: 8,
      spec: 3,
    },
  },
  ELEMENTAL_SHAMAN: {
    id: 262,
    className: 'Shaman',
    specName: 'Elemental',
    role: ROLES.DPS.RANGED,
    primaryStat: PRIMARY_STATS.INTELLECT,
    masterySpellId: 168534,
    masteryCoefficient: 1.875, // confirmed
    ranking: {
      class: 9,
      spec: 1,
    },
  },
  ENHANCEMENT_SHAMAN: {
    id: 263,
    className: 'Shaman',
    specName: 'Enhancement',
    role: ROLES.DPS.MELEE,
    primaryStat: PRIMARY_STATS.AGILITY,
    masterySpellId: 77223,
    masteryCoefficient: 2,
    ranking: {
      class: 9,
      spec: 2,
    },
  },
  RESTORATION_SHAMAN: {
    id: 264,
    className: 'Shaman',
    specName: 'Restoration',
    role: ROLES.HEALER,
    primaryStat: PRIMARY_STATS.INTELLECT,
    masterySpellId: 77226,
    masteryCoefficient: 3, // confirmed
    ranking: {
      class: 9,
      spec: 3,
    },
  },
  AFFLICTION_WARLOCK: {
    id: 265,
    className: 'Warlock',
    specName: 'Affliction',
    role: ROLES.DPS.RANGED,
    primaryStat: PRIMARY_STATS.INTELLECT,
    masterySpellId: 77215,
    masteryCoefficient: 3.125,
    ranking: {
      class: 10,
      spec: 1,
    },
  },
  DEMONOLOGY_WARLOCK: {
    id: 266,
    className: 'Warlock',
    specName: 'Demonology',
    role: ROLES.DPS.RANGED,
    primaryStat: PRIMARY_STATS.INTELLECT,
    masterySpellId: 77219,
    masteryCoefficient: 1.8,
    ranking: {
      class: 10,
      spec: 2,
    },
  },
  DESTRUCTION_WARLOCK: {
    id: 267,
    className: 'Warlock',
    specName: 'Destruction',
    role: ROLES.DPS.RANGED,
    primaryStat: PRIMARY_STATS.INTELLECT,
    masterySpellId: 77220,
    masteryCoefficient: 2,
    ranking: {
      class: 10,
      spec: 3,
    },
  },
  BREWMASTER_MONK: {
    id: 268,
    className: 'Monk',
    specName: 'Brewmaster',
    role: ROLES.TANK,
    primaryStat: PRIMARY_STATS.AGILITY,
    masterySpellId: 117906,
    masteryCoefficient: 1,
    ranking: {
      class: 5,
      spec: 1,
    },
  },
  WINDWALKER_MONK: {
    id: 269,
    className: 'Monk',
    specName: 'Windwalker',
    role: ROLES.DPS.MELEE,
    primaryStat: PRIMARY_STATS.AGILITY,
    masterySpellId: 115636,
    masteryCoefficient: 1.25,
    ranking: {
      class: 5,
      spec: 3,
    },
  },
  MISTWEAVER_MONK: {
    id: 270,
    className: 'Monk',
    specName: 'Mistweaver',
    role: ROLES.HEALER,
    primaryStat: PRIMARY_STATS.INTELLECT,
    masterySpellId: 117907,
    masteryCoefficient: 13,
    ranking: {
      class: 5,
      spec: 2,
    },
  },
  HAVOC_DEMON_HUNTER: {
    id: 577,
    className: 'Demon Hunter',
    specName: 'Havoc',
    role: ROLES.DPS.MELEE,
    primaryStat: PRIMARY_STATS.AGILITY,
    masterySpellId: 185164,
    masteryCoefficient: 1.5, // or 1
    ranking: {
      class: 12,
      spec: 1,
    },
  },
  VENGEANCE_DEMON_HUNTER: {
    id: 581,
    className: 'Demon Hunter',
    specName: 'Vengeance',
    role: ROLES.TANK,
    primaryStat: PRIMARY_STATS.AGILITY,
    masterySpellId: 203747,
    masteryCoefficient: 0.75, // or 1 or 2.25
    ranking: {
      class: 12,
      spec: 2,
    },
  },
};

export default indexById(SPECS);
<commit_msg>Update mastery coefficients to bfa latest version (8.0.1 25902)<commit_after>import indexById from 'common/indexById';

import ROLES from './ROLES';
import PRIMARY_STATS from './PRIMARY_STATS';

const SPECS = {
  ARCANE_MAGE: {
    id: 62,
    className: 'Mage',
    specName: 'Arcane',
    role: ROLES.DPS.RANGED,
    primaryStat: PRIMARY_STATS.INTELLECT,
    masterySpellId: 190740,
    masteryCoefficient: 1.2, // might be 0.6, Wowhead is unclear
    ranking: {
      class: 4,
      spec: 1,
    },
  },
  FIRE_MAGE: {
    id: 63,
    className: 'Mage',
    specName: 'Fire',
    role: ROLES.DPS.RANGED,
    primaryStat: PRIMARY_STATS.INTELLECT,
    masterySpellId: 12846,
    masteryCoefficient: 0.75,
    ranking: {
      class: 4,
      spec: 2,
    },
  },
  FROST_MAGE: {
    id: 64,
    className: 'Mage',
    specName: 'Frost',
    role: ROLES.DPS.RANGED,
    primaryStat: PRIMARY_STATS.INTELLECT,
    masterySpellId: 76613,
    masteryCoefficient: 1.9, // this is the coefficient for Frozen orb only, the icicle coeff. is 0.0165
    ranking: {
      class: 4,
      spec: 3,
    },
  },
  HOLY_PALADIN: {
    id: 65,
    className: 'Paladin',
    specName: 'Holy',
    role: ROLES.HEALER,
    primaryStat: PRIMARY_STATS.INTELLECT,
    masterySpellId: 183997,
    masteryCoefficient: 1.5, // confirmed
    ranking: {
      class: 6,
      spec: 1,
    },
  },
  PROTECTION_PALADIN: {
    id: 66,
    className: 'Paladin',
    specName: 'Protection',
    role: ROLES.TANK,
    primaryStat: PRIMARY_STATS.STRENGTH,
    masterySpellId: 76671,
    masteryCoefficient: 0.35,
    ranking: {
      class: 6,
      spec: 2,
    },
  },
  RETRIBUTION_PALADIN: {
    id: 70,
    className: 'Paladin',
    specName: 'Retribution',
    role: ROLES.DPS.MELEE,
    primaryStat: PRIMARY_STATS.STRENGTH,
    masterySpellId: 267316,
    masteryCoefficient: 1.6,
    ranking: {
      class: 6,
      spec: 3,
    },
  },
  ARMS_WARRIOR: {
    id: 71,
    className: 'Warrior',
    specName: 'Arms',
    role: ROLES.DPS.MELEE,
    primaryStat: PRIMARY_STATS.STRENGTH,
    masterySpellId: 76838,
    masteryCoefficient: 1.6,
    ranking: {
      class: 11,
      spec: 1,
    },
  },
  FURY_WARRIOR: {
    id: 72,
    className: 'Warrior',
    specName: 'Fury',
    role: ROLES.DPS.MELEE,
    primaryStat: PRIMARY_STATS.STRENGTH,
    masterySpellId: 76856,
    masteryCoefficient: 1.4,
    ranking: {
      class: 11,
      spec: 2,
    },
  },
  PROTECTION_WARRIOR: {
    id: 73,
    className: 'Warrior',
    specName: 'Protection',
    role: ROLES.TANK,
    primaryStat: PRIMARY_STATS.STRENGTH,
    masterySpellId: 76857,
    masteryCoefficient: 1, // or 1.5 or 0.5, unclear which the character panel uses
    ranking: {
      class: 11,
      spec: 3,
    },
  },
  BALANCE_DRUID: {
    id: 102,
    className: 'Druid',
    specName: 'Balance',
    role: ROLES.DPS.RANGED,
    primaryStat: PRIMARY_STATS.INTELLECT,
    masterySpellId: 77492,
    masteryCoefficient: 1.4, // or 0.7 for starsurge & solar empowerment
    ranking: {
      class: 2,
      spec: 1,
    },
  },
  FERAL_DRUID: {
    id: 103,
    className: 'Druid',
    specName: 'Feral',
    role: ROLES.DPS.MELEE,
    primaryStat: PRIMARY_STATS.AGILITY,
    masterySpellId: 77493,
    masteryCoefficient: 2,
    ranking: {
      class: 2,
      spec: 2,
    },
  },
  GUARDIAN_DRUID: {
    id: 104,
    className: 'Druid',
    specName: 'Guardian',
    role: ROLES.TANK,
    primaryStat: PRIMARY_STATS.AGILITY,
    masterySpellId: 155783,
    masteryCoefficient: 0.5,
    ranking: {
      class: 2,
      spec: 3,
    },
  },
  RESTORATION_DRUID: {
    id: 105,
    className: 'Druid',
    specName: 'Restoration',
    role: ROLES.HEALER,
    primaryStat: PRIMARY_STATS.INTELLECT,
    masterySpellId: 77495,
    masteryCoefficient: 0.6,
    ranking: {
      class: 2,
      spec: 4,
    },
  },
  BLOOD_DEATH_KNIGHT: {
    id: 250,
    className: 'Death Knight',
    specName: 'Blood',
    role: ROLES.TANK,
    primaryStat: PRIMARY_STATS.STRENGTH,
    masterySpellId: 77513,
    masteryCoefficient: 2,
    ranking: {
      class: 1,
      spec: 1,
    },
  },
  FROST_DEATH_KNIGHT: {
    id: 251,
    className: 'Death Knight',
    specName: 'Frost',
    role: ROLES.DPS.MELEE,
    primaryStat: PRIMARY_STATS.STRENGTH,
    masterySpellId: 77514,
    masteryCoefficient: 2,
    ranking: {
      class: 1,
      spec: 2,
    },
  },
  UNHOLY_DEATH_KNIGHT: {
    id: 252,
    className: 'Death Knight',
    specName: 'Unholy',
    role: ROLES.DPS.MELEE,
    primaryStat: PRIMARY_STATS.STRENGTH,
    masterySpellId: 77515,
    masteryCoefficient: 2.25,
    ranking: {
      class: 1,
      spec: 3,
    },
  },
  BEAST_MASTERY_HUNTER: {
    id: 253,
    className: 'Hunter',
    specName: 'Beast Mastery',
    role: ROLES.DPS.RANGED,
    primaryStat: PRIMARY_STATS.AGILITY,
    masterySpellId: 76657,
    masteryCoefficient: 1.9,
    ranking: {
      class: 3,
      spec: 1,
    },
  },
  MARKSMANSHIP_HUNTER: {
    id: 254,
    className: 'Hunter',
    specName: 'Marksmanship',
    role: ROLES.DPS.RANGED,
    primaryStat: PRIMARY_STATS.AGILITY,
    masterySpellId: 193468,
    masteryCoefficient: 0.625, // this is coeff. for the range part of the mastery, the damage part is different (1.4)
    ranking: {
      class: 3,
      spec: 2,
    },
  },
  SURVIVAL_HUNTER: {
    id: 255,
    className: 'Hunter',
    specName: 'Survival',
    role: ROLES.DPS.MELEE,
    primaryStat: PRIMARY_STATS.AGILITY,
    masterySpellId: 191334,
    masteryCoefficient: 0.5,
    ranking: {
      class: 3,
      spec: 3,
    },
  },
  DISCIPLINE_PRIEST: {
    id: 256,
    className: 'Priest',
    specName: 'Discipline',
    role: ROLES.HEALER,
    primaryStat: PRIMARY_STATS.INTELLECT,
    masterySpellId: 271534,
    masteryCoefficient: 1.2,
    ranking: {
      class: 7,
      spec: 1,
    },
  },
  HOLY_PRIEST: {
    id: 257,
    className: 'Priest',
    specName: 'Holy',
    role: ROLES.HEALER,
    primaryStat: PRIMARY_STATS.INTELLECT,
    masterySpellId: 77485,
    masteryCoefficient: 1.25,
    ranking: {
      class: 7,
      spec: 2,
    },
  },
  SHADOW_PRIEST: {
    id: 258,
    className: 'Priest',
    specName: 'Shadow',
    role: ROLES.DPS.RANGED,
    primaryStat: PRIMARY_STATS.INTELLECT,
    masterySpellId: 77486,
    masteryCoefficient: 1.2,
    ranking: {
      class: 7,
      spec: 3,
    },
  },
  ASSASSINATION_ROGUE: {
    id: 259,
    className: 'Rogue',
    specName: 'Assassination',
    role: ROLES.DPS.MELEE,
    primaryStat: PRIMARY_STATS.AGILITY,
    masterySpellId: 76803,
    masteryCoefficient: 1.7,
    ranking: {
      class: 8,
      spec: 1,
    },
  },
  OUTLAW_ROGUE: {
    id: 260,
    className: 'Rogue',
    specName: 'Outlaw',
    role: ROLES.DPS.MELEE,
    primaryStat: PRIMARY_STATS.AGILITY,
    masterySpellId: 76806,
    masteryCoefficient: 0.026, // this is only the damage part, the proc chance coef is 1.3
    ranking: {
      class: 8,
      spec: 4,
    },
  },
  SUBTLETY_ROGUE: {
    id: 261,
    className: 'Rogue',
    specName: 'Subtlety',
    role: ROLES.DPS.MELEE,
    primaryStat: PRIMARY_STATS.AGILITY,
    masterySpellId: 76808,
    masteryCoefficient: 2.45, // the periodic damages are modified by a coeff. of 2.76
    ranking: {
      class: 8,
      spec: 3,
    },
  },
  ELEMENTAL_SHAMAN: {
    id: 262,
    className: 'Shaman',
    specName: 'Elemental',
    role: ROLES.DPS.RANGED,
    primaryStat: PRIMARY_STATS.INTELLECT,
    masterySpellId: 168534,
    masteryCoefficient: 1.875, // confirmed
    ranking: {
      class: 9,
      spec: 1,
    },
  },
  ENHANCEMENT_SHAMAN: {
    id: 263,
    className: 'Shaman',
    specName: 'Enhancement',
    role: ROLES.DPS.MELEE,
    primaryStat: PRIMARY_STATS.AGILITY,
    masterySpellId: 77223,
    masteryCoefficient: 2, //proc chance coef. is 0.08
    ranking: {
      class: 9,
      spec: 2,
    },
  },
  RESTORATION_SHAMAN: {
    id: 264,
    className: 'Shaman',
    specName: 'Restoration',
    role: ROLES.HEALER,
    primaryStat: PRIMARY_STATS.INTELLECT,
    masterySpellId: 77226,
    masteryCoefficient: 3, // confirmed
    ranking: {
      class: 9,
      spec: 3,
    },
  },
  AFFLICTION_WARLOCK: {
    id: 265,
    className: 'Warlock',
    specName: 'Affliction',
    role: ROLES.DPS.RANGED,
    primaryStat: PRIMARY_STATS.INTELLECT,
    masterySpellId: 77215,
    masteryCoefficient: 2.5,
    ranking: {
      class: 10,
      spec: 1,
    },
  },
  DEMONOLOGY_WARLOCK: {
    id: 266,
    className: 'Warlock',
    specName: 'Demonology',
    role: ROLES.DPS.RANGED,
    primaryStat: PRIMARY_STATS.INTELLECT,
    masterySpellId: 77219,
    masteryCoefficient: 1.45,
    ranking: {
      class: 10,
      spec: 2,
    },
  },
  DESTRUCTION_WARLOCK: {
    id: 267,
    className: 'Warlock',
    specName: 'Destruction',
    role: ROLES.DPS.RANGED,
    primaryStat: PRIMARY_STATS.INTELLECT,
    masterySpellId: 77220,
    masteryCoefficient: 2, // reduced damage part coef. is 0.666
    ranking: {
      class: 10,
      spec: 3,
    },
  },
  BREWMASTER_MONK: {
    id: 268,
    className: 'Monk',
    specName: 'Brewmaster',
    role: ROLES.TANK,
    primaryStat: PRIMARY_STATS.AGILITY,
    masterySpellId: 117906,
    masteryCoefficient: 3,
    ranking: {
      class: 5,
      spec: 1,
    },
  },
  WINDWALKER_MONK: {
    id: 269,
    className: 'Monk',
    specName: 'Windwalker',
    role: ROLES.DPS.MELEE,
    primaryStat: PRIMARY_STATS.AGILITY,
    masterySpellId: 115636,
    masteryCoefficient: 1.25,
    ranking: {
      class: 5,
      spec: 3,
    },
  },
  MISTWEAVER_MONK: {
    id: 270,
    className: 'Monk',
    specName: 'Mistweaver',
    role: ROLES.HEALER,
    primaryStat: PRIMARY_STATS.INTELLECT,
    masterySpellId: 117907,
    masteryCoefficient: 3,
    ranking: {
      class: 5,
      spec: 2,
    },
  },
  HAVOC_DEMON_HUNTER: {
    id: 577,
    className: 'Demon Hunter',
    specName: 'Havoc',
    role: ROLES.DPS.MELEE,
    primaryStat: PRIMARY_STATS.AGILITY,
    masterySpellId: 185164,
    masteryCoefficient: 1.5, // or 0.6 for run speed coef. part
    ranking: {
      class: 12,
      spec: 1,
    },
  },
  VENGEANCE_DEMON_HUNTER: {
    id: 581,
    className: 'Demon Hunter',
    specName: 'Vengeance',
    role: ROLES.TANK,
    primaryStat: PRIMARY_STATS.AGILITY,
    masterySpellId: 203747,
    masteryCoefficient: 2.5,
    ranking: {
      class: 12,
      spec: 2,
    },
  },
};

export default indexById(SPECS);
<|endoftext|>


<commit_before>import * as PIXI from 'pixi.js';
import RouteNode from './routenode';
import RouteSegment from './routesegment';
import Log from './log';
import LogDeposit from './logdeposit';
import { endpointByStartPointDistanceAndAngle, distance } from './helpers';
import {LogType} from './logtypes'

export default class Level {
  constructor(map) {
    this.map = map;
    this.stage = new PIXI.Container();
    this.routeContainer = new PIXI.Container();
    this.stage.addChild(this.routeContainer);
    this.routeNodes = new Map();
    this.routeSegments = [];
    this.logs = [];
    this.logDeposits = [];
    this.routeTexture = PIXI.Texture.fromImage('/static/road.png');

    if (map) {
      this.parseRouteNodes();
      this.parseLogs();
      this.parseLogDeposits();

      this.generateRouteSegments();
      this.drawRoutes();

      if (map.startseg) {
        this.startingSegment = this.routeSegments[map.startseg];
      } else {
        this.startingSegment = this.routeSegments[0];
      }

      if (map.startinterp) {
        this.startingInterpolation = map.startinterp;
      } else {
        this.startingInterpolation = 0;
      }
    }

  }

  getStage() {
    return this.stage;
  }

  // used by map editor
  getRouteNodes() {
    return this.routeNodes;
  }

  // used by map editor
  getNextRouteNodeId() {
    return this.routeNodes.size + 1;
  }

  drawRoutes() {
    const roadSpriteLength = 50;

    for(var j = 0; j < this.routeSegments.length; ++j) {
      var spos = this.routeSegments[j].startNode.getPos();
      var epos = this.routeSegments[j].endNode.getPos();

      var angle = Math.atan2(epos.y - spos.y, epos.x - spos.x) + Math.PI/2;
      var currentPos = {x: spos.x, y: spos.y};
      var roadSprite;
      var distanceToEnd = 0;
      var tilingSprite = new PIXI.extras.TilingSprite(
        this.routeTexture,
        roadSpriteLength,
        distance(spos, epos)
      );
      tilingSprite.anchor.set(0.5, 0.0);
      tilingSprite.tileScale.set(0.1);
      tilingSprite.rotation = angle + Math.PI;
      tilingSprite.x = currentPos.x;
      tilingSprite.y = currentPos.y;
      this.stage.addChild(tilingSprite);
    }

    for(let [id, routeNode] of this.routeNodes) {
      var intersectionSprite = new PIXI.Sprite.fromImage('./static/road_intersection.png');
      intersectionSprite.anchor.set(0.5, 0.5);
      intersectionSprite.scale.set(0.1);
      intersectionSprite.x = routeNode.getPos().x;
      intersectionSprite.y = routeNode.getPos().y;
      this.stage.addChild(intersectionSprite);
    }
  }

  pastEndPosition(spos, epos, cpos) {
    if ((spos.x < epos.x && cpos.x > epos.x) ||
        (spos.x > epos.x && cpos.x < epos.x) ||
        (spos.y < epos.y && cpos.y > epos.y) ||
        (spos.y > epos.y && cpos.y < epos.y)) {
      return true;
    }
    return false;
  }

  parseRouteNodes() {
    for (var i = 0; i < this.map.routes.length; ++i) {
      var routeNodeData = this.map.routes[i];
      var id = routeNodeData.route_node;
      var pos = {x: routeNodeData.x, y: routeNodeData.y };
      var to = routeNodeData.to;
      this.addRouteNode(id, pos, to);
    }
  }

  addRouteNode(id, pos, to) {
    this.routeNodes.set(id, new RouteNode(id, pos, to));
  }

  // used by map editor
  refreshRoutes() {
    this.routeSegments = [];
    this.routeContainer.removeChildren();

    // reset, mainly to remove segments from them
    for (let [id, node] of this.routeNodes) {
      node.reset(id, node.point, node.to);
    }

    this.generateRouteSegments();
    this.drawRoutes();
  }

  generateRouteSegments() {

    for (let [id, startNode] of this.routeNodes) {
      var endNode = null;
      var segment = null;
      // checking if there is multiple routes from this node
      // if not we just use default settings

      /** REMOVED SUPPORT FOR AUTOMATIC NEXT NODE **/
      /** It was removed because there were some interesting problems with dublicate segments **/
      // if(!startNode.getPos().to && i < this.routeNodes.length-1) {
      //   endNode = this.routeNodes[i+1];
      //   segment = new RouteSegment(startNode, endNode);
      //   this.routeNodes[i].addSegment(segment);
      //   this.routeSegments.push(segment);
      //
      // } else

      // check if node has any waypoints and create segments between them
      if(Array.isArray(startNode.getTo())) {
        for(var j = 0; j < startNode.getTo().length; ++j) {
          var toNodeId = startNode.getTo()[j];
          endNode = this.routeNodes.get(toNodeId);

          // don't add segment twice if already connected
          if (startNode.hasSegmentWithNodes(endNode, startNode))
            continue;

          segment = new RouteSegment(startNode, endNode);
          startNode.addSegment(segment);
          endNode.addSegment(segment);
          this.routeSegments.push(segment);
        }
      }
    }

    // calculating segments for nodes
    // for(i = 0; i < this.routeNodes.length; ++i) {
    //   for(j = 0; j < this.routeSegments.length; ++j) {
    //     if(this.routeNodes[i] === this.routeSegments[j].getPreviousNode() || this.routeNodes[i] === this.routeSegments[j].getNextNode())
    //       this.routeNodes[i].addSegment(this.routeSegments[j]);
    //   }
    // }

    // var previousSegment = null;
    // for (var i = 0; i < this.map.routes.length; i++) {
    //   for (var j = 0; j < this.map.routes[i].length - 1; j++) {
    //     var startPos = this.map.routes[i][j];
    //     var endPos = this.map.routes[i][j + 1];
    //
    //     var segment = new RouteSegment(startPos, endPos, null, previousSegment);
    //     this.routeSegments.push(segment);
    //     if (previousSegment !== null) {
    //       previousSegment.setNext(segment);
    //     }
    //
    //     previousSegment = segment;
    //
    //   }
    // }
  }

  addLog(position, rotation, type) {
    var log = new Log(position, rotation, type, this.stage);
    this.logs.push(log);
  }

  removeLog(log) {
    log.removeFromStage();
    var index = this.logs.indexOf(log);
    if (index !== -1) {
      this.logs.splice(index, 1);
    }
  }

  parseLogs() {
    for (var i = 0; i < this.map.logs.length; ++i) {
      var logData = this.map.logs[i];
      this.addLog({x: logData.x, y: logData.y}, logData.rot, logData.type);
    }
  }

  addDeposit(position, rotation, type) {
    var logDeposit = new LogDeposit(position, rotation, type, this.stage);
    this.logDeposits.push(logDeposit);
  }

  removeDeposit(deposit) {
    deposit.removeFromStage();
    var index = this.logDeposits.indexOf(deposit);
    if (index !== -1) {
      this.logDeposits.splice(index, 1);
    }
  }

  parseLogDeposits() {
    for (var i = 0; i < this.map.logdeposits.length; ++i) {
      var depoData = this.map.logdeposits[i];
      this.addDeposit({x: depoData.x, y: depoData.y}, depoData.rot, depoData.type);
    }
  }

  getRouteSegments() {
    return this.routeSegments;
  }

  setStartingSegment(segment, interp) {
    this.startingSegment = segment;
    this.startingInterpolation = interp;
  }

  getStartingSegment() {
    return this.startingSegment;
  }

  getStaringInterpolation() {
    return this.startingInterpolation;
  }

  getLogs() {
    return this.logs;
  }

  getLogDeposits() {
    return this.logDeposits;
  }

  serialize() {
    var routes = [];
    for (let [id, node] of this.routeNodes) {
      var pos = node.getPos();
      routes.push({route_node: id, x: pos.x, y: pos.y, to: node.to});
    }

    var logs = []
    for (var log of this.logs) {
      var pos = log.getPosition();
      logs.push({x: pos.x, y: pos.y, rot: log.getRotation(), type: log.type});
    }

    var deposits = [];
    for (var deposit of this.logDeposits) {
      var pos = deposit.getPosition();
      deposits.push({x: pos.x, y: pos.y, rot: deposit.getRotation(), type: deposit.type});
    }

    var startingSegmentIdx = 0;
    for (var i = 0; i < this.routeSegments.length; ++i) {
      if (this.routeSegments[i] === this.startingSegment) {
        startingSegmentIdx = i;
        break;
      }
    }

    return {
      startpoint: this.getStartingSegment().getPositionAt(this.startingInterpolation),
      startseg: startingSegmentIdx,
      startinterp: this.startingInterpolation,
      routes: routes,
      logs: logs,
      logdeposits: deposits
    };
  }

  getInfo() {

    var pileTypes = [];
    for (var log of this.logs) {
      for (var pileType of pileTypes) {
        if (pileTypes.type === log.type) {
          pileTypes.amount += 1;
          break;
        }
      }

      pileTypes.push({name: LogType[log.type].name, type: log.type, amount: 1});
    }

    var totalRouteLength = 0;
    for (var segment of this.routeSegments) {
      totalRouteLength += segment.getLength();
    }

    return {
      pileTypes: pileTypes,
      routeLength: Math.round(totalRouteLength),
      storageAreas: this.logDeposits.length,
      passingLimit: false
    };

  }
}
<commit_msg>Level, proper route lenght calc<commit_after>import * as PIXI from 'pixi.js';
import RouteNode from './routenode';
import RouteSegment from './routesegment';
import Log from './log';
import LogDeposit from './logdeposit';
import { endpointByStartPointDistanceAndAngle, distance } from './helpers';
import {LogType} from './logtypes'

export default class Level {
  constructor(map) {
    this.map = map;
    this.stage = new PIXI.Container();
    this.routeContainer = new PIXI.Container();
    this.stage.addChild(this.routeContainer);
    this.routeNodes = new Map();
    this.routeSegments = [];
    this.logs = [];
    this.logDeposits = [];
    this.routeTexture = PIXI.Texture.fromImage('/static/road.png');

    if (map) {
      this.parseRouteNodes();
      this.parseLogs();
      this.parseLogDeposits();

      this.generateRouteSegments();
      this.drawRoutes();

      if (map.startseg) {
        this.startingSegment = this.routeSegments[map.startseg];
      } else {
        this.startingSegment = this.routeSegments[0];
      }

      if (map.startinterp) {
        this.startingInterpolation = map.startinterp;
      } else {
        this.startingInterpolation = 0;
      }
    }

  }

  getStage() {
    return this.stage;
  }

  // used by map editor
  getRouteNodes() {
    return this.routeNodes;
  }

  // used by map editor
  getNextRouteNodeId() {
    return this.routeNodes.size + 1;
  }

  drawRoutes() {
    const roadSpriteLength = 50;

    for(var j = 0; j < this.routeSegments.length; ++j) {
      var spos = this.routeSegments[j].startNode.getPos();
      var epos = this.routeSegments[j].endNode.getPos();

      var angle = Math.atan2(epos.y - spos.y, epos.x - spos.x) + Math.PI/2;
      var currentPos = {x: spos.x, y: spos.y};
      var roadSprite;
      var distanceToEnd = 0;
      var tilingSprite = new PIXI.extras.TilingSprite(
        this.routeTexture,
        roadSpriteLength,
        distance(spos, epos)
      );
      tilingSprite.anchor.set(0.5, 0.0);
      tilingSprite.tileScale.set(0.1);
      tilingSprite.rotation = angle + Math.PI;
      tilingSprite.x = currentPos.x;
      tilingSprite.y = currentPos.y;
      this.stage.addChild(tilingSprite);
    }

    for(let [id, routeNode] of this.routeNodes) {
      var intersectionSprite = new PIXI.Sprite.fromImage('./static/road_intersection.png');
      intersectionSprite.anchor.set(0.5, 0.5);
      intersectionSprite.scale.set(0.1);
      intersectionSprite.x = routeNode.getPos().x;
      intersectionSprite.y = routeNode.getPos().y;
      this.stage.addChild(intersectionSprite);
    }
  }

  pastEndPosition(spos, epos, cpos) {
    if ((spos.x < epos.x && cpos.x > epos.x) ||
        (spos.x > epos.x && cpos.x < epos.x) ||
        (spos.y < epos.y && cpos.y > epos.y) ||
        (spos.y > epos.y && cpos.y < epos.y)) {
      return true;
    }
    return false;
  }

  parseRouteNodes() {
    for (var i = 0; i < this.map.routes.length; ++i) {
      var routeNodeData = this.map.routes[i];
      var id = routeNodeData.route_node;
      var pos = {x: routeNodeData.x, y: routeNodeData.y };
      var to = routeNodeData.to;
      this.addRouteNode(id, pos, to);
    }
  }

  addRouteNode(id, pos, to) {
    this.routeNodes.set(id, new RouteNode(id, pos, to));
  }

  // used by map editor
  refreshRoutes() {
    this.routeSegments = [];
    this.routeContainer.removeChildren();

    // reset, mainly to remove segments from them
    for (let [id, node] of this.routeNodes) {
      node.reset(id, node.point, node.to);
    }

    this.generateRouteSegments();
    this.drawRoutes();
  }

  generateRouteSegments() {

    for (let [id, startNode] of this.routeNodes) {
      var endNode = null;
      var segment = null;
      // checking if there is multiple routes from this node
      // if not we just use default settings

      /** REMOVED SUPPORT FOR AUTOMATIC NEXT NODE **/
      /** It was removed because there were some interesting problems with dublicate segments **/
      // if(!startNode.getPos().to && i < this.routeNodes.length-1) {
      //   endNode = this.routeNodes[i+1];
      //   segment = new RouteSegment(startNode, endNode);
      //   this.routeNodes[i].addSegment(segment);
      //   this.routeSegments.push(segment);
      //
      // } else

      // check if node has any waypoints and create segments between them
      if(Array.isArray(startNode.getTo())) {
        for(var j = 0; j < startNode.getTo().length; ++j) {
          var toNodeId = startNode.getTo()[j];
          endNode = this.routeNodes.get(toNodeId);

          // don't add segment twice if already connected
          if (startNode.hasSegmentWithNodes(endNode, startNode))
            continue;

          segment = new RouteSegment(startNode, endNode);
          startNode.addSegment(segment);
          endNode.addSegment(segment);
          this.routeSegments.push(segment);
        }
      }
    }

    // calculating segments for nodes
    // for(i = 0; i < this.routeNodes.length; ++i) {
    //   for(j = 0; j < this.routeSegments.length; ++j) {
    //     if(this.routeNodes[i] === this.routeSegments[j].getPreviousNode() || this.routeNodes[i] === this.routeSegments[j].getNextNode())
    //       this.routeNodes[i].addSegment(this.routeSegments[j]);
    //   }
    // }

    // var previousSegment = null;
    // for (var i = 0; i < this.map.routes.length; i++) {
    //   for (var j = 0; j < this.map.routes[i].length - 1; j++) {
    //     var startPos = this.map.routes[i][j];
    //     var endPos = this.map.routes[i][j + 1];
    //
    //     var segment = new RouteSegment(startPos, endPos, null, previousSegment);
    //     this.routeSegments.push(segment);
    //     if (previousSegment !== null) {
    //       previousSegment.setNext(segment);
    //     }
    //
    //     previousSegment = segment;
    //
    //   }
    // }
  }

  addLog(position, rotation, type) {
    var log = new Log(position, rotation, type, this.stage);
    this.logs.push(log);
  }

  removeLog(log) {
    log.removeFromStage();
    var index = this.logs.indexOf(log);
    if (index !== -1) {
      this.logs.splice(index, 1);
    }
  }

  parseLogs() {
    for (var i = 0; i < this.map.logs.length; ++i) {
      var logData = this.map.logs[i];
      this.addLog({x: logData.x, y: logData.y}, logData.rot, logData.type);
    }
  }

  addDeposit(position, rotation, type) {
    var logDeposit = new LogDeposit(position, rotation, type, this.stage);
    this.logDeposits.push(logDeposit);
  }

  removeDeposit(deposit) {
    deposit.removeFromStage();
    var index = this.logDeposits.indexOf(deposit);
    if (index !== -1) {
      this.logDeposits.splice(index, 1);
    }
  }

  parseLogDeposits() {
    for (var i = 0; i < this.map.logdeposits.length; ++i) {
      var depoData = this.map.logdeposits[i];
      this.addDeposit({x: depoData.x, y: depoData.y}, depoData.rot, depoData.type);
    }
  }

  getRouteSegments() {
    return this.routeSegments;
  }

  setStartingSegment(segment, interp) {
    this.startingSegment = segment;
    this.startingInterpolation = interp;
  }

  getStartingSegment() {
    return this.startingSegment;
  }

  getStaringInterpolation() {
    return this.startingInterpolation;
  }

  getLogs() {
    return this.logs;
  }

  getLogDeposits() {
    return this.logDeposits;
  }

  serialize() {
    var routes = [];
    for (let [id, node] of this.routeNodes) {
      var pos = node.getPos();
      routes.push({route_node: id, x: pos.x, y: pos.y, to: node.to});
    }

    var logs = []
    for (var log of this.logs) {
      var pos = log.getPosition();
      logs.push({x: pos.x, y: pos.y, rot: log.getRotation(), type: log.type});
    }

    var deposits = [];
    for (var deposit of this.logDeposits) {
      var pos = deposit.getPosition();
      deposits.push({x: pos.x, y: pos.y, rot: deposit.getRotation(), type: deposit.type});
    }

    var startingSegmentIdx = 0;
    for (var i = 0; i < this.routeSegments.length; ++i) {
      if (this.routeSegments[i] === this.startingSegment) {
        startingSegmentIdx = i;
        break;
      }
    }

    return {
      startpoint: this.getStartingSegment().getPositionAt(this.startingInterpolation),
      startseg: startingSegmentIdx,
      startinterp: this.startingInterpolation,
      routes: routes,
      logs: logs,
      logdeposits: deposits
    };
  }

  getInfo() {

    var pileTypes = [];
    for (var log of this.logs) {
      for (var pileType of pileTypes) {
        if (pileTypes.type === log.type) {
          pileTypes.amount += 1;
          break;
        }
      }

      pileTypes.push({name: LogType[log.type].name, type: log.type, amount: 1});
    }

    var totalRouteLength = 0;
    for (var segment of this.routeSegments) {
      totalRouteLength += segment.getLength();
    }

    return {
      pileTypes: pileTypes,
      routeLength: Math.round(totalRouteLength / 10),
      storageAreas: this.logDeposits.length,
      passingLimit: false
    };

  }
}
<|endoftext|>


<commit_before>// Texture management
import Utils from '../utils/utils';
import subscribeMixin from '../utils/subscribe';
import WorkerBroker from '../utils/worker_broker';
import Builders from '../styles/builders';
import log from 'loglevel';

// GL texture wrapper object for keeping track of a global set of textures, keyed by a unique user-defined name
export default class Texture {

    constructor(gl, name, options = {}) {
        this.gl = gl;
        this.texture = gl.createTexture();
        if (this.texture) {
            this.valid = true;
        }
        this.bind();

        this.name = name;
        this.source = null;
        this.source_type = null;
        this.loading = null;    // a Promise object to track the loading state of this texture
        this.filtering = options.filtering;
        this.sprites = options.sprites;
        this.texcoords = {};    // sprite UVs ([0, 1] range)
        this.sizes = {};        // sprite sizes (pixel size)

        // Default to a 1-pixel black texture so we can safely render while we wait for an image to load
        // See: http://stackoverflow.com/questions/19722247/webgl-wait-for-texture-to-load
        this.setData(1, 1, new Uint8Array([0, 0, 0, 255]), { filtering: 'nearest' });

        // Destroy previous texture if present
        if (Texture.textures[this.name]) {
            Texture.textures[this.name].destroy();
        }

        // Cache texture instance and definition
        Texture.textures[this.name] = this;
        Texture.texture_configs[this.name] = Object.assign({ name }, options);

        this.load(options);
        log.trace(`creating Texture ${this.name}`);
    }

    // Destroy a single texture instance
    destroy() {
        if (!this.valid) {
            return;
        }
        this.gl.deleteTexture(this.texture);
        this.texture = null;
        delete this.data;
        this.data = null;
        delete Texture.textures[this.name];
        this.valid = false;
        log.trace(`destroying Texture ${this.name}`);
    }

    bind(unit) {
        if (!this.valid) {
            return;
        }
        if (typeof unit === 'number') {
            if (Texture.activeUnit !== unit) {
                this.gl.activeTexture(this.gl.TEXTURE0 + unit);
                Texture.activeUnit = unit;
            }
        }
        if (Texture.activeTexture !== this.texture) {
            this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);
            Texture.activeTexture = this.texture;
        }
    }

    load(options = {}) {
        if (typeof options.url === 'string') {
            return this.setUrl(options.url, options);
        } else if (options.element instanceof HTMLCanvasElement || options.element instanceof HTMLImageElement) {
            return this.setElement(options.element, options);
        } else if (options.data && options.width && options.height) {
            return this.setData(options.width, options.height, options.data, options);
        }
    }

    // Sets texture from an url
    setUrl(url, options = {}) {
        if (!this.valid) {
            return;
        }

        if (Texture.base_url) {
            url = Utils.addBaseURL(url, Texture.base_url);
        }

        this.source = Utils.cacheBusterForUrl(url);
        this.source_type = 'url';

        this.loading = new Promise((resolve, reject) => {
            let image = new Image();
            image.onload = () => {
                try {
                    this.setElement(image, options);
                    // this.update(options);
                    // this.setTextureFiltering(options);
                    this.calculateSprites();
                }
                catch (e) {
                    log.warn(`Texture: failed to load url: '${url}'`, e, options);
                    Texture.trigger('warning', { message: `Failed to load texture from ${url}`, error: e, texture: options });
                }

                resolve(this);
            };
            image.onerror = e => {
                // Warn and resolve on error
                log.warn(`Texture: failed to load url: '${url}'`, e, options);
                Texture.trigger('warning', { message: `Failed to load texture from ${url}`, error: e, texture: options });
                resolve(this);
            };
            image.crossOrigin = 'anonymous';
            image.src = url;
        });
        return this.loading;
    }

    // Sets texture to a raw image buffer
    setData(width, height, data, options = {}) {
        this.width = width;
        this.height = height;

        this.source = data;
        this.source_type = 'data';

        this.update(options);
        this.setTextureFiltering(options);

        this.loading = Promise.resolve(this);
        return this.loading;
    }

    // Sets the texture to track a element (canvas/image)
    setElement(element, options) {
        this.source = element;
        this.source_type = 'element';

        this.update(options);
        this.setTextureFiltering(options);

        this.loading = Promise.resolve(this);
        return this.loading;
    }

    // Uploads current image or buffer to the GPU (can be used to update animated textures on the fly)
    update(options = {}) {
        if (!this.valid) {
            return;
        }

        this.bind();
        this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, (options.UNPACK_FLIP_Y_WEBGL === false ? false : true));
        this.gl.pixelStorei(this.gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, options.UNPACK_PREMULTIPLY_ALPHA_WEBGL || false);

        // Image or Canvas element
        if ((this.source_type === 'element' && this.source instanceof HTMLCanvasElement) ||
            (this.source_type === 'element' && this.source instanceof HTMLImageElement &&  this.source.complete)) {

            this.width = this.source.width;
            this.height = this.source.height;
            this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, this.source);
        }
        // Raw image buffer
        else if (this.source_type === 'data') { // NOTE: this.data can be null, to zero out texture
            this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.width, this.height, 0, this.gl.RGBA, this.gl.UNSIGNED_BYTE, this.source);
        }

        Texture.trigger('update', this);
    }

    // Determines appropriate filtering mode
    setTextureFiltering(options = {}) {
        if (!this.valid) {
            return;
        }

        options.filtering = options.filtering || this.filtering || 'linear';

        var gl = this.gl;
        this.bind();

        // For power-of-2 textures, the following presets are available:
        // mipmap: linear blend from nearest mip
        // linear: linear blend from original image (no mips)
        // nearest: nearest pixel from original image (no mips, 'blocky' look)
        if (Utils.isPowerOf2(this.width) && Utils.isPowerOf2(this.height)) {
            this.power_of_2 = true;
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, options.TEXTURE_WRAP_S || (options.repeat && gl.REPEAT) || gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, options.TEXTURE_WRAP_T || (options.repeat && gl.REPEAT) || gl.CLAMP_TO_EDGE);

            // gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, options.TEXTURE_WRAP_S || gl.REPEAT);
            // gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, options.TEXTURE_WRAP_T || gl.REPEAT);

            if (options.filtering === 'mipmap') {
                this.filtering = 'mipmap';
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR); // TODO: use trilinear filtering by defualt instead?
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
                gl.generateMipmap(gl.TEXTURE_2D);
            }
            else if (options.filtering === 'linear') {
                this.filtering = 'linear';
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
            }
            else if (options.filtering === 'nearest') {
                this.filtering = 'nearest';
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
            }
        }
        else {
            // WebGL has strict requirements on non-power-of-2 textures:
            // No mipmaps and must clamp to edge
            this.power_of_2 = false;
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);

            if (options.filtering === 'nearest') {
                this.filtering = 'nearest';
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
            }
            else { // default to linear for non-power-of-2 textures
                this.filtering = 'linear';
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
            }
        }

        Texture.trigger('update', this);
    }

    // Pre-calc sprite regions for a texture sprite in UV [0, 1] space
    calculateSprites() {
        if (this.sprites) {
            for (let s in this.sprites) {
                let sprite = this.sprites[s];

                // Map [0, 0] to [1, 1] coords to the appropriate sprite sub-area of the texture
                this.texcoords[s] = Builders.getTexcoordsForSprite(
                    [sprite[0], sprite[1]],
                    [sprite[2], sprite[3]],
                    [this.width, this.height]
                );

                // Pixel size of sprite
                this.sizes[s] = [sprite[2], sprite[3]];
            }
        }
    }

}

// Static/class methods and state

Texture.create = function constructor(gl, name, options) {
    return new Texture(gl, name, options);
};

// Destroy all texture instances for a given GL context
Texture.destroy = function (gl) {
    var textures = Object.keys(Texture.textures);
    for (var t of textures) {
        var texture = Texture.textures[t];
        if (texture.gl === gl) {
            texture.destroy();
        }
    }
};

// Get sprite pixel size and UVs
Texture.getSpriteInfo = function (texname, sprite) {
    let texture = Texture.textures[texname];
    return texture && { size: texture.sizes[sprite], texcoords: texture.texcoords[sprite] };
};

// Create a set of textures keyed in an object
// Optionally load each if it has a URL specified
Texture.createFromObject = function (gl, textures) {
    let loading = [];
    if (textures) {
        for (let texname in textures) {
            let config = textures[texname];

            // If texture already exists and definition hasn't changed, no need to re-create
            // Note: to avoid flicker when other textures/scene items change
            if (!Texture.changed(texname, config)) {
                continue;
            }

            let texture = Texture.create(gl, texname, config);
            loading.push(texture.loading);
        }
    }
    return Promise.all(loading);
};

// Indicate if a texture definition would be a change from the current cache
Texture.changed = function (name, config) {
    if (Texture.textures[name]) { // cached texture
        // compare definitions
        // TODO: fix for canvas/image/data
        if (JSON.stringify(Texture.texture_configs[name]) ===
            JSON.stringify(Object.assign({ name }, config))) {
            return false;
        }
    }
    return true;
};

// Get metadata for a texture by name
// Returns via promise, in case texture is still loading
// Can be called on main thread from worker, to sync texture info to worker
Texture.getInfo = function (name) {
    // Get info for all textures by default
    if (!name) {
        name = Object.keys(Texture.textures);
    }

    // Get multiple textures
    if (Array.isArray(name)) {
        return Promise.all(name.map(n => Texture.getInfo(n)));
    }

    // Get single texture
    var tex = Texture.textures[name];
    if (tex) {
        // Wait for this texture to finish loading, or return immediately
        var loading = tex.loading || Promise.resolve(tex);
        return loading.then(() => {
            // Return a subset of texture info
            // (compatible w/structured cloning, suitable for passing to a worker)
            return {
                name: tex.name,
                width: tex.width,
                height: tex.height,
                sprites: tex.sprites,
                texcoords: tex.texcoords,
                sizes: tex.sizes,
                filtering: tex.filtering,
                power_of_2: tex.power_of_2,
                valid: tex.valid
            };
        });
    }
    else {
        // No texture found
        return Promise.resolve(null);
    }
};

// Sync texture info to worker
// Called from worker, gets info on one or more textures info from main thread via remote call, then stores it
// locally in worker. 'textures' can be an array of texture names to sync, or if null, all textures are synced.
Texture.syncTexturesToWorker = function (names) {
    return WorkerBroker.postMessage('Texture.getInfo', names).
        then(textures => {
            for (var tex of textures) {
                Texture.textures[tex.name] = tex;
            }
            return Texture.textures;
        });
};

// Report max texture size for a GL context
Texture.getMaxTextureSize = function (gl) {
    return gl.getParameter(gl.MAX_TEXTURE_SIZE);
};

// Global set of textures, by name
Texture.textures = {};
Texture.texture_configs = {};
Texture.boundTexture = -1;
Texture.activeUnit = -1;

Texture.base_url = null; // optional base URL to add to textures

WorkerBroker.addTarget('Texture', Texture);
subscribeMixin(Texture);
<commit_msg>canvas/image-based textures are considered dynamic and always refresh<commit_after>// Texture management
import Utils from '../utils/utils';
import subscribeMixin from '../utils/subscribe';
import WorkerBroker from '../utils/worker_broker';
import Builders from '../styles/builders';
import log from 'loglevel';

// GL texture wrapper object for keeping track of a global set of textures, keyed by a unique user-defined name
export default class Texture {

    constructor(gl, name, options = {}) {
        this.gl = gl;
        this.texture = gl.createTexture();
        if (this.texture) {
            this.valid = true;
        }
        this.bind();

        this.name = name;
        this.source = null;
        this.source_type = null;
        this.loading = null;    // a Promise object to track the loading state of this texture
        this.filtering = options.filtering;
        this.sprites = options.sprites;
        this.texcoords = {};    // sprite UVs ([0, 1] range)
        this.sizes = {};        // sprite sizes (pixel size)

        // Default to a 1-pixel black texture so we can safely render while we wait for an image to load
        // See: http://stackoverflow.com/questions/19722247/webgl-wait-for-texture-to-load
        this.setData(1, 1, new Uint8Array([0, 0, 0, 255]), { filtering: 'nearest' });

        // Destroy previous texture if present
        if (Texture.textures[this.name]) {
            Texture.textures[this.name].destroy();
        }

        // Cache texture instance and definition
        Texture.textures[this.name] = this;
        Texture.texture_configs[this.name] = Object.assign({ name }, options);

        this.load(options);
        log.trace(`creating Texture ${this.name}`);
    }

    // Destroy a single texture instance
    destroy() {
        if (!this.valid) {
            return;
        }
        this.gl.deleteTexture(this.texture);
        this.texture = null;
        delete this.data;
        this.data = null;
        delete Texture.textures[this.name];
        this.valid = false;
        log.trace(`destroying Texture ${this.name}`);
    }

    bind(unit) {
        if (!this.valid) {
            return;
        }
        if (typeof unit === 'number') {
            if (Texture.activeUnit !== unit) {
                this.gl.activeTexture(this.gl.TEXTURE0 + unit);
                Texture.activeUnit = unit;
            }
        }
        if (Texture.activeTexture !== this.texture) {
            this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);
            Texture.activeTexture = this.texture;
        }
    }

    load(options = {}) {
        if (typeof options.url === 'string') {
            return this.setUrl(options.url, options);
        } else if (options.element instanceof HTMLCanvasElement || options.element instanceof HTMLImageElement) {
            return this.setElement(options.element, options);
        } else if (options.data && options.width && options.height) {
            return this.setData(options.width, options.height, options.data, options);
        }
    }

    // Sets texture from an url
    setUrl(url, options = {}) {
        if (!this.valid) {
            return;
        }

        if (Texture.base_url) {
            url = Utils.addBaseURL(url, Texture.base_url);
        }

        this.source = Utils.cacheBusterForUrl(url);
        this.source_type = 'url';

        this.loading = new Promise((resolve, reject) => {
            let image = new Image();
            image.onload = () => {
                try {
                    this.setElement(image, options);
                    // this.update(options);
                    // this.setTextureFiltering(options);
                    this.calculateSprites();
                }
                catch (e) {
                    log.warn(`Texture: failed to load url: '${url}'`, e, options);
                    Texture.trigger('warning', { message: `Failed to load texture from ${url}`, error: e, texture: options });
                }

                resolve(this);
            };
            image.onerror = e => {
                // Warn and resolve on error
                log.warn(`Texture: failed to load url: '${url}'`, e, options);
                Texture.trigger('warning', { message: `Failed to load texture from ${url}`, error: e, texture: options });
                resolve(this);
            };
            image.crossOrigin = 'anonymous';
            image.src = url;
        });
        return this.loading;
    }

    // Sets texture to a raw image buffer
    setData(width, height, data, options = {}) {
        this.width = width;
        this.height = height;

        this.source = data;
        this.source_type = 'data';

        this.update(options);
        this.setTextureFiltering(options);

        this.loading = Promise.resolve(this);
        return this.loading;
    }

    // Sets the texture to track a element (canvas/image)
    setElement(element, options) {
        this.source = element;
        this.source_type = 'element';

        this.update(options);
        this.setTextureFiltering(options);

        this.loading = Promise.resolve(this);
        return this.loading;
    }

    // Uploads current image or buffer to the GPU (can be used to update animated textures on the fly)
    update(options = {}) {
        if (!this.valid) {
            return;
        }

        this.bind();
        this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, (options.UNPACK_FLIP_Y_WEBGL === false ? false : true));
        this.gl.pixelStorei(this.gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, options.UNPACK_PREMULTIPLY_ALPHA_WEBGL || false);

        // Image or Canvas element
        if ((this.source_type === 'element' && this.source instanceof HTMLCanvasElement) ||
            (this.source_type === 'element' && this.source instanceof HTMLImageElement &&  this.source.complete)) {

            this.width = this.source.width;
            this.height = this.source.height;
            this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, this.source);
        }
        // Raw image buffer
        else if (this.source_type === 'data') { // NOTE: this.data can be null, to zero out texture
            this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.width, this.height, 0, this.gl.RGBA, this.gl.UNSIGNED_BYTE, this.source);
        }

        Texture.trigger('update', this);
    }

    // Determines appropriate filtering mode
    setTextureFiltering(options = {}) {
        if (!this.valid) {
            return;
        }

        options.filtering = options.filtering || this.filtering || 'linear';

        var gl = this.gl;
        this.bind();

        // For power-of-2 textures, the following presets are available:
        // mipmap: linear blend from nearest mip
        // linear: linear blend from original image (no mips)
        // nearest: nearest pixel from original image (no mips, 'blocky' look)
        if (Utils.isPowerOf2(this.width) && Utils.isPowerOf2(this.height)) {
            this.power_of_2 = true;
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, options.TEXTURE_WRAP_S || (options.repeat && gl.REPEAT) || gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, options.TEXTURE_WRAP_T || (options.repeat && gl.REPEAT) || gl.CLAMP_TO_EDGE);

            // gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, options.TEXTURE_WRAP_S || gl.REPEAT);
            // gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, options.TEXTURE_WRAP_T || gl.REPEAT);

            if (options.filtering === 'mipmap') {
                this.filtering = 'mipmap';
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR); // TODO: use trilinear filtering by defualt instead?
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
                gl.generateMipmap(gl.TEXTURE_2D);
            }
            else if (options.filtering === 'linear') {
                this.filtering = 'linear';
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
            }
            else if (options.filtering === 'nearest') {
                this.filtering = 'nearest';
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
            }
        }
        else {
            // WebGL has strict requirements on non-power-of-2 textures:
            // No mipmaps and must clamp to edge
            this.power_of_2 = false;
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);

            if (options.filtering === 'nearest') {
                this.filtering = 'nearest';
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
            }
            else { // default to linear for non-power-of-2 textures
                this.filtering = 'linear';
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
            }
        }

        Texture.trigger('update', this);
    }

    // Pre-calc sprite regions for a texture sprite in UV [0, 1] space
    calculateSprites() {
        if (this.sprites) {
            for (let s in this.sprites) {
                let sprite = this.sprites[s];

                // Map [0, 0] to [1, 1] coords to the appropriate sprite sub-area of the texture
                this.texcoords[s] = Builders.getTexcoordsForSprite(
                    [sprite[0], sprite[1]],
                    [sprite[2], sprite[3]],
                    [this.width, this.height]
                );

                // Pixel size of sprite
                this.sizes[s] = [sprite[2], sprite[3]];
            }
        }
    }

}

// Static/class methods and state

Texture.create = function constructor(gl, name, options) {
    return new Texture(gl, name, options);
};

// Destroy all texture instances for a given GL context
Texture.destroy = function (gl) {
    var textures = Object.keys(Texture.textures);
    for (var t of textures) {
        var texture = Texture.textures[t];
        if (texture.gl === gl) {
            texture.destroy();
        }
    }
};

// Get sprite pixel size and UVs
Texture.getSpriteInfo = function (texname, sprite) {
    let texture = Texture.textures[texname];
    return texture && { size: texture.sizes[sprite], texcoords: texture.texcoords[sprite] };
};

// Create a set of textures keyed in an object
// Optionally load each if it has a URL specified
Texture.createFromObject = function (gl, textures) {
    let loading = [];
    if (textures) {
        for (let texname in textures) {
            let config = textures[texname];

            // If texture already exists and definition hasn't changed, no need to re-create
            // Note: to avoid flicker when other textures/scene items change
            if (!Texture.changed(texname, config)) {
                continue;
            }

            let texture = Texture.create(gl, texname, config);
            loading.push(texture.loading);
        }
    }
    return Promise.all(loading);
};

// Indicate if a texture definition would be a change from the current cache
Texture.changed = function (name, config) {
    let texture = Texture.textures[name];
    if (texture) { // cached texture
        // canvas/image-based textures are considered dynamic and always refresh
        if (texture.source_type === 'element' || config.element != null) {
            return true;
        }

        // compare definitions
        if (JSON.stringify(Texture.texture_configs[name]) ===
            JSON.stringify(Object.assign({ name }, config))) {
            return false;
        }
    }
    return true;
};

// Get metadata for a texture by name
// Returns via promise, in case texture is still loading
// Can be called on main thread from worker, to sync texture info to worker
Texture.getInfo = function (name) {
    // Get info for all textures by default
    if (!name) {
        name = Object.keys(Texture.textures);
    }

    // Get multiple textures
    if (Array.isArray(name)) {
        return Promise.all(name.map(n => Texture.getInfo(n)));
    }

    // Get single texture
    var tex = Texture.textures[name];
    if (tex) {
        // Wait for this texture to finish loading, or return immediately
        var loading = tex.loading || Promise.resolve(tex);
        return loading.then(() => {
            // Return a subset of texture info
            // (compatible w/structured cloning, suitable for passing to a worker)
            return {
                name: tex.name,
                width: tex.width,
                height: tex.height,
                sprites: tex.sprites,
                texcoords: tex.texcoords,
                sizes: tex.sizes,
                filtering: tex.filtering,
                power_of_2: tex.power_of_2,
                valid: tex.valid
            };
        });
    }
    else {
        // No texture found
        return Promise.resolve(null);
    }
};

// Sync texture info to worker
// Called from worker, gets info on one or more textures info from main thread via remote call, then stores it
// locally in worker. 'textures' can be an array of texture names to sync, or if null, all textures are synced.
Texture.syncTexturesToWorker = function (names) {
    return WorkerBroker.postMessage('Texture.getInfo', names).
        then(textures => {
            for (var tex of textures) {
                Texture.textures[tex.name] = tex;
            }
            return Texture.textures;
        });
};

// Report max texture size for a GL context
Texture.getMaxTextureSize = function (gl) {
    return gl.getParameter(gl.MAX_TEXTURE_SIZE);
};

// Global set of textures, by name
Texture.textures = {};
Texture.texture_configs = {};
Texture.boundTexture = -1;
Texture.activeUnit = -1;

Texture.base_url = null; // optional base URL to add to textures

WorkerBroker.addTarget('Texture', Texture);
subscribeMixin(Texture);
<|endoftext|>


<commit_before>/*
The MIT License (MIT)

Copyright (c) 2015 Patricio Gonzalez Vivo ( http://www.patriciogonzalezvivo.com )

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

var timeLoad = Date.now();
var mouse = {x: 0, y: 0};
var billboards = [];

function isCanvasVisible(_canvas){
	return	((_canvas.getBoundingClientRect().top + _canvas.height) > 0) &&
			(_canvas.getBoundingClientRect().top < (window.innerHeight || document.documentElement.clientHeight));
}

function isPowerOf2(value) {
  return (value & (value - 1)) == 0;
};

function nextHighestPowerOfTwo(x) {
    --x;
    for (var i = 1; i < 32; i <<= 1) {
        x = x | x >> i;
    }
    return x + 1;
}

function loadTexture(_gl, _texture) {
	_gl.bindTexture(_gl.TEXTURE_2D, _texture);
	_gl.pixelStorei(_gl.UNPACK_FLIP_Y_WEBGL, true);
	_gl.texImage2D(_gl.TEXTURE_2D, 0, _gl.RGBA, _gl.RGBA, _gl.UNSIGNED_BYTE, _texture.image);
	if (isPowerOf2(_texture.image.width) && isPowerOf2(_texture.image.height) ) {
		_gl.generateMipmap(_gl.TEXTURE_2D);
		_gl.texParameteri(_gl.TEXTURE_2D, _gl.TEXTURE_MAG_FILTER, _gl.LINEAR);
		_gl.texParameteri(_gl.TEXTURE_2D, _gl.TEXTURE_MIN_FILTER, _gl.LINEAR_MIPMAP_LINEAR);
	} else {
		_gl.texParameteri(_gl.TEXTURE_2D, _gl.TEXTURE_WRAP_S, _gl.CLAMP_TO_EDGE);
		_gl.texParameteri(_gl.TEXTURE_2D, _gl.TEXTURE_WRAP_T, _gl.CLAMP_TO_EDGE);
		_gl.texParameteri(_gl.TEXTURE_2D, _gl.TEXTURE_MIN_FILTER, _gl.LINEAR);
	}
	_gl.bindTexture(_gl.TEXTURE_2D, null);
}

function getMousePos(_canvas, _evt) {
	var rect = _canvas.getBoundingClientRect();
	return {
		x: _evt.clientX - rect.left,
		y: _evt.clientY - rect.top
	};
}

function loadShaders() {
	var list = document.getElementsByTagName("canvas");

	// Load canvas and WebGLContexta
	for(var i = 0; i < list.length; i++){

		var shaderSrc = { vertURL: null, vertSTR: null,
						  fragURL: null, fragSTR: null};

		if( list[i].hasAttribute("data-fragment") ){
			shaderSrc.fragSTR = list[i].getAttribute('data-fragment');
		} else if( list[i].hasAttribute("data-fragment-url") ){
			shaderSrc.fragURL = list[i].getAttribute('data-fragment-url');
		} else {
			continue;
		}

		var canvas = list[i];
		var gl;

		if( !billboards[i] || !billboards[i].gl){
			console.log("Creating WebGL context");
			gl = setupWebGL(list[i]);
		} else {
			gl = billboards[i].gl
		}

		var shader = loadShader(gl, shaderSrc);

		if(!shader.program){
			if(billboards[i].program){
				shader.program = billboards[i].program;
			} else {
				billboards[i] = null;
				return;
			}
		} else if ( billboards[i] && billboards[i].program ){
			billboards[i].gl.deleteProgram(billboards[i].program);
		}

		var vbo = [];
		if ( !billboards[i] || !billboards[i].vbo){
			console.log("Creating Vbo");

			// Define UVS buffer
			var uvs;
			var texCoordLocation = gl.getAttribLocation(shader.program, "a_texcoord");
			uvs = gl.createBuffer();
			gl.bindBuffer( gl.ARRAY_BUFFER, uvs);
			gl.bufferData( gl.ARRAY_BUFFER, new Float32Array([0.0,  0.0,
															1.0,  0.0,
															0.0,  1.0,
															0.0,  1.0,
															1.0,  0.0,
															1.0,  1.0]), gl.STATIC_DRAW);
			gl.enableVertexAttribArray( texCoordLocation );
			gl.vertexAttribPointer( texCoordLocation, 2, gl.FLOAT, false, 0, 0);
			vbo.push(uvs);

			// Define Vertex buffer
			var vertices;
			var positionLocation = gl.getAttribLocation(shader.program, "a_position");
			vertices = gl.createBuffer();
			gl.bindBuffer( gl.ARRAY_BUFFER, vertices);
			gl.bufferData( gl.ARRAY_BUFFER, new Float32Array([-1.0, -1.0,
															1.0, -1.0,
															-1.0,  1.0,
															-1.0,  1.0,
															1.0, -1.0,
															1.0,  1.0]), gl.STATIC_DRAW);
			gl.enableVertexAttribArray( positionLocation );
			gl.vertexAttribPointer( positionLocation , 2, gl.FLOAT, false, 0, 0);
			vbo.push(vertices);
		} else {
			vbo = billboards[i].vbo;
		}

		// Clean texture
		var textures = [];

		// Need to load textures
		var bLoadTextures = list[i].hasAttribute('data-textures');
		if ( billboards[i] && billboards[i].textures && bLoadTextures){
			var nImages = canvas.getAttribute('data-textures').split(',');

			if (nImages.length === billboards[i].textures.length){
				bLoadTextures = false;
				for(var j in nImages){
					if( billboards[i].textures[j].image.getAttribute("src") !== nImages[j] ){
						bLoadTextures = true;
						break;
					}
				}
			}

			if (!bLoadTextures){
				textures = billboards[i].textures;
			}
		}

		if( bLoadTextures ){
			// Clean the texture array
			while(textures.length > 0) {
				console.log("Deleting texture: " + (textures.length-1));
				gl.deleteTexture(textures[textures.length-1]);
    			textures.pop();
			}

			var imgList = list[i].getAttribute('data-textures').split(',');
			for(var nImg in imgList){
				console.log("Loading texture: " + imgList[nImg]);

				textures.push(gl.createTexture());

				gl.bindTexture(gl.TEXTURE_2D, textures[nImg]);
				gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([255, 255, 0, 255])); // red

				textures[nImg].image = new Image();
				textures[nImg].image.onload = function(_canvas,_gl,_program,_textures,_tex) {
					return function() {
						loadTexture(_gl, _tex);
						render(_canvas, _gl, _program, _textures)
					};
				}(canvas,gl,shader.program, textures, textures[nImg]);
	  			textures[nImg].image.src = imgList[nImg];
			}
		}

		// Assign canvas, gl context, shader, UV/Verts buffers and animate boolean to billboard
		billboards[i] = {	canvas: canvas,
							gl: gl,
							program: shader.program,
							vbo: vbo,
							textures: textures,
							mouse: mouse,
							animated: shader.animated };

		renderShader( billboards[i] );
	}
}

function renderShaders(){
	for(var i = 0; i < billboards.length; i++){
		// If there is something on the billboard
		if( billboards[i] !== undefined ){
			if ( billboards[i].animated === true &&
				 isCanvasVisible(billboards[i].canvas )){
				renderShader( billboards[i] );
			}
		}
	}
	window.requestAnimFrame(renderShaders);
}

/**
 * Creates the HTLM for a failure message
 * @param {string} canvasContainerId id of container of th
 *        canvas.
 * @return {string} The html.
 */
var makeFailHTML = function(msg) {
  return '' +
	'

' +
	'' +
	'
' +
	'
' + msg + '
' +
	'
' +
	'

';
};

/**
 * Mesasge for getting a webgl browser
 * @type {string}
 */
var GET_A_WEBGL_BROWSER = '' +
  'This page requires a browser that supports WebGL.
' +
  'Click here to upgrade your browser.';

/**
 * Mesasge for need better hardware
 * @type {string}
 */
var OTHER_PROBLEM = '' +
  "It doesn't appear your computer can support WebGL.
" +
  'Click here for more information.';

/**
 * Creates a webgl context. If creation fails it will
 * change the contents of the container of the <canvas>
 * tag to an error message with the correct links for WebGL.
 * @param {Element} canvas. The canvas element to create a
 *     context from.
 * @param {WebGLContextCreationAttirbutes} opt_attribs Any
 *     creation attributes you want to pass in.
 * @return {WebGLRenderingContext} The created context.
 */
function setupWebGL (_canvas, _opt_attribs) {

  function showLink(str) {
	var container = _canvas.parentNode;
	if (container) {
	  container.innerHTML = makeFailHTML(str);
	}
  };

  if (!window.WebGLRenderingContext) {
	showLink(GET_A_WEBGL_BROWSER);
	return null;
  }

  var context = create3DContext(_canvas, _opt_attribs);
  if (!context) {
	showLink(OTHER_PROBLEM);
  }

  context.getExtension('OES_standard_derivatives');
  return context;
};

/**
 * Creates a webgl context.
 * @param {!Canvas} canvas The canvas tag to get context
 *     from. If one is not passed in one will be created.
 * @return {!WebGLContext} The created context.
 */
function create3DContext(_canvas, _opt_attribs) {
  var names = ["webgl", "experimental-webgl"];
  var context = null;
  for (var ii = 0; ii < names.length; ++ii) {
	try {
	  context = _canvas.getContext(names[ii], _opt_attribs);
	} catch(e) {}
	if (context) {
	  break;
	}
  }
  return context;
}

/**
 * Loads a shader.
 * @param {!WebGLContext} gl The WebGLContext to use.
 * @param {string} shaderSource The shader source.
 * @param {number} shaderType The type of shader.
 * @param {function(string): void) opt_errorCallback callback for errors.
 * @return {!WebGLShader} The created shader.
 */
function createProgram(gl, shaders, opt_attribs, opt_locations) {
  var program = gl.createProgram();
  for (var ii = 0; ii < shaders.length; ++ii) {
	gl.attachShader(program, shaders[ii]);
  }
  if (opt_attribs) {
	for (var ii = 0; ii < opt_attribs.length; ++ii) {
	  gl.bindAttribLocation(
		  program,
		  opt_locations ? opt_locations[ii] : ii,
		  opt_attribs[ii]);
	}
  }
  gl.linkProgram(program);

  // Check the link status
  var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
  if (!linked) {
	  // something went wrong with the link
	  lastError = gl.getProgramInfoLog (program);
	  console.log("Error in program linking:" + lastError);

	  gl.deleteProgram(program);
	  return null;
  }
  return program;
};

/*
 *	Fetch for files
 */
function fetchHTTP(url, methood){
	var request = new XMLHttpRequest(), response;

	request.onreadystatechange = function () {
		if (request.readyState === 4 && request.status === 200) {
			response = request.responseText;
		}
	}
	request.open(methood ? methood : 'GET', url, false);
	request.overrideMimeType("text/plain");
	request.send();
	return response;
}

/*
 *	Create a Vertex of a specific type (gl.VERTEX_SHADER/)
 */
function createShader(_gl, _source, _type) {
	var shader = _gl.createShader( _type );
	_gl.shaderSource(shader, _source);
	_gl.compileShader(shader);

	var compiled = _gl.getShaderParameter(shader, _gl.COMPILE_STATUS);

	if (!compiled) {
		// Something went wrong during compilation; get the error
		lastError = _gl.getShaderInfoLog(shader);
		console.error("*** Error compiling shader '" + shader + "':" + lastError);
		_gl.deleteShader(shader);
		return null;
	}

	return shader;
}

/*
 *	Loads the vert/frag Shaders
 */
function loadShader( _gl, _shaderSrc ) {

	var vertString = "";

	if(_shaderSrc.vertURL){
		vertString = fetchHTTP( _shaderSrc.vertURL );
	} else {
		vertString = "precision mediump float;\n\
uniform vec2 u_resolution;\n\
uniform float u_time;\n\
attribute vec2 a_position;\n\
attribute vec2 a_texcoord;\n\
varying vec2 v_texcoord;\n\
void main() {\n\
   gl_Position = vec4(a_position, 0.0, 1.0);\n\
   v_texcoord = a_texcoord;\n\
}";
	}

	var fragString = "";

	if (_shaderSrc.fragSTR){
		fragString += _shaderSrc.fragSTR;
	} else if(_shaderSrc.fragURL){
		fragString += fetchHTTP( _shaderSrc.fragURL );
	} else {
		fragString += "uniform vec2 u_resolution;\n\
uniform float u_time;\n\
varying vec2 v_texcoord;\n\
void main(){\n\
	vec2 st = gl_FragCoord.xy/u_resolution;\n\
	gl_FragColor = vec4(st.x,st.y,abs(sin(u_time)),1.0);\n\
}";
	}

	var animated = false;
    var nTimes = (fragString.match(/u_time/g)|| []).length;
    var nMouse = (fragString.match(/u_mouse/g)|| []).length;
    animated = ((nTimes > 1) || (nMouse > 1));

	var vertexShader = createShader(_gl, vertString, _gl.VERTEX_SHADER);
	var fragmentShader = createShader(_gl, fragString , _gl.FRAGMENT_SHADER);

	if(!fragmentShader){
		fragmentShader = createShader(_gl, "void main(){\n\
	gl_FragColor = vec4(1.0);\n\
}" , _gl.FRAGMENT_SHADER);
	}

	// Create and use program
	var program = createProgram( _gl, [vertexShader, fragmentShader]);
	_gl.useProgram(program);

	// Delete shaders
	_gl.detachShader(program, vertexShader);
    _gl.detachShader(program, fragmentShader);
    _gl.deleteShader(vertexShader);
    _gl.deleteShader(fragmentShader);

	return {'program': program,
			'animated': animated};
}

function FormatNumberLength(_num, _length) {
    var r = "" + _num;
    while (r.length < _length) {
        r = "0" + r;
    }
    return r;
}

/*
 *	Render loop of shader in a canvas
 */
function renderShader( _billboard ) {
	render(_billboard.canvas, _billboard.gl, _billboard.program, _billboard.textures);
}

function render(_canvas, _gl, _program, _textures) {
	if (!_gl) {
		return;
	}

	// set the time uniform
	var timeFrame = Date.now();
	var time = (timeFrame-timeLoad) / 1000.0;
	var timeLocation = _gl.getUniformLocation(_program, "u_time");
	_gl.uniform1f(timeLocation, time);

	// set the mouse uniform
	var rect = _canvas.getBoundingClientRect();
	if( mouse.x >= rect.left &&
		mouse.x <= rect.right &&
		mouse.y >= rect.top &&
		mouse.y <= rect.bottom){

		var mouseLocation = _gl.getUniformLocation(_program, "u_mouse");
		_gl.uniform2f(mouseLocation,mouse.x-rect.left,_canvas.height-(mouse.y-rect.top));
	}

	// set the resolution uniform
	var resolutionLocation = _gl.getUniformLocation(_program, "u_resolution");
	_gl.uniform2f(resolutionLocation, _canvas.width, _canvas.height);

	for (var i = 0; i < _textures.length; ++i){

		_gl.uniform1i( _gl.getUniformLocation( _program, "u_tex"+i), i);
		_gl.uniform2f( _gl.getUniformLocation( _program, "u_tex"+i+"Resolution"),
								 _textures[i].image.width,
								 _textures[i].image.height);

		_gl.activeTexture(_gl.TEXTURE0+i);
		_gl.bindTexture(_gl.TEXTURE_2D, _textures[i]);

	}

	// Draw the rectangle.
	_gl.drawArrays(_gl.TRIANGLES, 0, 6);
	// console.log("Render " + time);
}

document.addEventListener('mousemove', function(e){
    mouse.x = e.clientX || e.pageX;
    mouse.y = e.clientY || e.pageY
}, false);

/**
 * Provides requestAnimationFrame in a cross browser way.
 */
window.requestAnimFrame = (function() {
	return	window.requestAnimationFrame ||
	    	window.webkitRequestAnimationFrame ||
	    	window.mozRequestAnimationFrame ||
	    	window.oRequestAnimationFrame ||
	    	window.msRequestAnimationFrame ||
	    	function(/* function FrameRequestCallback */ callback, /* DOMElement Element */ element) {
	        	return window.setTimeout(callback, 1000/60);
	     };
})();

/**
 * Provides cancelRequestAnimationFrame in a cross browser way.
 */
window.cancelRequestAnimFrame = (function() {
 	return	window.cancelCancelRequestAnimationFrame ||
        	window.webkitCancelRequestAnimationFrame ||
        	window.mozCancelRequestAnimationFrame ||
        	window.oCancelRequestAnimationFrame ||
        	window.msCancelRequestAnimationFrame ||
        	window.clearTimeout;
})();<commit_msg>no local glslCanvas anymore<commit_after><|endoftext|>


<commit_before>'use strict';

const glob = require('glob')
const fs = require('fs')
const path = require('path')

const { requireIfExist } = require('./core');

const ACTIVATORS_CACHE_PATH = '../build/activators.json';
const LERNA_JSON_PATH = '../lerna.json';

function saveCache(packagesByPaths, packages) {
  const cachePath = path.resolve(__dirname, ACTIVATORS_CACHE_PATH)
  const cacheDirPath = path.dirname(cachePath)

  if (!fs.existsSync(cacheDirPath)) {
    fs.mkdirSync(cacheDirPath)
  }

  fs.writeFileSync(cachePath, JSON.stringify({
    paths: Object.keys(packagesByPaths)
      .reduce((result, path) => {
        const packages = packagesByPaths[path]
        result[path] = Object.keys(packages).filter((key, idx, keys) => keys.indexOf(packages[key].name) === idx)
        return result
      }, {}),

    packages: Object.keys(packages)
      .filter((key, idx, keys) => keys.indexOf(packages[key].name) === idx)
      .reduce((result, key) => {
        result[key] = packages[key]
        return result
      }, {})
  }, null, 2))
}

function findPackagesByPathFromCache(path, cache) {
  return cache.paths[path].reduce((result, packageName) => {
    const packageInfo = cache.packages[packageName];

    if (packageInfo.alias) {
      result[packageInfo.alias] = packageInfo
    }

    result[packageInfo.name] = packageInfo

    return result
  }, {})
}

function findPackagesByPath(packagesPath) {
  return glob.sync(packagesPath).reduce((result, packageFolder) => {
    const packageJson = requireIfExist(`../${packageFolder}/package.json`)
    if (!packageJson || !packageJson['rispa:plugin']) {
      return result
    }

    const name = packageJson['name']
    const rispaName = packageJson['rispa:name']
    const activatorPath = path.resolve(__dirname, `../${packageFolder}/.rispa/activator.js`);

    const packageInfo = {
      path: packageFolder,
      alias: rispaName,
      name: name,
      commands: Object.keys(packageJson.scripts),
      activatorPath: fs.existsSync(activatorPath) ? activatorPath : null
    }

    if (rispaName) {
      result[rispaName] = packageInfo
    }

    result[packageInfo.name] = packageInfo

    return result
  }, {})
}

module.exports = function scanPackages() {
  const cache = requireIfExist(ACTIVATORS_CACHE_PATH)
  const lernaJson = requireIfExist(LERNA_JSON_PATH)

  const packageScanPaths = ['node_modules/*'].concat(lernaJson && lernaJson.packages ? lernaJson.packages : [])
  const packagesByPaths = packageScanPaths.reduce((result, path) => {
    const packages = cache && cache.paths[path] ?
      findPackagesByPathFromCache(path, cache) :
      findPackagesByPath(path)

    result[path] = packages;
    return result;
  }, {})
  const packages = Object.keys(packagesByPaths).reduce((result, path) => Object.assign(result, packagesByPaths[path]), {})

  saveCache(packagesByPaths, packages)

  return packages;
}<commit_msg>fix abs paths<commit_after>'use strict';

const glob = require('glob')
const fs = require('fs')
const path = require('path')

const { requireIfExist } = require('./core');

const BASE_PATH = process.cwd();
const ACTIVATORS_CACHE_PATH = path.resolve(BASE_PATH, './build/activators.json');
const ACTIVATORS_CACHE_DIR_PATH = path.dirname(ACTIVATORS_CACHE_PATH);
const LERNA_JSON_PATH = path.resolve(BASE_PATH, './lerna.json');

function saveCache(packagesByPaths, packages) {
  if (!fs.existsSync(ACTIVATORS_CACHE_DIR_PATH)) {
    fs.mkdirSync(ACTIVATORS_CACHE_DIR_PATH)
  }

  fs.writeFileSync(ACTIVATORS_CACHE_PATH, JSON.stringify({
    paths: Object.keys(packagesByPaths)
      .reduce((result, path) => {
        const packages = packagesByPaths[path]
        result[path] = Object.keys(packages).filter((key, idx, keys) => keys.indexOf(packages[key].name) === idx)
        return result
      }, {}),

    packages: Object.keys(packages)
      .filter((key, idx, keys) => keys.indexOf(packages[key].name) === idx)
      .reduce((result, key) => {
        result[key] = packages[key]
        return result
      }, {})
  }, null, 2))
}

function findPackagesByPathFromCache(path, cache) {
  return cache.paths[path].reduce((result, packageName) => {
    const packageInfo = cache.packages[packageName];

    if (packageInfo.alias) {
      result[packageInfo.alias] = packageInfo
    }

    result[packageInfo.name] = packageInfo

    return result
  }, {})
}

function findPackagesByPath(packagesPath) {
  return glob.sync(packagesPath).reduce((result, packagePath) => {
    const packageJson = requireIfExist(`${packagePath}/package.json`)

    if (!packageJson || !packageJson['rispa:plugin']) {
      return result
    }

    const name = packageJson['name']
    const rispaName = packageJson['rispa:name']
    const activatorPath = `${packagePath}/.rispa/activator.js`;

    const packageInfo = {
      path: packagePath,
      alias: rispaName,
      name: name,
      commands: Object.keys(packageJson.scripts),
      activatorPath: fs.existsSync(activatorPath) ? activatorPath : null
    }

    if (rispaName) {
      result[rispaName] = packageInfo
    }

    result[packageInfo.name] = packageInfo

    return result
  }, {})
}

module.exports = function scanPackages() {
  const activatorsCache = requireIfExist(ACTIVATORS_CACHE_PATH)
  const lernaJson = requireIfExist(LERNA_JSON_PATH)

  const lernaPackagesPaths = lernaJson && lernaJson.packages ?
    lernaJson.packages.map(path => `${BASE_PATH}/${path}`) : []

  const packageScanPaths = [`${BASE_PATH}/node_modules/*`, ...lernaPackagesPaths]

  const packagesByPaths = packageScanPaths.reduce((result, path) => {
    const packages = activatorsCache && activatorsCache.paths[path] ?
      findPackagesByPathFromCache(path, activatorsCache) :
      findPackagesByPath(path)

    result[path] = packages;
    return result;
  }, {})

  const packages = Object.keys(packagesByPaths)
    .reduce((result, path) => Object.assign(result, packagesByPaths[path]), {})

  saveCache(packagesByPaths, packages)

  return packages;
}<|endoftext|>


<commit_before>"use strict";

window.gswaBuffer = function() {
	this.ABSNs = {};
	this.ABSNsLength =
	this._currId = 0;
};

gswaBuffer.regFilename = /(?:([^/]*)\.([a-zA-Z\d]*))?$/;

gswaBuffer.prototype = {
	setContext: function( ctx ) {
		this.ctx = ctx;
	},
	setData: function( data ) {
		if ( data ) {
			var reg, str = data.name || data;

			this.data = data;
			if ( typeof str === "string" ) {
				reg = gswaBuffer.regFilename.exec( str );
				this.filename = reg[ 0 ];
				this.name = reg[ 1 ];
			}
		}
	},
	load: function() {
		var dat = this.data;

		return (
			dat instanceof AudioBuffer ? Promise.resolve( this._setDataFromAudioBuffer( dat ) ) :
			dat instanceof ArrayBuffer ? this._setDataFromArrayBuffer( dat ) :
			dat instanceof Blob ? this._setDataFromBlob( dat ) :
			this._setDataFromURL( dat )
		);
	},
	unload: function() {
		this.stop();
		this.disconnect();
		this.buffer = null;
	},
	connect: function( node ) {
		this.connectedTo = node;
		for ( var id in this.ABSNs ) {
			this.ABSNs[ id ].connect( node );
		}
	},
	disconnect: function() {
		this.connectedTo = null;
		for ( var id in this.ABSNs ) {
			this.ABSNs[ id ].disconnect();
		}
	},
	start: function( when, offset, duration ) {
		var absn,
			ctx = this.ctx,
			buf = this.buffer;

		if ( ctx && buf ) {
			absn = ctx.createBufferSource();
			absn.buffer = buf;
			absn.connect( this.connectedTo );
			absn.onended = this._removeSource.bind( this, this._currId );
			this.ABSNs[ this._currId++ ] = absn;
			++this.ABSNsLength;
			absn.start( when || 0, offset || 0,
				arguments.length > 2 ? duration : this.duration );
			return absn;
		}
	},
	stop: function() {
		var id, absn, ABSNs = this.ABSNs;

		for ( id in ABSNs ) {
			absn = ABSNs[ id ];
			absn.onended = null;
			absn.stop();
			delete ABSNs[ id ];
		}
		this.ABSNsLength = 0;
	},

	// private:
	_removeSource: function( id ) {
		delete this.ABSNs[ id ];
		--this.ABSNsLength;
	},
	_setDataFromAudioBuffer: function( audioBuffer ) {
		this.buffer = audioBuffer;
		this.duration = audioBuffer.duration;
		return audioBuffer;
	},
	_setDataFromArrayBuffer: function( arrayBuffer ) {
		return this.ctx.decodeAudioData( arrayBuffer )
			.then( this._setDataFromAudioBuffer.bind( this ) );
	},
	_setDataFromBlob: function( blob ) {
		var that = this,
			reader = new FileReader();

		return new Promise( function( resolve, reject ) {
			reader.onloadend = function() {
				resolve( that._setDataFromArrayBuffer( reader.result ) );
			};
			reader.readAsArrayBuffer( blob );
		} );
	},
	_setDataFromURL: function( url ) {
		return fetch( url )
			.then( function( res ) { return res.arrayBuffer(); } )
			.then( this._setDataFromArrayBuffer.bind( this ) );
	}
};
<commit_msg>gswaBuffer: .unload(), delete buffer and duration<commit_after>"use strict";

window.gswaBuffer = function() {
	this.ABSNs = {};
	this.ABSNsLength =
	this._currId = 0;
};

gswaBuffer.regFilename = /(?:([^/]*)\.([a-zA-Z\d]*))?$/;

gswaBuffer.prototype = {
	setContext: function( ctx ) {
		this.ctx = ctx;
	},
	setData: function( data ) {
		if ( data ) {
			var reg, str = data.name || data;

			this.data = data;
			if ( typeof str === "string" ) {
				reg = gswaBuffer.regFilename.exec( str );
				this.filename = reg[ 0 ];
				this.name = reg[ 1 ];
			}
		}
	},
	load: function() {
		var dat = this.data;

		return (
			dat instanceof AudioBuffer ? Promise.resolve( this._setDataFromAudioBuffer( dat ) ) :
			dat instanceof ArrayBuffer ? this._setDataFromArrayBuffer( dat ) :
			dat instanceof Blob ? this._setDataFromBlob( dat ) :
			this._setDataFromURL( dat )
		);
	},
	unload: function() {
		this.stop();
		this.disconnect();
		delete this.buffer;
		delete this.duration;
	},
	connect: function( node ) {
		this.connectedTo = node;
		for ( var id in this.ABSNs ) {
			this.ABSNs[ id ].connect( node );
		}
	},
	disconnect: function() {
		this.connectedTo = null;
		for ( var id in this.ABSNs ) {
			this.ABSNs[ id ].disconnect();
		}
	},
	start: function( when, offset, duration ) {
		var absn,
			ctx = this.ctx,
			buf = this.buffer;

		if ( ctx && buf ) {
			absn = ctx.createBufferSource();
			absn.buffer = buf;
			absn.connect( this.connectedTo );
			absn.onended = this._removeSource.bind( this, this._currId );
			this.ABSNs[ this._currId++ ] = absn;
			++this.ABSNsLength;
			absn.start( when || 0, offset || 0,
				arguments.length > 2 ? duration : this.duration );
			return absn;
		}
	},
	stop: function() {
		var id, absn, ABSNs = this.ABSNs;

		for ( id in ABSNs ) {
			absn = ABSNs[ id ];
			absn.onended = null;
			absn.stop();
			delete ABSNs[ id ];
		}
		this.ABSNsLength = 0;
	},

	// private:
	_removeSource: function( id ) {
		delete this.ABSNs[ id ];
		--this.ABSNsLength;
	},
	_setDataFromAudioBuffer: function( audioBuffer ) {
		this.buffer = audioBuffer;
		this.duration = audioBuffer.duration;
		return audioBuffer;
	},
	_setDataFromArrayBuffer: function( arrayBuffer ) {
		return this.ctx.decodeAudioData( arrayBuffer )
			.then( this._setDataFromAudioBuffer.bind( this ) );
	},
	_setDataFromBlob: function( blob ) {
		var that = this,
			reader = new FileReader();

		return new Promise( function( resolve, reject ) {
			reader.onloadend = function() {
				resolve( that._setDataFromArrayBuffer( reader.result ) );
			};
			reader.readAsArrayBuffer( blob );
		} );
	},
	_setDataFromURL: function( url ) {
		return fetch( url )
			.then( function( res ) { return res.arrayBuffer(); } )
			.then( this._setDataFromArrayBuffer.bind( this ) );
	}
};
<|endoftext|>


<commit_before>/* eslint-disable */

'use strict';

const reusify = require('./reusify');

const noop = () => {};

function parallel(released = noop, results = true) {
  const queue = reusify(ResultsHolder);
  const queueSingleCaller = reusify(SingleCaller);

  return Object.freeze((toCall = [], args, done = noop) => {
    const holder = queue.get();

    if (toCall.length === 0) {
      done();
      released(holder);
      return;
    }

    holder._callback = done;
    holder._release = release;

    goResultsArray(toCall, args, holder);

    if (holder._count === 0) {
      holder.release();
    }
  });

  function release(holder) {
    queue.release(holder);
    released(holder);
  }
}

function ResultsHolder() {
  const holder = {
    release,
    count: -1,
    callback: noop,
    results: undefined,
    err: undefined,
    next: undefined
  };

  function release(err, pos, result) {
    holder.err = holder.err || err;

    if (pos >= 0) {
      holder.results[pos] = result;
    }

  }

  this._count = -1
  this._callback = noop
  this._results = null
  this._err = null
  this._callThat = null
  this._release = nop
  this.next = null

  var that = this
  var i = 0
  this.release = function (err, pos, result) {
    that._err = that._err || err
    if (pos >= 0) {
      that._results[pos] = result
    }
    var cb = that._callback
    if (++i === that._count || that._count === 0) {
      if (that._callThat) {
        cb.call(that._callThat, that._err, that._results)
      } else {
        cb(that._err, that._results)
      }
      that._callback = nop
      that._results = null
      that._err = null
      that._callThat = null
      i = 0
      that._release(that)
    }
  }
}

function goResultsArray (funcs, args, holder) {
  var sc = null
  var tc = nop

  holder._count = funcs.length;

  holder._results = new Array(holder._count)
  for (var i = 0; i < funcs.length; i++) {
    sc = queueSingleCaller.get()
    sc._release = singleCallerRelease
    sc.parent = holder
    sc.pos = i
    tc = funcs[i]
    if (that) {
      if (tc.length === 1) tc.call(that, sc.release)
      else tc.call(that, arg, sc.release)
    } else {
      if (tc.length === 1) tc(sc.release)
      else tc(arg, sc.release)
    }
  }
}

function SingleCaller() {
  const caller = {
    release,
    pos: -1,
    parent: undefined,
    next: undefined,
  };

  function release(err, result) {
    caller.parent.release(err, caller.pos, result);
    caller.pos = -1;
    caller.parent = undefined;
  }

  return caller;
}
<commit_msg>chore($performance): finished a faster queue<commit_after>'use strict';

const reusify = require('./reusify');

const noop = () => {};

module.exports = parallel;

function parallel(released = noop, results = true) {
  const queue = reusify(ResultsHolder);
  const queueSingleCaller = reusify(SingleCaller);
  const goArray = results ? goResultsArray : goNoResultsArray;

  return Object.freeze((toCall = [], args, done = noop) => {
    const holder = queue.get();

    if (toCall.length === 0) {
      done();
      released(holder);
      return;
    }

    holder.callback = done;
    holder._release = release;

    goArray(toCall, args, holder);

    if (holder.count === 0) {
      holder.release();
    }
  });

  function release(holder) {
    queue.release(holder);
    released(holder);
  }

  function singleCallerRelease(holder) {
    queueSingleCaller.release(holder);
  }

  function goResultsArray(funcs, args, holder) {
    const funcsLen = funcs.length;
    const release = holder.release;

    holder.count = funcsLen;
    holder.results = new Array(funcsLen);

    funcs.forEach((fn, i) => {
      const singleCaller = queueSingleCaller.get();

      singleCaller._release = singleCallerRelease;
      singleCaller.parent = holder;
      singleCaller.pos = i;

      fn(release, ...args);
    });
  }

  function goNoResultsArray(funcs, args, holder) {
    const funcsLen = funcs.length;
    const release = holder.release;

    holder.count = funcsLen;

    funcs.forEach(fn => fn(release, ...args));
  }
}

function ResultsHolder() {
  let count = 0;
  const holder = {
    release,
    _release: noop,
    count: -1,
    callback: noop,
    results: undefined,
    err: undefined,
    next: undefined
  };

  return holder;

  function release(err, pos, result) {
    if (holder.err === undefined) {
      holder.err = err;
    }

    if (pos >= 0) {
      holder.results[pos] = result;
    }

    count += 1;

    if (count === holder.count || holder.count === 0) {
      holder.callback.call(undefined, holder.err, holder.results);
    }

    holder.callback = noop;
    holder.results = undefined;
    holder.err = undefined;
    holder._release();
  }
}

function SingleCaller() {
  const caller = {
    release,
    pos: -1,
    parent: undefined,
    next: undefined,
  };

  function release(err, result) {
    caller.parent.release(err, caller.pos, result);
    caller.pos = -1;
    caller.parent = undefined;
  }

  return caller;
}
<|endoftext|>