'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-- }