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.util.*
|
||||
|
||||
private val threadLocalBuffer = ThreadLocal.withInitial { ByteBuffer.allocateDirect(9) }
|
||||
|
||||
@ -103,4 +104,10 @@ val Short.varIntSize get() = toLong().interlace().varIntSize
|
||||
val Char.varIntSize get() = code.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) }
|
||||
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)) }
|
||||
@ -188,9 +193,9 @@ fun readCompressedPortal(
|
||||
},
|
||||
if (PortalFlag.LINKED.isFlagSet(flags)) UUID(inputBuffer.long, inputBuffer.long)
|
||||
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 {
|
||||
val collect = SortedList.create(comparator = PLAYER_COMPARATOR)
|
||||
val collect = SortedList(comparator = PLAYER_COMPARATOR)
|
||||
while (inputBuffer.position < dataLen)
|
||||
collect += playerMapper(inputBuffer.packedUInt) ?: continue
|
||||
return@run collect
|
||||
|
@ -1,7 +1,10 @@
|
||||
import org.bukkit.Location
|
||||
import org.bukkit.configuration.ConfigurationSection
|
||||
import org.bukkit.event.EventHandler
|
||||
import org.bukkit.event.HandlerList
|
||||
import org.bukkit.event.Listener
|
||||
import org.bukkit.event.player.PlayerMoveEvent
|
||||
import org.bukkit.plugin.Plugin
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
@ -10,10 +13,55 @@ private const val PATH_WORLDS = "worlds"
|
||||
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 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() {
|
||||
players.reload()
|
||||
@ -21,9 +69,22 @@ class PortalManager(private val data: ConfigurationSection, private val config:
|
||||
|
||||
val portalList = ArrayList<Portal>()
|
||||
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() {
|
||||
@ -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) })
|
||||
}
|
||||
|
||||
fun onEnable(plugin: Plugin) {
|
||||
plugin.server.pluginManager.registerEvents(this, plugin)
|
||||
}
|
||||
|
||||
fun onDisable() {
|
||||
HandlerList.unregisterAll(this)
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
fun onPlayerMove(moveEvent: PlayerMoveEvent) {
|
||||
|
@ -10,13 +10,24 @@ class PortalsPlugin: JavaPlugin() {
|
||||
override fun onEnable() {
|
||||
super.onEnable()
|
||||
|
||||
reloadConfig()
|
||||
|
||||
portalManager.onEnable(this)
|
||||
}
|
||||
|
||||
override fun reloadConfig() {
|
||||
super.reloadConfig()
|
||||
saveDefaultConfig()
|
||||
data.load()
|
||||
|
||||
portalManager.reload()
|
||||
}
|
||||
|
||||
override fun onDisable() {
|
||||
super.onDisable()
|
||||
|
||||
portalManager.onDisable()
|
||||
|
||||
data.save()
|
||||
saveConfig()
|
||||
}
|
||||
|
@ -1,24 +1,15 @@
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class SortedList<E> private constructor(
|
||||
private val underlying: MutableList<E>,
|
||||
private val comparator: Comparator<in E>
|
||||
open class SortedList<E> constructor(
|
||||
private val underlying: MutableList<E> = ArrayList(),
|
||||
protected val comparator: Comparator<in E>
|
||||
): MutableList<E> by underlying {
|
||||
|
||||
companion object {
|
||||
fun <T> create(
|
||||
type: Class<T>,
|
||||
underlying: MutableList<T>,
|
||||
comparator: Comparator<in T>
|
||||
) = SortedList(Collections.checkedList(underlying, type), comparator)
|
||||
|
||||
inline fun <reified T: Comparable<T>> create(
|
||||
fun <T: Comparable<T>> ofComparable(
|
||||
underlying: MutableList<T> = ArrayList(),
|
||||
comparator: Comparator<T> = Comparator { a, b -> a.compareTo(b) }
|
||||
) = create(T::class.java, underlying, comparator)
|
||||
|
||||
inline fun <reified T> create(comparator: Comparator<T>, underlying: MutableList<T> = ArrayList()) =
|
||||
create(T::class.java, underlying, comparator)
|
||||
) = SortedList(underlying, comparator)
|
||||
}
|
||||
|
||||
init {
|
||||
@ -29,29 +20,23 @@ class SortedList<E> private constructor(
|
||||
override fun add(element: E): Boolean {
|
||||
val index = underlying.binarySearch(element, comparator)
|
||||
|
||||
if (index < 0)
|
||||
underlying.add(-(index + 1), element)
|
||||
else
|
||||
underlying[index] = element
|
||||
underlying.add(if (index < 0) -(index + 1) else index, element)
|
||||
|
||||
return index < 0
|
||||
return true
|
||||
}
|
||||
|
||||
override fun add(index: Int, element: E) =
|
||||
throw UnsupportedOperationException("Cannot insert at index for sorted list")
|
||||
override fun add(index: Int, element: E) {
|
||||
add(element)
|
||||
}
|
||||
|
||||
override fun addAll(elements: Collection<E>): Boolean {
|
||||
var result = false
|
||||
|
||||
for (element in elements)
|
||||
result = add(element) || result
|
||||
add(element)
|
||||
|
||||
return result
|
||||
return elements.isNotEmpty()
|
||||
}
|
||||
|
||||
override fun addAll(index: Int, elements: Collection<E>) =
|
||||
throw UnsupportedOperationException("Cannot insert at index for sorted list")
|
||||
|
||||
override fun addAll(index: Int, elements: Collection<E>) = addAll(elements)
|
||||
override fun contains(element: E) = underlying.binarySearch(element, comparator) >= 0
|
||||
|
||||
override fun containsAll(elements: Collection<E>): Boolean {
|
||||
@ -73,9 +58,7 @@ class SortedList<E> private constructor(
|
||||
return false
|
||||
}
|
||||
|
||||
override fun removeAt(index: Int) =
|
||||
underlying.removeAt(index)
|
||||
|
||||
override fun removeAt(index: Int) = underlying.removeAt(index)
|
||||
override fun removeAll(elements: Collection<E>): Boolean {
|
||||
var result = false
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user