queue.bmx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  1. SuperStrict
  2. Import "collection.bmx"
  3. ?threaded
  4. Import BRL.threads
  5. Import BRL.Time
  6. ?
  7. Rem
  8. bbdoc: A first-in, first-out (FIFO) collection of elements.
  9. about: Implements a queue as a circular array. Elements stored in a #TQueue are inserted at one end and removed from the other.
  10. Use a #TQueue if you need to access the information in the same order that it is stored in the collection.
  11. The capacity of a #TQueue is the number of elements the #TQueue can hold. As elements are added to a #TQueue, the capacity
  12. is automatically increased as required by reallocating the internal array. The capacity can be decreased by calling #TrimExcess.
  13. End Rem
  14. Type TQueue<T> Implements ICollection<T>
  15. Private
  16. Field initialCapacity:Int
  17. Field data:T[]
  18. Field head:Int
  19. Field tail:Int
  20. Field size:Int
  21. Field full:Int
  22. Public
  23. Rem
  24. bbdoc: Creates a new #TQueue instance.
  25. End Rem
  26. Method New(initialCapacity:Int = 16)
  27. Self.initialCapacity = initialCapacity
  28. data = New T[initialCapacity]
  29. End Method
  30. Rem
  31. bbdoc: Creates a new #TQueue initialised by @array.
  32. End Rem
  33. Method New(array:T[])
  34. initialCapacity = 16
  35. If array Then
  36. initialCapacity = Max(initialCapacity, array.length)
  37. End If
  38. data = New T[initialCapacity]
  39. If array Then
  40. For Local element:T = EachIn array
  41. Enqueue(element)
  42. Next
  43. End If
  44. End Method
  45. Rem
  46. bbdoc: Creates a new #TQueue initialised by @iterable.
  47. End Rem
  48. Method New(iterable:IIterable<T>)
  49. New(16)
  50. If iterable Then
  51. For Local value:T = EachIn iterable
  52. Enqueue(value)
  53. Next
  54. End If
  55. End Method
  56. Rem
  57. bbdoc: Returns an iterator that iterates through the #TQueue.
  58. End Rem
  59. Method GetIterator:IIterator<T>()
  60. Return New TQueueIterator<T>(Self)
  61. End Method
  62. Rem
  63. bbdoc: Gets the number of elements contained in the #TQueue.
  64. End Rem
  65. Method Count:Int()
  66. Return size
  67. End Method
  68. Rem
  69. bbdoc: Returns #True if the #TQueue is empty, otherwise #False.
  70. End Rem
  71. Method IsEmpty:Int() Override
  72. Return size = 0
  73. End Method
  74. Method CopyTo(array:T[], index:Int = 0)
  75. End Method
  76. Rem
  77. bbdoc: Converts a #TQueue to an array.
  78. returns: An array of elements.
  79. End Rem
  80. Method ToArray:T[]()
  81. Local arr:T[size]
  82. Local i:Int
  83. For Local element:T = EachIn Self
  84. arr[i] = element
  85. i :+ 1
  86. Next
  87. Return arr
  88. End Method
  89. Rem
  90. bbdoc: Removes all elements from the #TQueue.
  91. End Rem
  92. Method Clear()
  93. If size Then
  94. Local index:Int = head
  95. Repeat
  96. data[index] = Null
  97. index :+ 1
  98. If index = data.length Then
  99. index = 0
  100. End If
  101. Until index = tail
  102. size = 0
  103. head = tail
  104. End If
  105. End Method
  106. Rem
  107. bbdoc: Determines whether an element is in the #TQueue.
  108. End Rem
  109. Method Contains:Int(element:T)
  110. If Not size Then
  111. Return False
  112. End If
  113. Local index:Int = head
  114. Repeat
  115. If element = data[index] Then
  116. Return True
  117. End If
  118. index :+ 1
  119. If index = data.length Then
  120. index = 0
  121. End If
  122. Until index = tail
  123. Return False
  124. End Method
  125. Rem
  126. bbdoc: Removes and returns the element at the beginning of the #TQueue.
  127. about: Similar to the #Peek method, but #Peek does not modify the #TQueue.
  128. End Rem
  129. Method Dequeue:T()
  130. If Not size Then
  131. Throw New TInvalidOperationException("The queue is empty")
  132. End If
  133. full = False
  134. Local element:T = data[head]
  135. head :+ 1
  136. size :- 1
  137. If head = data.length Then
  138. head = 0
  139. End If
  140. Return element
  141. End Method
  142. Rem
  143. bbdoc: Adds an element to the end of the #TQueue.
  144. about: If #Count already equals the capacity, the capacity of the #TQueue is increased by automatically reallocating
  145. the internal array, and the existing elements are copied to the new array before the new element is added.
  146. End Rem
  147. Method Enqueue(element:T)
  148. If full Then
  149. Resize()
  150. End If
  151. If Not full Then
  152. data[tail] = element
  153. tail :+ 1
  154. size :+ 1
  155. If tail = data.length Then
  156. tail = 0
  157. End If
  158. If tail = head Then
  159. full = True
  160. End If
  161. End If
  162. End Method
  163. Rem
  164. bbdoc: Returns the element at the beginning of the #TQueue without removing it.
  165. End Rem
  166. Method Peek:T()
  167. If Not size Then
  168. Throw New TInvalidOperationException("The queue is empty")
  169. End If
  170. Return data[head]
  171. End Method
  172. Rem
  173. bbdoc: Can be used to minimize a collection's memory overhead if no new elements will be added to the collection.
  174. End Rem
  175. Method TrimExcess()
  176. Local temp:T[]
  177. If Not size Then
  178. temp = temp[..initialCapacity]
  179. Else If size < data.length Then
  180. temp = temp[..size]
  181. End If
  182. Local tempIndex:Int
  183. Local dataIndex:Int = head
  184. Repeat
  185. temp[tempIndex] = data[dataIndex]
  186. dataIndex :+ 1
  187. If dataIndex = data.length Then
  188. dataIndex = 0
  189. End If
  190. tempIndex :+ 1
  191. Until dataIndex = tail
  192. head = 0
  193. data = temp
  194. tail = 0
  195. full = size > 0
  196. End Method
  197. Rem
  198. bbdoc: Tries to remove and return the element at the beginning of the #TQueue.
  199. returns: #True if an element was removed and returned from the beginning of the #TQueue successfully; otherwise, #False.
  200. about: When this method returns, if the operation was successful, @vlaue contains the element removed. If no element was available to be removed, the value is unspecified.
  201. End Rem
  202. Method TryDequeue:Int(value:T Var)
  203. If Not size Then
  204. Return False
  205. End If
  206. value = Dequeue()
  207. Return True
  208. End Method
  209. Rem
  210. bbdoc: Tries to return an element from the beginning of the #TQueue without removing it.
  211. returns: #True if an element was returned successfully; otherwise, #False.
  212. about: When this method returns, @value contains an element from the beginning of the #TQueue or an unspecified value if the operation failed.
  213. End Rem
  214. Method TryPeek:Int(value:T Var)
  215. If Not size Then
  216. Return False
  217. End If
  218. value = data[head]
  219. Return True
  220. End Method
  221. Private
  222. Method Resize()
  223. Local temp:T[] = New T[data.length * 2]
  224. Local tempIndex:Int
  225. Local dataIndex:Int = head
  226. Repeat
  227. temp[tempIndex] = data[dataIndex]
  228. dataIndex :+ 1
  229. If dataIndex = data.length Then
  230. dataIndex = 0
  231. End If
  232. tempIndex :+ 1
  233. Until dataIndex = tail
  234. head = 0
  235. tail = data.length
  236. data = temp
  237. full = False
  238. End Method
  239. Public
  240. End Type
  241. Type TQueueIterator<T> Implements IIterator<T>
  242. Private
  243. Field queue:TQueue<T>
  244. Field index:Int
  245. Method New(queue:TQueue<T>)
  246. Self.queue = queue
  247. index = queue.head - 1
  248. End Method
  249. Public
  250. Method Current:T()
  251. Return queue.data[index]
  252. End Method
  253. Method MoveNext:Int()
  254. If Not queue.size Then
  255. Return False
  256. End If
  257. index :+ 1
  258. If index = 0 Then
  259. Return True
  260. End If
  261. If index = queue.data.length Then
  262. index = 0
  263. End If
  264. Return index <> queue.tail
  265. End Method
  266. End Type
  267. ?threaded
  268. Rem
  269. bbdoc: A thread-safe first-in, first-out (FIFO) collection of elements.
  270. about: Implements a queue as a circular array. Elements stored in a #TBlockingQueue are inserted at one end and removed from the other.
  271. Use a #TBlockingQueue if you need to access the information in the same order that it is stored in the collection and you need to ensure that the collection is thread-safe.
  272. A call to #Dequeue will block if the queue is empty. A call to #Enqueue will block if the queue is full.
  273. The capacity of a #TBlockingQueue is the number of elements the #TBlockingQueue can hold. Once the queue is full, any attempt to add an element will block until space is available.
  274. End Rem
  275. Type TBlockingQueue<T> Extends TQueue<T>
  276. Private
  277. Field lock:TMutex
  278. Field notEmpty:TCondVar
  279. Field notFull:TCondVar
  280. Public
  281. Method New(capacity:Int = 16)
  282. Super.New(capacity)
  283. lock = TMutex.Create()
  284. notEmpty = TCondVar.Create()
  285. notFull = TCondVar.Create()
  286. End Method
  287. Method Enqueue(element:T)
  288. lock.Lock()
  289. While full
  290. notFull.Wait(lock)
  291. Wend
  292. Super.Enqueue(element)
  293. notEmpty.Signal()
  294. lock.Unlock()
  295. End Method
  296. Rem
  297. bbdoc: Adds an element to the end of the #TBlockingQueue, waiting up to the specified wait time if necessary for space to become available
  298. about: If the queue is full, the operation will block until space becomes available or the specified timeout elapses.
  299. Throws a #TTimeoutException if the operation times out.
  300. End Rem
  301. Method Enqueue(element:T, timeout:ULong, unit:ETimeUnit = ETimeUnit.Milliseconds)
  302. Local timeoutMs:ULong = TimeUnitToMillis(timeout, unit)
  303. Local startTime:ULong = CurrentUnixTime()
  304. lock.Lock()
  305. While full
  306. Local now:ULong = CurrentUnixTime()
  307. If timeout > 0 And now - startTime >= timeoutMs
  308. lock.Unlock()
  309. Throw New TTimeoutException("The operation timed out after " + timeoutMs + "ms")
  310. End If
  311. notFull.TimedWait(lock, Int(timeoutMs - (now - startTime)))
  312. Wend
  313. Super.Enqueue(element)
  314. notEmpty.Signal()
  315. lock.Unlock()
  316. End Method
  317. Rem
  318. bbdoc: Removes and returns the element at the beginning of the #TBlockingQueue, waiting up to the specified wait time if necessary for an element to become available.
  319. about: If the queue is empty, the operation will block until an element becomes available or the specified timeout elapses.
  320. Throws a #TTimeoutException if the operation times out.
  321. End Rem
  322. Method Dequeue:T(timeout:ULong, unit:ETimeUnit = ETimeUnit.Milliseconds)
  323. Local timeoutMs:ULong = TimeUnitToMillis(timeout, unit)
  324. Local startTime:Long = CurrentUnixTime()
  325. lock.Lock()
  326. While IsEmpty()
  327. Local now:ULong = CurrentUnixTime()
  328. If timeout > 0 And now - startTime >= timeoutMs
  329. lock.Unlock()
  330. Throw New TTimeoutException("The operation timed out after " + timeoutMs + "ms")
  331. End If
  332. notEmpty.TimedWait(lock, Int(timeoutMs - (now - startTime)))
  333. Wend
  334. Local element:T = Super.Dequeue()
  335. notFull.Signal()
  336. lock.Unlock()
  337. Return element
  338. End Method
  339. Method Dequeue:T()
  340. lock.Lock()
  341. While IsEmpty()
  342. notEmpty.Wait(lock)
  343. Wend
  344. Local element:T = Super.Dequeue()
  345. notFull.Signal()
  346. lock.Unlock()
  347. Return element
  348. End Method
  349. Method TryDequeue:Int(value:T Var)
  350. lock.Lock()
  351. If IsEmpty()
  352. lock.Unlock()
  353. Return False
  354. End If
  355. value = Super.Dequeue()
  356. notFull.Signal()
  357. lock.Unlock()
  358. Return True
  359. End Method
  360. Method TryPeek:Int(value:T Var)
  361. lock.Lock()
  362. If IsEmpty()
  363. lock.Unlock()
  364. Return False
  365. End If
  366. value = data[head]
  367. lock.Unlock()
  368. Return True
  369. End Method
  370. Method Clear()
  371. lock.Lock()
  372. Super.Clear()
  373. notFull.Signal()
  374. lock.Unlock()
  375. End Method
  376. Method TrimExcess()
  377. ' noop since a blocking queue does not grow beyond its initial capacity
  378. End Method
  379. Method Resize()
  380. lock.Lock()
  381. Super.Resize()
  382. notFull.Signal()
  383. lock.Unlock()
  384. End Method
  385. End Type
  386. Rem
  387. bbdoc: A thread-safe first-in, first-out (FIFO) collection of elements that supports the concept of tasks.
  388. about: When a task is complete, the task should call the #TaskDone method to signal that the task is done.
  389. End Rem
  390. Type TBlockingTaskQueue<T> Extends TQueue<T>
  391. Private
  392. Field lock:TMutex
  393. Field notEmpty:TCondVar
  394. Field notFull:TCondVar
  395. Field allTasksDone:TCondVar
  396. Field taskLock:TMutex
  397. Field unfinishedTasks:Int
  398. Public
  399. Method New(capacity:Int = 16)
  400. Super.New(capacity)
  401. lock = TMutex.Create()
  402. notEmpty = TCondVar.Create()
  403. notFull = TCondVar.Create()
  404. allTasksDone = TCondVar.Create()
  405. taskLock = TMutex.Create()
  406. unfinishedTasks = 0
  407. End Method
  408. Method Enqueue(element:T)
  409. lock.Lock()
  410. While full
  411. notFull.Wait(lock)
  412. Wend
  413. Super.Enqueue(element)
  414. taskLock.Lock()
  415. unfinishedTasks :+ 1
  416. taskLock.Unlock()
  417. notEmpty.Signal()
  418. lock.Unlock()
  419. End Method
  420. Rem
  421. bbdoc: Adds an element to the end of the #TBlockingTaskQueue, waiting up to the specified wait time if necessary for space to become available
  422. about: If the queue is full, the operation will block until space becomes available or the specified timeout elapses.
  423. Throws a #TTimeoutException if the operation times out.
  424. End Rem
  425. Method Enqueue(element:T, timeout:ULong, unit:ETimeUnit = ETimeUnit.Milliseconds)
  426. Local timeoutMs:ULong = TimeUnitToMillis(timeout, unit)
  427. Local startTime:ULong = CurrentUnixTime()
  428. lock.Lock()
  429. While full
  430. Local now:ULong = CurrentUnixTime()
  431. If timeout > 0 And now - startTime >= timeoutMs
  432. lock.Unlock()
  433. Throw New TTimeoutException("The operation timed out after " + timeoutMs + "ms")
  434. End If
  435. notFull.TimedWait(lock, Int(timeoutMs - (now - startTime)))
  436. Wend
  437. Super.Enqueue(element)
  438. taskLock.Lock()
  439. unfinishedTasks :+ 1
  440. taskLock.Unlock()
  441. notEmpty.Signal()
  442. lock.Unlock()
  443. End Method
  444. Rem
  445. bbdoc: Removes and returns the element at the beginning of the #TBlockingTaskQueue, waiting up to the specified wait time if necessary for an element to become available.
  446. about: If the queue is empty, the operation will block until an element becomes available or the specified timeout elapses.
  447. Throws a #TTimeoutException if the operation times out.
  448. End Rem
  449. Method Dequeue:T(timeout:ULong, unit:ETimeUnit = ETimeUnit.Milliseconds)
  450. Local timeoutMs:ULong = TimeUnitToMillis(timeout, unit)
  451. Local startTime:Long = CurrentUnixTime()
  452. lock.Lock()
  453. While IsEmpty()
  454. Local now:ULong = CurrentUnixTime()
  455. If timeout > 0 And now - startTime >= timeoutMs
  456. lock.Unlock()
  457. Throw New TTimeoutException("The operation timed out after " + timeoutMs + "ms")
  458. End If
  459. notEmpty.TimedWait(lock, Int(timeoutMs - (now - startTime)))
  460. Wend
  461. Local element:T = Super.Dequeue()
  462. notFull.Signal()
  463. lock.Unlock()
  464. Return element
  465. End Method
  466. Method Dequeue:T()
  467. lock.Lock()
  468. While IsEmpty()
  469. notEmpty.Wait(lock)
  470. Wend
  471. Local element:T = Super.Dequeue()
  472. notFull.Signal()
  473. lock.Unlock()
  474. Return element
  475. End Method
  476. Method TryDequeue:Int(value:T Var)
  477. lock.Lock()
  478. If IsEmpty()
  479. lock.Unlock()
  480. Return False
  481. End If
  482. value = Super.Dequeue()
  483. notFull.Signal()
  484. lock.Unlock()
  485. Return True
  486. End Method
  487. Method TryPeek:Int(value:T Var)
  488. lock.Lock()
  489. If IsEmpty()
  490. lock.Unlock()
  491. Return False
  492. End If
  493. value = data[head]
  494. lock.Unlock()
  495. Return True
  496. End Method
  497. Method Clear()
  498. lock.Lock()
  499. Super.Clear()
  500. taskLock.Lock()
  501. unfinishedTasks = 0
  502. allTasksDone.Signal()
  503. taskLock.Unlock()
  504. notFull.Signal()
  505. lock.Unlock()
  506. End Method
  507. Method TrimExcess()
  508. ' noop since a blocking queue does not grow beyond its initial capacity
  509. End Method
  510. Method Resize()
  511. lock.Lock()
  512. Super.Resize()
  513. notFull.Signal()
  514. lock.Unlock()
  515. End Method
  516. Rem
  517. bbdoc: Signals that a task is done.
  518. End Rem
  519. Method TaskDone()
  520. taskLock.Lock()
  521. If unfinishedTasks > 0 Then
  522. unfinishedTasks :- 1
  523. If unfinishedTasks = 0 Then
  524. allTasksDone.Signal()
  525. End If
  526. End If
  527. taskLock.Unlock()
  528. End Method
  529. Rem
  530. bbdoc: Waits until all tasks are done.
  531. End Rem
  532. Method Join()
  533. taskLock.Lock()
  534. While unfinishedTasks > 0
  535. allTasksDone.Wait(taskLock)
  536. Wend
  537. taskLock.Unlock()
  538. End Method
  539. End Type
  540. ?