189 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			189 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
'use strict'
 | 
						|
 | 
						|
var transport = require('../spdy-transport')
 | 
						|
var utils = transport.utils
 | 
						|
 | 
						|
var assert = require('assert')
 | 
						|
var debug = require('debug')('spdy:priority')
 | 
						|
 | 
						|
function PriorityNode (tree, options) {
 | 
						|
  this.tree = tree
 | 
						|
 | 
						|
  this.id = options.id
 | 
						|
  this.parent = options.parent
 | 
						|
  this.weight = options.weight
 | 
						|
 | 
						|
  // To be calculated in `addChild`
 | 
						|
  this.priorityFrom = 0
 | 
						|
  this.priorityTo = 1
 | 
						|
  this.priority = 1
 | 
						|
 | 
						|
  this.children = {
 | 
						|
    list: [],
 | 
						|
    weight: 0
 | 
						|
  }
 | 
						|
 | 
						|
  if (this.parent !== null) {
 | 
						|
    this.parent.addChild(this)
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
function compareChildren (a, b) {
 | 
						|
  return a.weight === b.weight ? a.id - b.id : a.weight - b.weight
 | 
						|
}
 | 
						|
 | 
						|
PriorityNode.prototype.toJSON = function toJSON () {
 | 
						|
  return {
 | 
						|
    parent: this.parent,
 | 
						|
    weight: this.weight,
 | 
						|
    exclusive: this.exclusive
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
PriorityNode.prototype.getPriority = function getPriority () {
 | 
						|
  return this.priority
 | 
						|
}
 | 
						|
 | 
						|
PriorityNode.prototype.getPriorityRange = function getPriorityRange () {
 | 
						|
  return { from: this.priorityFrom, to: this.priorityTo }
 | 
						|
}
 | 
						|
 | 
						|
PriorityNode.prototype.addChild = function addChild (child) {
 | 
						|
  child.parent = this
 | 
						|
  utils.binaryInsert(this.children.list, child, compareChildren)
 | 
						|
  this.children.weight += child.weight
 | 
						|
 | 
						|
  this._updatePriority(this.priorityFrom, this.priorityTo)
 | 
						|
}
 | 
						|
 | 
						|
PriorityNode.prototype.remove = function remove () {
 | 
						|
  assert(this.parent, 'Can\'t remove root node')
 | 
						|
 | 
						|
  this.parent.removeChild(this)
 | 
						|
  this.tree._removeNode(this)
 | 
						|
 | 
						|
  // Move all children to the parent
 | 
						|
  for (var i = 0; i < this.children.list.length; i++) {
 | 
						|
    this.parent.addChild(this.children.list[i])
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
PriorityNode.prototype.removeChild = function removeChild (child) {
 | 
						|
  this.children.weight -= child.weight
 | 
						|
  var index = utils.binarySearch(this.children.list, child, compareChildren)
 | 
						|
  if (index !== -1 && this.children.list.length >= index) {
 | 
						|
    this.children.list.splice(index, 1)
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
PriorityNode.prototype.removeChildren = function removeChildren () {
 | 
						|
  var children = this.children.list
 | 
						|
  this.children.list = []
 | 
						|
  this.children.weight = 0
 | 
						|
  return children
 | 
						|
}
 | 
						|
 | 
						|
PriorityNode.prototype._updatePriority = function _updatePriority (from, to) {
 | 
						|
  this.priority = to - from
 | 
						|
  this.priorityFrom = from
 | 
						|
  this.priorityTo = to
 | 
						|
 | 
						|
  var weight = 0
 | 
						|
  for (var i = 0; i < this.children.list.length; i++) {
 | 
						|
    var node = this.children.list[i]
 | 
						|
    var nextWeight = weight + node.weight
 | 
						|
 | 
						|
    node._updatePriority(
 | 
						|
      from + this.priority * (weight / this.children.weight),
 | 
						|
      from + this.priority * (nextWeight / this.children.weight)
 | 
						|
    )
 | 
						|
    weight = nextWeight
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
function PriorityTree (options) {
 | 
						|
  this.map = {}
 | 
						|
  this.list = []
 | 
						|
  this.defaultWeight = options.defaultWeight || 16
 | 
						|
 | 
						|
  this.count = 0
 | 
						|
  this.maxCount = options.maxCount
 | 
						|
 | 
						|
  // Root
 | 
						|
  this.root = this.add({
 | 
						|
    id: 0,
 | 
						|
    parent: null,
 | 
						|
    weight: 1
 | 
						|
  })
 | 
						|
}
 | 
						|
module.exports = PriorityTree
 | 
						|
 | 
						|
PriorityTree.create = function create (options) {
 | 
						|
  return new PriorityTree(options)
 | 
						|
}
 | 
						|
 | 
						|
PriorityTree.prototype.add = function add (options) {
 | 
						|
  if (options.id === options.parent) {
 | 
						|
    return this.addDefault(options.id)
 | 
						|
  }
 | 
						|
 | 
						|
  var parent = options.parent === null ? null : this.map[options.parent]
 | 
						|
  if (parent === undefined) {
 | 
						|
    return this.addDefault(options.id)
 | 
						|
  }
 | 
						|
 | 
						|
  debug('add node=%d parent=%d weight=%d exclusive=%d',
 | 
						|
    options.id,
 | 
						|
    options.parent === null ? -1 : options.parent,
 | 
						|
    options.weight || this.defaultWeight,
 | 
						|
    options.exclusive ? 1 : 0)
 | 
						|
 | 
						|
  var children
 | 
						|
  if (options.exclusive) {
 | 
						|
    children = parent.removeChildren()
 | 
						|
  }
 | 
						|
 | 
						|
  var node = new PriorityNode(this, {
 | 
						|
    id: options.id,
 | 
						|
    parent: parent,
 | 
						|
    weight: options.weight || this.defaultWeight
 | 
						|
  })
 | 
						|
  this.map[options.id] = node
 | 
						|
 | 
						|
  if (options.exclusive) {
 | 
						|
    for (var i = 0; i < children.length; i++) {
 | 
						|
      node.addChild(children[i])
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  this.count++
 | 
						|
  if (this.count > this.maxCount) {
 | 
						|
    debug('hit maximum remove id=%d', this.list[0].id)
 | 
						|
    this.list.shift().remove()
 | 
						|
  }
 | 
						|
 | 
						|
  // Root node is not subject to removal
 | 
						|
  if (node.parent !== null) {
 | 
						|
    this.list.push(node)
 | 
						|
  }
 | 
						|
 | 
						|
  return node
 | 
						|
}
 | 
						|
 | 
						|
// Only for testing, should use `node`'s methods
 | 
						|
PriorityTree.prototype.get = function get (id) {
 | 
						|
  return this.map[id]
 | 
						|
}
 | 
						|
 | 
						|
PriorityTree.prototype.addDefault = function addDefault (id) {
 | 
						|
  debug('creating default node')
 | 
						|
  return this.add({ id: id, parent: 0, weight: this.defaultWeight })
 | 
						|
}
 | 
						|
 | 
						|
PriorityTree.prototype._removeNode = function _removeNode (node) {
 | 
						|
  delete this.map[node.id]
 | 
						|
  var index = utils.binarySearch(this.list, node, compareChildren)
 | 
						|
  this.list.splice(index, 1)
 | 
						|
  this.count--
 | 
						|
}
 |