Files
Terrarum/src/net/torvald/util/CircularArray.kt

192 lines
5.4 KiB
Kotlin

package net.torvald.util
import java.util.*
/**
* buffer[head] contains the most recent item, whereas buffer[tail] contains the oldest one.
*
* Notes for particle storage:
* Particles does not need to be removed, just let it overwrite as their operation is rather
* lightweight. So, just flagDespawn = true if it need to be "deleted" so that it won't update
* anymore.
*
* Created by minjaesong on 2017-01-22.
*/
class CircularArray<T>(val size: Int, val overwriteOnOverflow: Boolean): Iterable<T> {
/**
* What to do RIGHT BEFORE old element is being overridden by the new element (only makes sense when ```overwriteOnOverflow = true```)
*
* This function will not be called when ```removeHead()``` or ```removeTail()``` is called.
*/
var overwritingPolicy: (T) -> Unit = {
// do nothing
}
val buffer: Array<T> = arrayOfNulls<Any>(size) as Array<T>
/** Tail stands for the oldest element. The tail index points AT the tail element */
var tail: Int = 0; private set
/** Head stands for the youngest element. The head index points AFTER the head element */
var head: Int = 0; private set
private var overflow = false
val lastIndex = size - 1
/**
* Number of elements that forEach() or fold() would iterate.
*/
val elemCount: Int
get() = if (overflow) size else head - tail
val isEmpty: Boolean
get() = !overflow && head == tail
private inline fun incHead() { head = (head + 1).wrap() }
private inline fun decHead() { head = (head - 1).wrap() }
private inline fun incTail() { tail = (tail + 1).wrap() }
private inline fun decTail() { tail = (tail - 1).wrap() }
fun clear() {
tail = 0
head = 0
overflow = false
}
/**
* When the overflowing is enabled, tail element (ultimate element) will be changed into the penultimate element.
*/
fun appendHead(item: T) {
if (overflow && !overwriteOnOverflow) {
throw StackOverflowError()
}
else {
if (overflow) {
overwritingPolicy.invoke(buffer[head])
}
buffer[head] = item
incHead()
}
if (overflow) {
incTail()
}
// must be checked AFTER the actual head increment; otherwise this condition doesn't make sense
if (tail == head) {
overflow = true
}
}
fun appendTail(item: T) {
// even if overflowing is enabled, appending at tail causes head element to be altered, therefore such action
// must be blocked by throwing overflow error
if (overflow) {
throw StackOverflowError()
}
else {
decTail()
buffer[tail] = item
}
// must be checked AFTER the actual head increment; otherwise this condition doesn't make sense
if (tail == head) {
overflow = true
}
}
fun removeHead(): T {
if (isEmpty) throw EmptyStackException()
decHead()
overflow = false
return buffer[head]
}
fun removeTail(): T {
if (isEmpty) throw EmptyStackException()
val ret = buffer[tail]
incTail()
overflow = false
return ret
}
/** Returns the youngest (last of the array) element */
fun getHeadElem(): T = if (isEmpty) throw EmptyStackException() else buffer[(head - 1).wrap()]
/** Returns the oldest (first of the array) element */
fun getTailElem(): T = buffer[tail]
/**
* Relative-indexed get. Index of zero will return the head element.
*/
operator fun get(index: Int) = buffer[(head - 1 - index).wrap()]
private fun getAbsoluteRange() = 0 until when {
head == tail -> buffer.size
tail > head -> buffer.size - (((head - 1).wrap()) - tail)
else -> head - tail
}
override fun iterator(): Iterator<T> {
if (isEmpty) {
return object : Iterator<T> {
override fun next(): T = throw EmptyStackException()
override fun hasNext() = false
}
}
val rangeMax = getAbsoluteRange().last
var counter = 0
return object : Iterator<T> {
override fun next(): T {
val ret = buffer[(counter + tail).wrap()]
counter += 1
return ret
}
override fun hasNext() = (counter <= rangeMax)
}
}
/**
* Iterates the array with oldest element (tail) first.
*/
fun forEach(action: (T) -> Unit) {
// for (element in buffer) action(element)
// return nothing
iterator().forEach(action)
}
fun <R> fold(initial: R, operation: (R, T) -> R): R {
// accumulator = initial
// for (element in buffer) accumulator = operation(accumulator, element)
// return accumulator
var accumulator = initial
if (isEmpty)
return initial
else {
iterator().forEach {
accumulator = operation(accumulator, it)
}
}
return accumulator
}
private inline fun Int.wrap() = this fmod size
override fun toString(): String {
return "CircularArray(size=${buffer.size}, head=$head, tail=$tail, overflow=$overflow)"
}
private inline infix fun Int.fmod(other: Int) = Math.floorMod(this, other)
}