From 00090ce56e27c88882108bfa5e17e6e6d1bd502f Mon Sep 17 00:00:00 2001 From: TomTom <93038247+AverageGithub@users.noreply.github.com> Date: Mon, 9 Oct 2023 19:52:17 +0200 Subject: [PATCH] Major optimisations to minion ticking --- .../axminions/api/minions/Minion.kt | 4 ++ .../api/minions/miniontype/MinionType.kt | 10 ++- .../axminions/api/minions/utils/ChunkPos.kt | 8 +++ .../axminions/AxMinionsPlugin.kt | 2 + .../axminions/commands/AxMinionsCommand.kt | 2 +- .../axminions/listeners/ChunkListener.kt | 20 ++++++ .../listeners/MinionPlaceListener.kt | 1 + .../axminions/minions/Minion.kt | 20 ++++-- .../axminions/minions/MinionTicker.kt | 7 +- .../axminions/minions/Minions.kt | 70 +++++++++++++++++-- .../minions/miniontype/CollectorMinionType.kt | 11 ++- 11 files changed, 136 insertions(+), 19 deletions(-) create mode 100644 api/src/main/kotlin/com/artillexstudios/axminions/api/minions/utils/ChunkPos.kt create mode 100644 common/src/main/kotlin/com/artillexstudios/axminions/listeners/ChunkListener.kt diff --git a/api/src/main/kotlin/com/artillexstudios/axminions/api/minions/Minion.kt b/api/src/main/kotlin/com/artillexstudios/axminions/api/minions/Minion.kt index 5f6d65f..06c5171 100644 --- a/api/src/main/kotlin/com/artillexstudios/axminions/api/minions/Minion.kt +++ b/api/src/main/kotlin/com/artillexstudios/axminions/api/minions/Minion.kt @@ -93,4 +93,8 @@ interface Minion : InventoryHolder { fun getChestLocationId(): Int fun removeOpenInventory(inventory: Inventory) + + fun isTicking(): Boolean + + fun setTicking(ticking: Boolean) } \ No newline at end of file diff --git a/api/src/main/kotlin/com/artillexstudios/axminions/api/minions/miniontype/MinionType.kt b/api/src/main/kotlin/com/artillexstudios/axminions/api/minions/miniontype/MinionType.kt index 557f578..4fef0fb 100644 --- a/api/src/main/kotlin/com/artillexstudios/axminions/api/minions/miniontype/MinionType.kt +++ b/api/src/main/kotlin/com/artillexstudios/axminions/api/minions/miniontype/MinionType.kt @@ -25,16 +25,20 @@ abstract class MinionType(private val name: String, private val defaults: InputS return this.name } + open fun onToolDirty(minion: Minion) { + + } + open fun shouldRun(minion: Minion): Boolean { return true } - fun isChunkLoaded(location: Location): Boolean { - return location.world?.isChunkLoaded(location.blockX shr 4, location.blockZ shr 4) ?: return false + fun isTicking(minion: Minion): Boolean { + return minion.isTicking() } fun tick(minion: Minion) { - if (!isChunkLoaded(minion.getLocation())) return + if (!minion.isTicking()) return if (!shouldRun(minion)) return run(minion) diff --git a/api/src/main/kotlin/com/artillexstudios/axminions/api/minions/utils/ChunkPos.kt b/api/src/main/kotlin/com/artillexstudios/axminions/api/minions/utils/ChunkPos.kt new file mode 100644 index 0000000..6518715 --- /dev/null +++ b/api/src/main/kotlin/com/artillexstudios/axminions/api/minions/utils/ChunkPos.kt @@ -0,0 +1,8 @@ +package com.artillexstudios.axminions.api.minions.utils + +data class ChunkPos(var x: Int, var z: Int) { + + fun clone(): ChunkPos { + return ChunkPos(x, z) + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/artillexstudios/axminions/AxMinionsPlugin.kt b/common/src/main/kotlin/com/artillexstudios/axminions/AxMinionsPlugin.kt index 5c56135..eb740e7 100644 --- a/common/src/main/kotlin/com/artillexstudios/axminions/AxMinionsPlugin.kt +++ b/common/src/main/kotlin/com/artillexstudios/axminions/AxMinionsPlugin.kt @@ -14,6 +14,7 @@ import com.artillexstudios.axminions.api.minions.miniontype.MinionType import com.artillexstudios.axminions.api.minions.miniontype.MinionTypes import com.artillexstudios.axminions.commands.AxMinionsCommand import com.artillexstudios.axminions.data.H2DataHandler +import com.artillexstudios.axminions.listeners.ChunkListener import com.artillexstudios.axminions.listeners.LinkingListener import com.artillexstudios.axminions.listeners.MinionInventoryListener import com.artillexstudios.axminions.listeners.MinionPlaceListener @@ -80,6 +81,7 @@ class AxMinionsPlugin : AxPlugin() { Bukkit.getPluginManager().registerEvents(MinionPlaceListener(), this) Bukkit.getPluginManager().registerEvents(LinkingListener(), this) Bukkit.getPluginManager().registerEvents(MinionInventoryListener(), this) + Bukkit.getPluginManager().registerEvents(ChunkListener(), this) MinionTicker.startTicking() } diff --git a/common/src/main/kotlin/com/artillexstudios/axminions/commands/AxMinionsCommand.kt b/common/src/main/kotlin/com/artillexstudios/axminions/commands/AxMinionsCommand.kt index 90d6c7b..38bd1ad 100644 --- a/common/src/main/kotlin/com/artillexstudios/axminions/commands/AxMinionsCommand.kt +++ b/common/src/main/kotlin/com/artillexstudios/axminions/commands/AxMinionsCommand.kt @@ -74,7 +74,7 @@ class AxMinionsCommand { val total = minions.size minions.fastFor { - if (it.getType().isChunkLoaded(it.getLocation())) { + if (it.getType().isTicking(it)) { loaded++ } } diff --git a/common/src/main/kotlin/com/artillexstudios/axminions/listeners/ChunkListener.kt b/common/src/main/kotlin/com/artillexstudios/axminions/listeners/ChunkListener.kt new file mode 100644 index 0000000..a972c9b --- /dev/null +++ b/common/src/main/kotlin/com/artillexstudios/axminions/listeners/ChunkListener.kt @@ -0,0 +1,20 @@ +package com.artillexstudios.axminions.listeners + +import com.artillexstudios.axminions.minions.Minions +import org.bukkit.event.EventHandler +import org.bukkit.event.Listener +import org.bukkit.event.world.ChunkLoadEvent +import org.bukkit.event.world.ChunkUnloadEvent + +class ChunkListener : Listener { + + @EventHandler + fun onChunkLoadEvent(event: ChunkLoadEvent) { + Minions.addTicking(event.chunk) + } + + @EventHandler + fun onChunkUnloadEvent(event: ChunkUnloadEvent) { + Minions.removeTicking(event.chunk) + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/artillexstudios/axminions/listeners/MinionPlaceListener.kt b/common/src/main/kotlin/com/artillexstudios/axminions/listeners/MinionPlaceListener.kt index 5fea831..151a6ce 100644 --- a/common/src/main/kotlin/com/artillexstudios/axminions/listeners/MinionPlaceListener.kt +++ b/common/src/main/kotlin/com/artillexstudios/axminions/listeners/MinionPlaceListener.kt @@ -47,6 +47,7 @@ class MinionPlaceListener : Listener { } val minion = Minion(location, event.player.uniqueId, event.player, minionType, 1, ItemStack(Material.AIR), null, Direction.NORTH, 0, 0.0, AxMinionsPlugin.dataHandler.getLocationID(location), 0) + minion.setTicking(true) AxMinionsPlugin.dataHandler.saveMinion(minion) event.player.sendMessage(StringUtils.formatToString(Messages.PREFIX() + Messages.PLACE_SUCCESS(), Placeholder.unparsed("type", minionType.getName()), Placeholder.unparsed("placed", (placed + 1).toString()), Placeholder.unparsed("max", (maxMinions).toString()))) diff --git a/common/src/main/kotlin/com/artillexstudios/axminions/minions/Minion.kt b/common/src/main/kotlin/com/artillexstudios/axminions/minions/Minion.kt index 372f713..5ae1cb8 100644 --- a/common/src/main/kotlin/com/artillexstudios/axminions/minions/Minion.kt +++ b/common/src/main/kotlin/com/artillexstudios/axminions/minions/Minion.kt @@ -25,14 +25,12 @@ import com.artillexstudios.axminions.utils.fastFor import org.bukkit.Location import org.bukkit.OfflinePlayer import org.bukkit.block.Container -import org.bukkit.enchantments.Enchantment import org.bukkit.entity.EntityType import org.bukkit.entity.Player import org.bukkit.inventory.Inventory import org.bukkit.inventory.ItemStack import org.bukkit.util.EulerAngle import java.util.UUID -import kotlin.math.roundToInt import org.bukkit.Bukkit import org.bukkit.Material import org.bukkit.persistence.PersistentDataType @@ -52,8 +50,8 @@ class Minion( private var chestLocationId: Int ) : Minion { private lateinit var entity: PacketArmorStand - private var nextAction = 0 - private var range = 0.0 + internal var nextAction = 0 + internal var range = 0.0 private var dirty = true private var armTick = 2.0 private var warning: Warning? = null @@ -61,6 +59,8 @@ class Minion( private val extraData = hashMapOf() private var linkedInventory: Inventory? = null private val openInventories = mutableListOf() + @Volatile + private var ticking = false init { spawn() @@ -95,9 +95,7 @@ class Minion( override fun tick() { if (dirty) { dirty = false - range = type.getDouble("range", level) - val efficiency = 1.0 - (getTool()?.getEnchantmentLevel(Enchantment.DIG_SPEED)?.div(10.0) ?: 0.1) - nextAction = (type.getLong("speed", level) * efficiency).roundToInt() + type.onToolDirty(this) } type.tick(this) @@ -342,6 +340,14 @@ class Minion( openInventories.remove(inventory) } + override fun isTicking(): Boolean { + return ticking + } + + override fun setTicking(ticking: Boolean) { + this.ticking = ticking + } + override fun getInventory(): Inventory { return Bukkit.createInventory(this, 9) } diff --git a/common/src/main/kotlin/com/artillexstudios/axminions/minions/MinionTicker.kt b/common/src/main/kotlin/com/artillexstudios/axminions/minions/MinionTicker.kt index 6c714ec..23b698e 100644 --- a/common/src/main/kotlin/com/artillexstudios/axminions/minions/MinionTicker.kt +++ b/common/src/main/kotlin/com/artillexstudios/axminions/minions/MinionTicker.kt @@ -7,9 +7,12 @@ object MinionTicker { private var tick = 0L private inline fun tickAll() { - Minions.getMinions().fastFor { minion -> - minion.tick() + Minions.get().values.forEach { list -> + list.fastFor { + it.tick() + } } + tick++ } diff --git a/common/src/main/kotlin/com/artillexstudios/axminions/minions/Minions.kt b/common/src/main/kotlin/com/artillexstudios/axminions/minions/Minions.kt index 84c6c28..2a10831 100644 --- a/common/src/main/kotlin/com/artillexstudios/axminions/minions/Minions.kt +++ b/common/src/main/kotlin/com/artillexstudios/axminions/minions/Minions.kt @@ -1,21 +1,81 @@ package com.artillexstudios.axminions.minions import com.artillexstudios.axminions.api.minions.Minion +import com.artillexstudios.axminions.api.minions.utils.ChunkPos +import com.artillexstudios.axminions.utils.fastFor import java.util.Collections -import java.util.concurrent.ConcurrentLinkedQueue +import org.bukkit.Chunk object Minions { - private val entities = ConcurrentLinkedQueue() + private val mutex = Object() + private val minions = hashMapOf>() + private val mutableChunkPos = ChunkPos(0, 0) + + fun addTicking(chunk: Chunk) { + synchronized(mutex) { + mutableChunkPos.x = chunk.x + mutableChunkPos.z = chunk.z + + minions[mutableChunkPos]?.fastFor { + it.setTicking(true) + } ?: return + } + } + + fun removeTicking(chunk: Chunk) { + synchronized(mutex) { + mutableChunkPos.x = chunk.x + mutableChunkPos.z = chunk.z + + val minions = this.minions[mutableChunkPos] ?: return + + minions.fastFor { + it.setTicking(false) + } + } + } fun load(minion: Minion) { - entities.add(minion) + synchronized(mutex) { + mutableChunkPos.x = round(minion.getLocation().x) shr 4 + mutableChunkPos.z = round(minion.getLocation().z) shr 4 + val pos = minions[mutableChunkPos] ?: arrayListOf() + + pos.add(minion) + minions[mutableChunkPos] = pos + } } fun remove(minion: Minion) { - entities.remove(minion) + synchronized(mutex) { + mutableChunkPos.x = minion.getLocation().blockX shr 4 + mutableChunkPos.z = minion.getLocation().blockZ shr 4 + val pos = minions[mutableChunkPos] ?: return + + pos.remove(minion) + if (pos.isEmpty()) { + minions.remove(mutableChunkPos) + } + } } fun getMinions(): List { - return Collections.unmodifiableList(entities.toList()) + synchronized(mutex) { + val list = mutableListOf() + minions.forEach { (_, value) -> + list.addAll(value) + } + return Collections.unmodifiableList(list) + } + } + + internal fun get(): HashMap> { + synchronized(mutex) { + return minions + } + } + + private infix fun round(double: Double): Int { + return (double + 0.5).toInt() } } \ No newline at end of file diff --git a/common/src/main/kotlin/com/artillexstudios/axminions/minions/miniontype/CollectorMinionType.kt b/common/src/main/kotlin/com/artillexstudios/axminions/minions/miniontype/CollectorMinionType.kt index b38148d..49c93db 100644 --- a/common/src/main/kotlin/com/artillexstudios/axminions/minions/miniontype/CollectorMinionType.kt +++ b/common/src/main/kotlin/com/artillexstudios/axminions/minions/miniontype/CollectorMinionType.kt @@ -5,7 +5,9 @@ import com.artillexstudios.axminions.api.minions.Minion import com.artillexstudios.axminions.api.minions.miniontype.MinionType import com.artillexstudios.axminions.api.warnings.Warnings import com.artillexstudios.axminions.minions.MinionTicker +import kotlin.math.roundToInt import org.bukkit.Material +import org.bukkit.enchantments.Enchantment import org.bukkit.entity.Item class CollectorMinionType : MinionType("collector", AxMinionsPlugin.INSTANCE.getResource("minions/collector.yml")!!) { @@ -14,6 +16,13 @@ class CollectorMinionType : MinionType("collector", AxMinionsPlugin.INSTANCE.get return MinionTicker.getTick() % minion.getNextAction() == 0L } + override fun onToolDirty(minion: Minion) { + val minionImpl = minion as com.artillexstudios.axminions.minions.Minion + minionImpl.range = getDouble("range", minion.getLevel()) + val efficiency = 1.0 - (minion.getTool()?.getEnchantmentLevel(Enchantment.DIG_SPEED)?.div(10.0) ?: 0.1) + minionImpl.nextAction = (getLong("speed", minion.getLevel()) * efficiency).roundToInt() + } + override fun run(minion: Minion) { minion.resetAnimation() if (minion.getLinkedChest() == null) { @@ -22,7 +31,7 @@ class CollectorMinionType : MinionType("collector", AxMinionsPlugin.INSTANCE.get } val type = minion.getLinkedChest()!!.block.type - if (type != Material.CHEST && type != Material.TRAPPED_CHEST && type != Material.BARREL && !type.name.lowercase().contains("shulker_box")) { + if (type != Material.CHEST && type != Material.TRAPPED_CHEST && type != Material.BARREL) { Warnings.NO_CONTAINER.display(minion) minion.setLinkedChest(null) return