Implement multiple-comparator sorted lists for efficiently tracking portals

This commit is contained in:
Gabriel Tofvesson 2021-09-19 22:23:24 +02:00
parent 3f99420006
commit 4721c22701
6 changed files with 214 additions and 40 deletions

View 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)
}

View File

@ -1,4 +1,5 @@
import java.nio.ByteBuffer
import java.util.*
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 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())
}

View File

@ -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

View File

@ -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) {

View File

@ -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()
}

View File

@ -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