diff --git a/src/main/kotlin/xyz/fortern/forternhelper/Helper.kt b/src/main/kotlin/xyz/fortern/forternhelper/Helper.kt index ced2b9a..d52430b 100644 --- a/src/main/kotlin/xyz/fortern/forternhelper/Helper.kt +++ b/src/main/kotlin/xyz/fortern/forternhelper/Helper.kt @@ -4,24 +4,38 @@ import me.clip.placeholderapi.expansion.PlaceholderExpansion import net.kyori.adventure.platform.bukkit.BukkitAudiences import org.bukkit.Bukkit import org.bukkit.plugin.java.JavaPlugin +import xyz.fortern.forternhelper.async.AsyncManager import xyz.fortern.forternhelper.command.HelperCommand import xyz.fortern.forternhelper.listener.ForternListener import xyz.fortern.forternhelper.placeholder.ForternExpansion +import java.io.File class Helper : JavaPlugin() { private lateinit var adventure: BukkitAudiences private lateinit var expansion: PlaceholderExpansion + private lateinit var asyncManager: AsyncManager + override fun onEnable() { // Plugin startup logic this.adventure = BukkitAudiences.create(this) - logger.info("Registering listeners...") + + // init data-folders + logger.info("Initializing data-folders...") + File(this.dataFolder, "block-nbt").mkdirs() + File(this.dataFolder, "item-nbt").mkdirs() + + // register asyncManager + logger.info("Registering asyncManager...") + asyncManager = AsyncManager(this) + // register listeners + logger.info("Registering listeners...") Bukkit.getPluginManager().registerEvents(ForternListener(this), this) - logger.info("Registering commands...") // register commands - Bukkit.getPluginCommand("helper")?.setExecutor(HelperCommand(this, adventure)) + logger.info("Registering commands...") + Bukkit.getPluginCommand("helper")!!.setExecutor(HelperCommand(this, adventure, asyncManager)) // register placeholders if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) { expansion = ForternExpansion(this) diff --git a/src/main/kotlin/xyz/fortern/forternhelper/async/AsyncManager.kt b/src/main/kotlin/xyz/fortern/forternhelper/async/AsyncManager.kt new file mode 100644 index 0000000..d70bb76 --- /dev/null +++ b/src/main/kotlin/xyz/fortern/forternhelper/async/AsyncManager.kt @@ -0,0 +1,88 @@ +package xyz.fortern.forternhelper.async + +import org.bukkit.Bukkit +import org.bukkit.plugin.java.JavaPlugin +import java.util.concurrent.CompletableFuture +import java.util.logging.Level + +class AsyncManager( + private val plugin: JavaPlugin, +) { + private val tasks: MutableSet = HashSet() + + init { + // sync + Bukkit.getScheduler().runTaskTimer(plugin, Runnable { + val iterator = tasks.iterator() + while (iterator.hasNext()) { + val asyncTaskTracker = iterator.next() + try { + val result = asyncTaskTracker.tick() + if (result == AsyncTask.Result.RUNNING) { + continue + } else if (result == AsyncTask.Result.TIMEOUT) { + plugin.logger.warning("Async task timed out: ${asyncTaskTracker.info}") + } + } catch (t: Throwable) { + plugin.logger.log(Level.SEVERE, "Error while ticking AsyncManager", t) + } + iterator.remove() + } + }, 0, 0) + } + + /** + * 添加一个异步任务[asyncFun],该异步任务结束后,会在主线程同步执行另一个任务[syncFun]。 + * 需要超时时间[timeout]。 + * 此方法应当在主线程调用。 + */ + fun execInMainAfterAsync(info: String, timeout: Int, asyncFun: () -> Unit, syncFun: () -> Unit) { + val future = CompletableFuture() + val bukkitTask = Bukkit.getScheduler().runTaskAsynchronously(plugin, Runnable { + try { + asyncFun() + future.complete(null) + } catch (t: Throwable) { + future.completeExceptionally(t) + } + }) + val asyncTaskTracker = AsyncTask(bukkitTask.taskId, info, future, timeout, syncFun) + tasks.add(asyncTaskTracker) + } +} + +class AsyncTask( + val id: Int, + val info: String, + val future: CompletableFuture, + var timeout: Int, + val syncFun: () -> Unit, +) { + /** + * 每tick执行一次。 返回 [Result.TIMEOUT] 如果超时; 返回 [Result.TIMEOUT] 如果正在运行; 返回 [Result.DONE] 如果完成。 + * + * @throws Throwable 当异步任务或同步任务执行出现异常时,抛出那个异常 + */ + @Throws(Throwable::class) + fun tick(): Result { + if (future.isDone) { + if (!future.isCompletedExceptionally) { + syncFun.invoke() + } else { + throw future.exceptionNow() + } + return Result.DONE + } + timeout-- + if (timeout < 0) { + future.cancel(true) + return Result.TIMEOUT + } + return Result.RUNNING + } + + enum class Result { + RUNNING, DONE, TIMEOUT + } +} + diff --git a/src/main/kotlin/xyz/fortern/forternhelper/command/HelperCommand.kt b/src/main/kotlin/xyz/fortern/forternhelper/command/HelperCommand.kt index 920a206..50e6708 100644 --- a/src/main/kotlin/xyz/fortern/forternhelper/command/HelperCommand.kt +++ b/src/main/kotlin/xyz/fortern/forternhelper/command/HelperCommand.kt @@ -11,14 +11,16 @@ import org.bukkit.command.CommandSender import org.bukkit.command.TabExecutor import org.bukkit.entity.Player import org.bukkit.plugin.java.JavaPlugin +import xyz.fortern.forternhelper.async.AsyncManager import java.io.File import java.io.FileReader +import java.util.logging.Level class HelperCommand( private val plugin: JavaPlugin, private val adventure: BukkitAudiences, - - ) : TabExecutor { + private val asyncManager: AsyncManager, +) : TabExecutor { private val subCommands: List = listOf("loadlevel") private val helpMessages = listOf( Component.text("Minehunt v${plugin.description.version}", NamedTextColor.GREEN), @@ -97,16 +99,48 @@ class HelperCommand( val i = args[1] val isNbt = if (args.size > 2) args[2] == "nbt" else false val itemNbtDir = File(plugin.dataFolder, "item-nbt") - val nbt = if (isNbt) { - val file = File(itemNbtDir, "${i}.nbt") - if (!file.exists()) return - NBT.readFile(file) - } else { - val file = File(itemNbtDir, "${i}.txt") - if (!file.exists()) return - NBT.parseNBT(FileReader(file).readAllAsString()) + + var readWriteNBT: ReadWriteNBT? = null + var fileExists = true + var parse = true + + asyncManager.execInMainAfterAsync("read item nbt in helper command", 20, run@{ + if (isNbt) { + val file = File(itemNbtDir, "${i}.nbt") + if (!file.exists()) { + fileExists = false + return@run + } + try { + readWriteNBT = NBT.readFile(file) + } catch (ex: Exception) { + plugin.logger.log(Level.WARNING, "Error reading nbt", ex) + parse = false + } + } else { + val file = File(itemNbtDir, "${i}.txt") + if (!file.exists()) { + fileExists = false + return@run + } + try { + readWriteNBT = NBT.parseNBT(FileReader(file).readAllAsString()) + } catch (ex: Exception) { + plugin.logger.log(Level.WARNING, "Error reading nbt", ex) + parse = false + } + } + }) { + if (!fileExists) { + adventure.sender(sender).sendMessage(Component.text("文件不存在")) + } else if (!parse) { + adventure.sender(sender).sendMessage(Component.text("文件解析错误")) + } else { + if (readWriteNBT != null) { + sender.inventory.addItem(NBT.itemStackFromNBT(readWriteNBT)) + } + } } - sender.inventory.addItem(NBT.itemStackFromNBT(nbt)) return } @@ -120,18 +154,45 @@ class HelperCommand( val isNbt = if (args.size > 2) args[2] == "nbt" else false val world = sender.world val blockState = world.getBlockState(0, 128, 0) - val itemNbtDir = File(plugin.dataFolder, "block-nbt") - val readWriteNBT = if (isNbt) { - val file = File(itemNbtDir, "${i}.nbt") - if (!file.exists()) return - NBT.readFile(file) - } else { - val file = File(itemNbtDir, "${i}.txt") - if (!file.exists()) return - NBT.parseNBT(FileReader(file).readAllAsString()) - } - NBT.modify(blockState) { nbt: ReadWriteNBT -> - nbt.mergeCompound(readWriteNBT) + val blockNbtDir = File(plugin.dataFolder, "block-nbt") + + var readWriteNBT: ReadWriteNBT? = null + var fileExists = true + var parse = true + + asyncManager.execInMainAfterAsync("read block nbt in helper command", 20, run@{ + if (isNbt) { + val file = File(blockNbtDir, "${i}.nbt") + if (!file.exists()) { + fileExists = false + return@run + } + try { + readWriteNBT = NBT.readFile(file) + } catch (ex: Exception) { + plugin.logger.log(Level.WARNING, "Error reading nbt", ex) + parse = false + } + } else { + val file = File(blockNbtDir, "${i}.txt") + if (!file.exists()) { + fileExists = false + return@run + } + readWriteNBT = NBT.parseNBT(FileReader(file).readAllAsString()) + } + }) { + if (!fileExists) { + adventure.sender(sender).sendMessage(Component.text("文件不存在")) + } else if (!parse) { + adventure.sender(sender).sendMessage(Component.text("文件解析错误")) + } else { + if (readWriteNBT != null) { + NBT.modify(blockState) { nbt: ReadWriteNBT -> + nbt.mergeCompound(readWriteNBT) + } + } + } } return }