priority.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. 'use strict'
  2. var transport = require('../spdy-transport')
  3. var utils = transport.utils
  4. var assert = require('assert')
  5. var debug = require('debug')('spdy:priority')
  6. function PriorityNode (tree, options) {
  7. this.tree = tree
  8. this.id = options.id
  9. this.parent = options.parent
  10. this.weight = options.weight
  11. // To be calculated in `addChild`
  12. this.priorityFrom = 0
  13. this.priorityTo = 1
  14. this.priority = 1
  15. this.children = {
  16. list: [],
  17. weight: 0
  18. }
  19. if (this.parent !== null) {
  20. this.parent.addChild(this)
  21. }
  22. }
  23. function compareChildren (a, b) {
  24. return a.weight === b.weight ? a.id - b.id : a.weight - b.weight
  25. }
  26. PriorityNode.prototype.toJSON = function toJSON () {
  27. return {
  28. parent: this.parent,
  29. weight: this.weight,
  30. exclusive: this.exclusive
  31. }
  32. }
  33. PriorityNode.prototype.getPriority = function getPriority () {
  34. return this.priority
  35. }
  36. PriorityNode.prototype.getPriorityRange = function getPriorityRange () {
  37. return { from: this.priorityFrom, to: this.priorityTo }
  38. }
  39. PriorityNode.prototype.addChild = function addChild (child) {
  40. child.parent = this
  41. utils.binaryInsert(this.children.list, child, compareChildren)
  42. this.children.weight += child.weight
  43. this._updatePriority(this.priorityFrom, this.priorityTo)
  44. }
  45. PriorityNode.prototype.remove = function remove () {
  46. assert(this.parent, 'Can\'t remove root node')
  47. this.parent.removeChild(this)
  48. this.tree._removeNode(this)
  49. // Move all children to the parent
  50. for (var i = 0; i < this.children.list.length; i++) {
  51. this.parent.addChild(this.children.list[i])
  52. }
  53. }
  54. PriorityNode.prototype.removeChild = function removeChild (child) {
  55. this.children.weight -= child.weight
  56. var index = utils.binarySearch(this.children.list, child, compareChildren)
  57. if (index !== -1 && this.children.list.length >= index) {
  58. this.children.list.splice(index, 1)
  59. }
  60. }
  61. PriorityNode.prototype.removeChildren = function removeChildren () {
  62. var children = this.children.list
  63. this.children.list = []
  64. this.children.weight = 0
  65. return children
  66. }
  67. PriorityNode.prototype._updatePriority = function _updatePriority (from, to) {
  68. this.priority = to - from
  69. this.priorityFrom = from
  70. this.priorityTo = to
  71. var weight = 0
  72. for (var i = 0; i < this.children.list.length; i++) {
  73. var node = this.children.list[i]
  74. var nextWeight = weight + node.weight
  75. node._updatePriority(
  76. from + this.priority * (weight / this.children.weight),
  77. from + this.priority * (nextWeight / this.children.weight)
  78. )
  79. weight = nextWeight
  80. }
  81. }
  82. function PriorityTree (options) {
  83. this.map = {}
  84. this.list = []
  85. this.defaultWeight = options.defaultWeight || 16
  86. this.count = 0
  87. this.maxCount = options.maxCount
  88. // Root
  89. this.root = this.add({
  90. id: 0,
  91. parent: null,
  92. weight: 1
  93. })
  94. }
  95. module.exports = PriorityTree
  96. PriorityTree.create = function create (options) {
  97. return new PriorityTree(options)
  98. }
  99. PriorityTree.prototype.add = function add (options) {
  100. if (options.id === options.parent) {
  101. return this.addDefault(options.id)
  102. }
  103. var parent = options.parent === null ? null : this.map[options.parent]
  104. if (parent === undefined) {
  105. return this.addDefault(options.id)
  106. }
  107. debug('add node=%d parent=%d weight=%d exclusive=%d',
  108. options.id,
  109. options.parent === null ? -1 : options.parent,
  110. options.weight || this.defaultWeight,
  111. options.exclusive ? 1 : 0)
  112. var children
  113. if (options.exclusive) {
  114. children = parent.removeChildren()
  115. }
  116. var node = new PriorityNode(this, {
  117. id: options.id,
  118. parent: parent,
  119. weight: options.weight || this.defaultWeight
  120. })
  121. this.map[options.id] = node
  122. if (options.exclusive) {
  123. for (var i = 0; i < children.length; i++) {
  124. node.addChild(children[i])
  125. }
  126. }
  127. this.count++
  128. if (this.count > this.maxCount) {
  129. debug('hit maximum remove id=%d', this.list[0].id)
  130. this.list.shift().remove()
  131. }
  132. // Root node is not subject to removal
  133. if (node.parent !== null) {
  134. this.list.push(node)
  135. }
  136. return node
  137. }
  138. // Only for testing, should use `node`'s methods
  139. PriorityTree.prototype.get = function get (id) {
  140. return this.map[id]
  141. }
  142. PriorityTree.prototype.addDefault = function addDefault (id) {
  143. debug('creating default node')
  144. return this.add({ id: id, parent: 0, weight: this.defaultWeight })
  145. }
  146. PriorityTree.prototype._removeNode = function _removeNode (node) {
  147. delete this.map[node.id]
  148. var index = utils.binarySearch(this.list, node, compareChildren)
  149. this.list.splice(index, 1)
  150. this.count--
  151. }