fixed-queue.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. 'use strict'
  2. // Extracted from node/lib/internal/fixed_queue.js
  3. // Currently optimal queue size, tested on V8 6.0 - 6.6. Must be power of two.
  4. const kSize = 2048
  5. const kMask = kSize - 1
  6. // The FixedQueue is implemented as a singly-linked list of fixed-size
  7. // circular buffers. It looks something like this:
  8. //
  9. // head tail
  10. // | |
  11. // v v
  12. // +-----------+ <-----\ +-----------+ <------\ +-----------+
  13. // | [null] | \----- | next | \------- | next |
  14. // +-----------+ +-----------+ +-----------+
  15. // | item | <-- bottom | item | <-- bottom | undefined |
  16. // | item | | item | | undefined |
  17. // | item | | item | | undefined |
  18. // | item | | item | | undefined |
  19. // | item | | item | bottom --> | item |
  20. // | item | | item | | item |
  21. // | ... | | ... | | ... |
  22. // | item | | item | | item |
  23. // | item | | item | | item |
  24. // | undefined | <-- top | item | | item |
  25. // | undefined | | item | | item |
  26. // | undefined | | undefined | <-- top top --> | undefined |
  27. // +-----------+ +-----------+ +-----------+
  28. //
  29. // Or, if there is only one circular buffer, it looks something
  30. // like either of these:
  31. //
  32. // head tail head tail
  33. // | | | |
  34. // v v v v
  35. // +-----------+ +-----------+
  36. // | [null] | | [null] |
  37. // +-----------+ +-----------+
  38. // | undefined | | item |
  39. // | undefined | | item |
  40. // | item | <-- bottom top --> | undefined |
  41. // | item | | undefined |
  42. // | undefined | <-- top bottom --> | item |
  43. // | undefined | | item |
  44. // +-----------+ +-----------+
  45. //
  46. // Adding a value means moving `top` forward by one, removing means
  47. // moving `bottom` forward by one. After reaching the end, the queue
  48. // wraps around.
  49. //
  50. // When `top === bottom` the current queue is empty and when
  51. // `top + 1 === bottom` it's full. This wastes a single space of storage
  52. // but allows much quicker checks.
  53. /**
  54. * @type {FixedCircularBuffer}
  55. * @template T
  56. */
  57. class FixedCircularBuffer {
  58. constructor () {
  59. /**
  60. * @type {number}
  61. */
  62. this.bottom = 0
  63. /**
  64. * @type {number}
  65. */
  66. this.top = 0
  67. /**
  68. * @type {Array<T|undefined>}
  69. */
  70. this.list = new Array(kSize).fill(undefined)
  71. /**
  72. * @type {T|null}
  73. */
  74. this.next = null
  75. }
  76. /**
  77. * @returns {boolean}
  78. */
  79. isEmpty () {
  80. return this.top === this.bottom
  81. }
  82. /**
  83. * @returns {boolean}
  84. */
  85. isFull () {
  86. return ((this.top + 1) & kMask) === this.bottom
  87. }
  88. /**
  89. * @param {T} data
  90. * @returns {void}
  91. */
  92. push (data) {
  93. this.list[this.top] = data
  94. this.top = (this.top + 1) & kMask
  95. }
  96. /**
  97. * @returns {T|null}
  98. */
  99. shift () {
  100. const nextItem = this.list[this.bottom]
  101. if (nextItem === undefined) { return null }
  102. this.list[this.bottom] = undefined
  103. this.bottom = (this.bottom + 1) & kMask
  104. return nextItem
  105. }
  106. }
  107. /**
  108. * @template T
  109. */
  110. module.exports = class FixedQueue {
  111. constructor () {
  112. /**
  113. * @type {FixedCircularBuffer<T>}
  114. */
  115. this.head = this.tail = new FixedCircularBuffer()
  116. }
  117. /**
  118. * @returns {boolean}
  119. */
  120. isEmpty () {
  121. return this.head.isEmpty()
  122. }
  123. /**
  124. * @param {T} data
  125. */
  126. push (data) {
  127. if (this.head.isFull()) {
  128. // Head is full: Creates a new queue, sets the old queue's `.next` to it,
  129. // and sets it as the new main queue.
  130. this.head = this.head.next = new FixedCircularBuffer()
  131. }
  132. this.head.push(data)
  133. }
  134. /**
  135. * @returns {T|null}
  136. */
  137. shift () {
  138. const tail = this.tail
  139. const next = tail.shift()
  140. if (tail.isEmpty() && tail.next !== null) {
  141. // If there is another queue, it forms the new tail.
  142. this.tail = tail.next
  143. tail.next = null
  144. }
  145. return next
  146. }
  147. }