monix.execution.internal.collection.DropHeadOnOverflowQueue.scala Maven / Gradle / Ivy
The newest version!
/*
* Copyright (c) 2014-2019 by The Monix Project Developers.
* See the project homepage at: https://monix.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package monix.execution.internal.collection
import monix.execution.internal.math.nextPowerOf2
import scala.collection.mutable
import scala.reflect.ClassTag
/**
* An [[EvictingQueue]] implementation that on overflow starts
* dropping old elements.
*
* This implementation is not thread-safe and on the JVM it
* needs to be synchronized.
*/
private[monix] final class DropHeadOnOverflowQueue[A: ClassTag] private (_recommendedCapacity: Int)
extends EvictingQueue[A] { self =>
require(_recommendedCapacity > 0, "recommendedCapacity must be positive")
private[this] val maxSize = {
val v = nextPowerOf2(_recommendedCapacity + 1)
if (v <= 1) 2 else v
}
private[this] val modulus = maxSize - 1
def capacity: Int = modulus
private[this] val array = new Array[A](maxSize)
// head is incremented by `poll()`, or by `offer()` on overflow
private[this] var headIdx = 0
// tail is incremented by `offer()`
private[this] var tailIdx = 0
override def isEmpty: Boolean =
headIdx == tailIdx
override def isAtCapacity: Boolean =
size >= modulus
def offer(elem: A): Int = {
if (elem == null) throw new NullPointerException("Null is not supported")
array(tailIdx) = elem
tailIdx = (tailIdx + 1) & modulus
if (tailIdx != headIdx) 0
else {
// overflow just happened, dropping one by incrementing head
headIdx = (headIdx + 1) & modulus
1
}
}
def state: (Int, Int, Seq[A]) = {
(headIdx, tailIdx, array.toSeq)
}
def offerMany(seq: A*): Long = {
val iterator = seq.iterator
var acc = 0L
while (iterator.hasNext) acc += offer(iterator.next())
acc
}
def poll(): A = {
if (headIdx == tailIdx) null.asInstanceOf[A]
else {
val elem = array(headIdx)
// incrementing head pointer
headIdx = (headIdx + 1) & modulus
elem
}
}
def drainToArray(array: Array[A], offset: Int = 0): Int = {
var arrayIdx = offset
while (arrayIdx < array.length && headIdx != tailIdx) {
array(arrayIdx) = self.array(headIdx)
// incrementing head pointer
headIdx = (headIdx + 1) & modulus
arrayIdx += 1
}
arrayIdx - offset
}
override def drainToBuffer(buffer: mutable.Buffer[A], limit: Int): Int = {
var count = 0
while (headIdx != tailIdx && count < limit) {
buffer += self.array(headIdx)
// incrementing head pointer
headIdx = (headIdx + 1) & modulus
count += 1
}
count
}
override def size: Int = {
if (tailIdx >= headIdx)
tailIdx - headIdx
else
(maxSize - headIdx) + tailIdx
}
override def head: A = {
if (headIdx == tailIdx)
throw new NoSuchElementException("EvictingQueue is empty")
else
array(headIdx)
}
override def headOption: Option[A] = {
try Some(head)
catch {
case _: NoSuchElementException =>
None
}
}
def iterator: Iterator[A] =
iterator(exactSize = false)
/** Returns an `Iterator` for this queue.
*
* @param exactSize specified if the `Iterator` should be relaxed regarding
* the `_recommendedCapacity` (if `false`), or whether it should have
* a fixed size and thus not exceed `_recommendedCapacity` (if `true`)
*/
def iterator(exactSize: Boolean): Iterator[A] = {
new Iterator[A] {
private[this] var isStarted = false
private[this] val initialTailIdx = self.tailIdx
private[this] val initialHeadIdx = {
if (!exactSize) self.headIdx
else {
// Dropping extra elements
val currentSize = self.size
if (currentSize < _recommendedCapacity) self.headIdx
else (self.headIdx + (currentSize - _recommendedCapacity)) & modulus
}
}
private[this] var tailIdx = 0
private[this] var headIdx = 0
def hasNext: Boolean = {
if (!isStarted) init()
headIdx != tailIdx
}
def next(): A = {
if (!isStarted) init()
if (headIdx == tailIdx)
throw new NoSuchElementException("EvictingQueue.iterator is empty")
else {
val elem = array(headIdx)
// incrementing head pointer
headIdx = (headIdx + 1) & modulus
elem
}
}
private[this] def init(): Unit = {
isStarted = true
if (self.headIdx != self.tailIdx) {
headIdx = initialHeadIdx
tailIdx = initialTailIdx
}
}
}
}
def clear(): Unit = {
headIdx = 0
tailIdx = 0
}
def length: Int = size
}
/** [[DropHeadOnOverflowQueue]] builders.
*
* @define recommendedCapacityDesc is the recommended capacity that
* this queue will support, however the actual capacity will
* be the closest power of 2 that is bigger or equal to the
* given number minus one, or a maximum of 2^30^-1 (the
* maximum positive int that can be expressed as a power of
* 2, minus 1)
*/
private[monix] object DropHeadOnOverflowQueue {
/**
* Builder for [[DropHeadOnOverflowQueue]]
*
* @param recommendedCapacity $recommendedCapacityDesc
*/
def apply[A: ClassTag](recommendedCapacity: Int): DropHeadOnOverflowQueue[A] = {
new DropHeadOnOverflowQueue[A](recommendedCapacity)
}
/** Builder for [[DropHeadOnOverflowQueue]] that boxes
* elements into an `Array[Any]`.
*
* @param recommendedCapacity $recommendedCapacityDesc
*/
def boxed[A](recommendedCapacity: Int): DropHeadOnOverflowQueue[A] =
apply[Any](recommendedCapacity).asInstanceOf[DropHeadOnOverflowQueue[A]]
}