diff --git a/src/main/kotlin/MultiSortedList.kt b/src/main/kotlin/MultiSortedList.kt new file mode 100644 index 0000000..c075236 --- /dev/null +++ b/src/main/kotlin/MultiSortedList.kt @@ -0,0 +1,100 @@ +import java.util.* +import kotlin.Comparator + +class MultiSortedList constructor( + underlying: MutableList, + comparator: Comparator, + vararg extraComparators: Comparator +): SortedList(underlying, comparator) { + companion object { + fun > ofComparable( + underlying: MutableList = ArrayList(), + comparator: Comparator = Comparator { a, b -> a.compareTo(b) }, + vararg extraComparators: Comparator + ) = 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): Boolean { + for (element in elements) + add(element) + + return elements.isNotEmpty() + } + + override fun addAll(index: Int, elements: Collection) = addAll(elements) + override fun containsAll(elements: Collection): 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): Boolean { + var result = false + + for (element in elements) + result = remove(element) || result + + return result + } + + fun removeAt(index: Int, comparator: Comparator): 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) = + if (comparator == this.comparator) this[index] + else extraLists[comparator]!![index] + + fun binSearch(element: E, comparator: Comparator) = + if (comparator == this.comparator) binarySearch(element, comparator) + else extraLists[comparator]!!.binarySearch(element, comparator) + + fun binSearch(comparator: Comparator, comparison: (E) -> Int) = + if (comparator == this.comparator) binarySearch(comparison = comparison) + else extraLists[comparator]!!.binarySearch(comparison = comparison) +} diff --git a/src/main/kotlin/Numbers.kt b/src/main/kotlin/Numbers.kt index d41ab5f..43d02f4 100644 --- a/src/main/kotlin/Numbers.kt +++ b/src/main/kotlin/Numbers.kt @@ -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 \ No newline at end of file +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()) +} \ No newline at end of file diff --git a/src/main/kotlin/Portal.kt b/src/main/kotlin/Portal.kt index 4f059f2..7feb024 100644 --- a/src/main/kotlin/Portal.kt +++ b/src/main/kotlin/Portal.kt @@ -10,7 +10,12 @@ import kotlin.experimental.or private val PLAYER_COMPARATOR = Comparator { a, b -> a.uniqueId.compareTo(b.uniqueId) } val LOCATION_COMPARATOR = Comparator { a, b -> a.compareByOrder(b, { world!!.uid }, Location::getBlockX, Location::getBlockY, Location::getBlockZ) } -val PORTAL_COMPARATOR = Comparator { 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 { 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 { 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 diff --git a/src/main/kotlin/PortalManager.kt b/src/main/kotlin/PortalManager.kt index 8459b81..d294843 100644 --- a/src/main/kotlin/PortalManager.kt +++ b/src/main/kotlin/PortalManager.kt @@ -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() 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) { diff --git a/src/main/kotlin/PortalsPlugin.kt b/src/main/kotlin/PortalsPlugin.kt index c20e194..93e7835 100644 --- a/src/main/kotlin/PortalsPlugin.kt +++ b/src/main/kotlin/PortalsPlugin.kt @@ -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() } diff --git a/src/main/kotlin/SortedList.kt b/src/main/kotlin/SortedList.kt index ec86ae9..dfe25e5 100644 --- a/src/main/kotlin/SortedList.kt +++ b/src/main/kotlin/SortedList.kt @@ -1,24 +1,15 @@ import java.util.* +import kotlin.collections.ArrayList -class SortedList private constructor( - private val underlying: MutableList, - private val comparator: Comparator +open class SortedList constructor( + private val underlying: MutableList = ArrayList(), + protected val comparator: Comparator ): MutableList by underlying { - companion object { - fun create( - type: Class, - underlying: MutableList, - comparator: Comparator - ) = SortedList(Collections.checkedList(underlying, type), comparator) - - inline fun > create( + fun > ofComparable( underlying: MutableList = ArrayList(), comparator: Comparator = Comparator { a, b -> a.compareTo(b) } - ) = create(T::class.java, underlying, comparator) - - inline fun create(comparator: Comparator, underlying: MutableList = ArrayList()) = - create(T::class.java, underlying, comparator) + ) = SortedList(underlying, comparator) } init { @@ -29,29 +20,23 @@ class SortedList 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): 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) = - throw UnsupportedOperationException("Cannot insert at index for sorted list") - + override fun addAll(index: Int, elements: Collection) = addAll(elements) override fun contains(element: E) = underlying.binarySearch(element, comparator) >= 0 override fun containsAll(elements: Collection): Boolean { @@ -73,9 +58,7 @@ class SortedList 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): Boolean { var result = false