Implement multiple-comparator sorted lists for efficiently tracking portals
This commit is contained in:
parent
3f99420006
commit
4721c22701
100
src/main/kotlin/MultiSortedList.kt
Normal file
100
src/main/kotlin/MultiSortedList.kt
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import java.util.*
|
||||||
|
import kotlin.Comparator
|
||||||
|
|
||||||
|
class MultiSortedList<E> constructor(
|
||||||
|
underlying: MutableList<E>,
|
||||||
|
comparator: Comparator<in E>,
|
||||||
|
vararg extraComparators: Comparator<in E>
|
||||||
|
): SortedList<E>(underlying, comparator) {
|
||||||
|
companion object {
|
||||||
|
fun <T: Comparable<T>> ofComparable(
|
||||||
|
underlying: MutableList<T> = ArrayList(),
|
||||||
|
comparator: Comparator<in T> = Comparator { a, b -> a.compareTo(b) },
|
||||||
|
vararg extraComparators: Comparator<in T>
|
||||||
|
) = MultiSortedList(underlying, comparator, *extraComparators)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var extraLists = extraComparators.associateWith { SortedList(underlying.subList(0, underlying.size), it) }
|
||||||
|
|
||||||
|
override fun add(element: E): Boolean {
|
||||||
|
extraLists.values.forEach {
|
||||||
|
it.add(element)
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.add(element)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun add(index: Int, element: E) {
|
||||||
|
add(element)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addAll(elements: Collection<E>): Boolean {
|
||||||
|
for (element in elements)
|
||||||
|
add(element)
|
||||||
|
|
||||||
|
return elements.isNotEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addAll(index: Int, elements: Collection<E>) = addAll(elements)
|
||||||
|
override fun containsAll(elements: Collection<E>): Boolean {
|
||||||
|
for (element in elements)
|
||||||
|
if (!contains(element))
|
||||||
|
return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun remove(element: E): Boolean {
|
||||||
|
if (super.remove(element)) {
|
||||||
|
extraLists.values.forEach {
|
||||||
|
it.remove(element)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeAt(index: Int) = removeAt(index, comparator)
|
||||||
|
|
||||||
|
override fun removeAll(elements: Collection<E>): Boolean {
|
||||||
|
var result = false
|
||||||
|
|
||||||
|
for (element in elements)
|
||||||
|
result = remove(element) || result
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeAt(index: Int, comparator: Comparator<in E>): E {
|
||||||
|
if (comparator == this.comparator) {
|
||||||
|
val result = super.removeAt(index)
|
||||||
|
|
||||||
|
extraLists.values.forEach {
|
||||||
|
it.remove(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
} else {
|
||||||
|
val result = extraLists[comparator]!!.removeAt(index)
|
||||||
|
|
||||||
|
extraLists.forEach { (comp, list) -> if (comparator != comp) list.remove(result) }
|
||||||
|
super.remove(result)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun get(index: Int, comparator: Comparator<in E>) =
|
||||||
|
if (comparator == this.comparator) this[index]
|
||||||
|
else extraLists[comparator]!![index]
|
||||||
|
|
||||||
|
fun binSearch(element: E, comparator: Comparator<in E>) =
|
||||||
|
if (comparator == this.comparator) binarySearch(element, comparator)
|
||||||
|
else extraLists[comparator]!!.binarySearch(element, comparator)
|
||||||
|
|
||||||
|
fun binSearch(comparator: Comparator<in E>, comparison: (E) -> Int) =
|
||||||
|
if (comparator == this.comparator) binarySearch(comparison = comparison)
|
||||||
|
else extraLists[comparator]!!.binarySearch(comparison = comparison)
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
private val threadLocalBuffer = ThreadLocal.withInitial { ByteBuffer.allocateDirect(9) }
|
private val threadLocalBuffer = ThreadLocal.withInitial { ByteBuffer.allocateDirect(9) }
|
||||||
|
|
||||||
@ -104,3 +105,9 @@ val Char.varIntSize get() = code.varIntSize
|
|||||||
|
|
||||||
fun Float.varIntSize(min: Float, max: Float) = ((this - min)/max).toULong().varIntSize
|
fun Float.varIntSize(min: Float, max: Float) = ((this - min)/max).toULong().varIntSize
|
||||||
fun Double.varIntSize(min: Double, max: Double) = ((this - min)/max).toULong().varIntSize
|
fun Double.varIntSize(min: Double, max: Double) = ((this - min)/max).toULong().varIntSize
|
||||||
|
|
||||||
|
|
||||||
|
operator fun UUID.plus(value: ULong): UUID {
|
||||||
|
val lsb = leastSignificantBits.toULong() + value
|
||||||
|
return UUID(if (lsb < leastSignificantBits.toULong()) mostSignificantBits + 1L else mostSignificantBits, lsb.toLong())
|
||||||
|
}
|
@ -10,7 +10,12 @@ import kotlin.experimental.or
|
|||||||
|
|
||||||
private val PLAYER_COMPARATOR = Comparator<OfflinePlayer> { a, b -> a.uniqueId.compareTo(b.uniqueId) }
|
private val PLAYER_COMPARATOR = Comparator<OfflinePlayer> { a, b -> a.uniqueId.compareTo(b.uniqueId) }
|
||||||
val LOCATION_COMPARATOR = Comparator<Location> { a, b -> a.compareByOrder(b, { world!!.uid }, Location::getBlockX, Location::getBlockY, Location::getBlockZ) }
|
val LOCATION_COMPARATOR = Comparator<Location> { a, b -> a.compareByOrder(b, { world!!.uid }, Location::getBlockX, Location::getBlockY, Location::getBlockZ) }
|
||||||
val PORTAL_COMPARATOR = Comparator<Portal> { a, b -> a.compareByOrder(b, { world.uid }, Portal::x, Portal::y, Portal::z, Portal::id) }
|
|
||||||
|
// An owner cannot place two portals on the same block, implying that this comparator defines a partial order
|
||||||
|
val PORTAL_LOCATION_COMPARATOR = Comparator<Portal> { a, b -> a.compareByOrder(b, { world.uid }, Portal::x, Portal::y, Portal::z, { owner.uniqueId }) }
|
||||||
|
|
||||||
|
// IDs are unique, so this comparator inherently defines a partial order
|
||||||
|
val PORTAL_UID_COMPARATOR = Comparator<Portal> { a, b -> a.id.compareTo(b.id) }
|
||||||
|
|
||||||
|
|
||||||
private val threadLocalInputBuffer = ThreadLocal.withInitial { ReallocatingBuffer(ByteBuffer.allocate(96)) }
|
private val threadLocalInputBuffer = ThreadLocal.withInitial { ReallocatingBuffer(ByteBuffer.allocate(96)) }
|
||||||
@ -188,9 +193,9 @@ fun readCompressedPortal(
|
|||||||
},
|
},
|
||||||
if (PortalFlag.LINKED.isFlagSet(flags)) UUID(inputBuffer.long, inputBuffer.long)
|
if (PortalFlag.LINKED.isFlagSet(flags)) UUID(inputBuffer.long, inputBuffer.long)
|
||||||
else null,
|
else null,
|
||||||
if (PortalFlag.NO_EXCLUSIONS.isFlagSet(flags)) SortedList.create(comparator = PLAYER_COMPARATOR)
|
if (PortalFlag.NO_EXCLUSIONS.isFlagSet(flags)) SortedList(comparator = PLAYER_COMPARATOR)
|
||||||
else run {
|
else run {
|
||||||
val collect = SortedList.create(comparator = PLAYER_COMPARATOR)
|
val collect = SortedList(comparator = PLAYER_COMPARATOR)
|
||||||
while (inputBuffer.position < dataLen)
|
while (inputBuffer.position < dataLen)
|
||||||
collect += playerMapper(inputBuffer.packedUInt) ?: continue
|
collect += playerMapper(inputBuffer.packedUInt) ?: continue
|
||||||
return@run collect
|
return@run collect
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import org.bukkit.Location
|
import org.bukkit.Location
|
||||||
import org.bukkit.configuration.ConfigurationSection
|
import org.bukkit.configuration.ConfigurationSection
|
||||||
import org.bukkit.event.EventHandler
|
import org.bukkit.event.EventHandler
|
||||||
|
import org.bukkit.event.HandlerList
|
||||||
|
import org.bukkit.event.Listener
|
||||||
import org.bukkit.event.player.PlayerMoveEvent
|
import org.bukkit.event.player.PlayerMoveEvent
|
||||||
|
import org.bukkit.plugin.Plugin
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
@ -10,10 +13,55 @@ private const val PATH_WORLDS = "worlds"
|
|||||||
private const val PATH_PORTALS = "portals"
|
private const val PATH_PORTALS = "portals"
|
||||||
|
|
||||||
|
|
||||||
class PortalManager(private val data: ConfigurationSection, private val config: () -> ConfigurationSection) {
|
class PortalManager(private val data: ConfigurationSection, private val config: () -> ConfigurationSection): Listener {
|
||||||
private val players = PlayerMapper(data, PATH_PLAYERS)
|
private val players = PlayerMapper(data, PATH_PLAYERS)
|
||||||
private val worlds = WorldMapper(data, PATH_WORLDS)
|
private val worlds = WorldMapper(data, PATH_WORLDS)
|
||||||
private var portals = SortedList.create(comparator = PORTAL_COMPARATOR)
|
private var portals = MultiSortedList(ArrayList(), PORTAL_LOCATION_COMPARATOR, PORTAL_UID_COMPARATOR)
|
||||||
|
|
||||||
|
// Make UUIDs as "sequential" as possible
|
||||||
|
private var nextUUIDUsed = false
|
||||||
|
private var nextUUID = UUID(0, 0)
|
||||||
|
get() {
|
||||||
|
// If currently held value guaranteed to be unused, just return it
|
||||||
|
if (!nextUUIDUsed) {
|
||||||
|
nextUUIDUsed = true
|
||||||
|
return field
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute next available uuid
|
||||||
|
var lsb = field.leastSignificantBits.toULong()
|
||||||
|
var msb = field.mostSignificantBits.toULong()
|
||||||
|
|
||||||
|
// Start sequential search at the resulting index if it is populated
|
||||||
|
val index = portals.binSearch(PORTAL_UID_COMPARATOR) {
|
||||||
|
compareValues(
|
||||||
|
{ msb } to { it.id.mostSignificantBits.toULong() },
|
||||||
|
{ lsb } to { it.id.leastSignificantBits.toULong() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index >= 0) {
|
||||||
|
// Increment 128-bit value
|
||||||
|
if (++lsb == 0UL)
|
||||||
|
++msb
|
||||||
|
|
||||||
|
for (i in index until portals.size) {
|
||||||
|
val find = portals.get(index, PORTAL_UID_COMPARATOR).id
|
||||||
|
|
||||||
|
// Found a gap in the UUIDs
|
||||||
|
if (find.mostSignificantBits.toULong() != msb || find.leastSignificantBits.toULong() != lsb)
|
||||||
|
break
|
||||||
|
else if (++lsb == 0UL)
|
||||||
|
++msb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save result and mark as used
|
||||||
|
field = UUID(msb.toLong(), lsb.toLong())
|
||||||
|
nextUUIDUsed = true
|
||||||
|
|
||||||
|
return field
|
||||||
|
}
|
||||||
|
|
||||||
fun reload() {
|
fun reload() {
|
||||||
players.reload()
|
players.reload()
|
||||||
@ -21,9 +69,22 @@ class PortalManager(private val data: ConfigurationSection, private val config:
|
|||||||
|
|
||||||
val portalList = ArrayList<Portal>()
|
val portalList = ArrayList<Portal>()
|
||||||
data.getStringList(PATH_PORTALS).forEach {
|
data.getStringList(PATH_PORTALS).forEach {
|
||||||
portalList += readCompressedPortal(it, worlds::getValue, players::getValue) ?: return@forEach
|
val portal = readCompressedPortal(it, worlds::getValue, players::getValue) ?: return@forEach
|
||||||
|
portalList += portal
|
||||||
|
|
||||||
|
if (portal.id >= nextUUID)
|
||||||
|
nextUUID = portal.id + 1UL
|
||||||
|
}
|
||||||
|
portals = MultiSortedList(portalList, PORTAL_LOCATION_COMPARATOR, PORTAL_UID_COMPARATOR)
|
||||||
|
|
||||||
|
if(portals.isEmpty()) nextUUID = UUID(0, 0)
|
||||||
|
else {
|
||||||
|
nextUUID = portals.get(0, PORTAL_UID_COMPARATOR).id + 1UL
|
||||||
|
|
||||||
|
// Compute next UUID
|
||||||
|
nextUUID
|
||||||
|
nextUUIDUsed = false
|
||||||
}
|
}
|
||||||
portals = SortedList.create(PORTAL_COMPARATOR, portalList)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun save() {
|
fun save() {
|
||||||
@ -32,6 +93,13 @@ class PortalManager(private val data: ConfigurationSection, private val config:
|
|||||||
data.set(PATH_PORTALS, portals.map { it.toCompressedString(worlds::getIndex, players::getIndex) })
|
data.set(PATH_PORTALS, portals.map { it.toCompressedString(worlds::getIndex, players::getIndex) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onEnable(plugin: Plugin) {
|
||||||
|
plugin.server.pluginManager.registerEvents(this, plugin)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onDisable() {
|
||||||
|
HandlerList.unregisterAll(this)
|
||||||
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
fun onPlayerMove(moveEvent: PlayerMoveEvent) {
|
fun onPlayerMove(moveEvent: PlayerMoveEvent) {
|
||||||
|
@ -10,13 +10,24 @@ class PortalsPlugin: JavaPlugin() {
|
|||||||
override fun onEnable() {
|
override fun onEnable() {
|
||||||
super.onEnable()
|
super.onEnable()
|
||||||
|
|
||||||
|
reloadConfig()
|
||||||
|
|
||||||
|
portalManager.onEnable(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun reloadConfig() {
|
||||||
|
super.reloadConfig()
|
||||||
saveDefaultConfig()
|
saveDefaultConfig()
|
||||||
data.load()
|
data.load()
|
||||||
|
|
||||||
|
portalManager.reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDisable() {
|
override fun onDisable() {
|
||||||
super.onDisable()
|
super.onDisable()
|
||||||
|
|
||||||
|
portalManager.onDisable()
|
||||||
|
|
||||||
data.save()
|
data.save()
|
||||||
saveConfig()
|
saveConfig()
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,15 @@
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
class SortedList<E> private constructor(
|
open class SortedList<E> constructor(
|
||||||
private val underlying: MutableList<E>,
|
private val underlying: MutableList<E> = ArrayList(),
|
||||||
private val comparator: Comparator<in E>
|
protected val comparator: Comparator<in E>
|
||||||
): MutableList<E> by underlying {
|
): MutableList<E> by underlying {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun <T> create(
|
fun <T: Comparable<T>> ofComparable(
|
||||||
type: Class<T>,
|
|
||||||
underlying: MutableList<T>,
|
|
||||||
comparator: Comparator<in T>
|
|
||||||
) = SortedList(Collections.checkedList(underlying, type), comparator)
|
|
||||||
|
|
||||||
inline fun <reified T: Comparable<T>> create(
|
|
||||||
underlying: MutableList<T> = ArrayList(),
|
underlying: MutableList<T> = ArrayList(),
|
||||||
comparator: Comparator<T> = Comparator { a, b -> a.compareTo(b) }
|
comparator: Comparator<T> = Comparator { a, b -> a.compareTo(b) }
|
||||||
) = create(T::class.java, underlying, comparator)
|
) = SortedList(underlying, comparator)
|
||||||
|
|
||||||
inline fun <reified T> create(comparator: Comparator<T>, underlying: MutableList<T> = ArrayList()) =
|
|
||||||
create(T::class.java, underlying, comparator)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@ -29,29 +20,23 @@ class SortedList<E> private constructor(
|
|||||||
override fun add(element: E): Boolean {
|
override fun add(element: E): Boolean {
|
||||||
val index = underlying.binarySearch(element, comparator)
|
val index = underlying.binarySearch(element, comparator)
|
||||||
|
|
||||||
if (index < 0)
|
underlying.add(if (index < 0) -(index + 1) else index, element)
|
||||||
underlying.add(-(index + 1), element)
|
|
||||||
else
|
|
||||||
underlying[index] = element
|
|
||||||
|
|
||||||
return index < 0
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun add(index: Int, element: E) =
|
override fun add(index: Int, element: E) {
|
||||||
throw UnsupportedOperationException("Cannot insert at index for sorted list")
|
add(element)
|
||||||
|
}
|
||||||
|
|
||||||
override fun addAll(elements: Collection<E>): Boolean {
|
override fun addAll(elements: Collection<E>): Boolean {
|
||||||
var result = false
|
|
||||||
|
|
||||||
for (element in elements)
|
for (element in elements)
|
||||||
result = add(element) || result
|
add(element)
|
||||||
|
|
||||||
return result
|
return elements.isNotEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun addAll(index: Int, elements: Collection<E>) =
|
override fun addAll(index: Int, elements: Collection<E>) = addAll(elements)
|
||||||
throw UnsupportedOperationException("Cannot insert at index for sorted list")
|
|
||||||
|
|
||||||
override fun contains(element: E) = underlying.binarySearch(element, comparator) >= 0
|
override fun contains(element: E) = underlying.binarySearch(element, comparator) >= 0
|
||||||
|
|
||||||
override fun containsAll(elements: Collection<E>): Boolean {
|
override fun containsAll(elements: Collection<E>): Boolean {
|
||||||
@ -73,9 +58,7 @@ class SortedList<E> private constructor(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun removeAt(index: Int) =
|
override fun removeAt(index: Int) = underlying.removeAt(index)
|
||||||
underlying.removeAt(index)
|
|
||||||
|
|
||||||
override fun removeAll(elements: Collection<E>): Boolean {
|
override fun removeAll(elements: Collection<E>): Boolean {
|
||||||
var result = false
|
var result = false
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user