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