Compare commits

...

3 Commits

Author SHA1 Message Date
Fortern 4ed6b8ec5a ver 1.3
Build Plugin / Build with Maven (push) Successful in 52s
2026-06-08 14:37:28 +08:00
Fortern d850455b98 nbt tools 2026-06-07 19:32:48 +08:00
Fortern 9c3e597789 异步工具
异步函数通过BukkitScheduler提交
2026-06-07 19:31:03 +08:00
5 changed files with 206 additions and 29 deletions
+15 -1
View File
@@ -9,9 +9,11 @@ jobs:
build: build:
name: Build with Maven name: Build with Maven
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: write
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v5 uses: actions/checkout@v6
- name: Set up JDK 25 and enable Maven cache - name: Set up JDK 25 and enable Maven cache
uses: actions/setup-java@v5 uses: actions/setup-java@v5
@@ -28,8 +30,20 @@ jobs:
- name: Build and package with Maven - name: Build and package with Maven
run: mvn -B package run: mvn -B package
- name: Determine version
id: set-version
shell: bash
run: |
VERSION=${GITHUB_REF#refs/tags/ver/}
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Upload built artifacts - name: Upload built artifacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: maven-artifacts name: maven-artifacts
path: target/spigot/*.jar path: target/spigot/*.jar
- uses: akkuman/gitea-release-action@v1
with:
name: '${{ steps.set-version.outputs.version }} Release'
files: target/spigot/*.jar
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>xyz.fortern</groupId> <groupId>xyz.fortern</groupId>
<artifactId>fortern-helper</artifactId> <artifactId>fortern-helper</artifactId>
<version>1.2</version> <version>1.3</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>fortern-helper</name> <name>fortern-helper</name>
@@ -4,24 +4,38 @@ import me.clip.placeholderapi.expansion.PlaceholderExpansion
import net.kyori.adventure.platform.bukkit.BukkitAudiences import net.kyori.adventure.platform.bukkit.BukkitAudiences
import org.bukkit.Bukkit import org.bukkit.Bukkit
import org.bukkit.plugin.java.JavaPlugin import org.bukkit.plugin.java.JavaPlugin
import xyz.fortern.forternhelper.async.AsyncManager
import xyz.fortern.forternhelper.command.HelperCommand import xyz.fortern.forternhelper.command.HelperCommand
import xyz.fortern.forternhelper.listener.ForternListener import xyz.fortern.forternhelper.listener.ForternListener
import xyz.fortern.forternhelper.placeholder.ForternExpansion import xyz.fortern.forternhelper.placeholder.ForternExpansion
import java.io.File
class Helper : JavaPlugin() { class Helper : JavaPlugin() {
private lateinit var adventure: BukkitAudiences private lateinit var adventure: BukkitAudiences
private lateinit var expansion: PlaceholderExpansion private lateinit var expansion: PlaceholderExpansion
private lateinit var asyncManager: AsyncManager
override fun onEnable() { override fun onEnable() {
// Plugin startup logic // Plugin startup logic
this.adventure = BukkitAudiences.create(this) 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 // register listeners
logger.info("Registering listeners...")
Bukkit.getPluginManager().registerEvents(ForternListener(this), this) Bukkit.getPluginManager().registerEvents(ForternListener(this), this)
logger.info("Registering commands...")
// register commands // register commands
Bukkit.getPluginCommand("helper")?.setExecutor(HelperCommand(this, adventure)) logger.info("Registering commands...")
Bukkit.getPluginCommand("helper")!!.setExecutor(HelperCommand(this, adventure, asyncManager))
// register placeholders // register placeholders
if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) { if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) {
expansion = ForternExpansion(this) expansion = ForternExpansion(this)
@@ -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<AsyncTask> = 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<Void>()
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<Void>,
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
}
}
@@ -11,14 +11,16 @@ import org.bukkit.command.CommandSender
import org.bukkit.command.TabExecutor import org.bukkit.command.TabExecutor
import org.bukkit.entity.Player import org.bukkit.entity.Player
import org.bukkit.plugin.java.JavaPlugin import org.bukkit.plugin.java.JavaPlugin
import xyz.fortern.forternhelper.async.AsyncManager
import java.io.File import java.io.File
import java.io.FileReader import java.io.FileReader
import java.util.logging.Level
class HelperCommand( class HelperCommand(
private val plugin: JavaPlugin, private val plugin: JavaPlugin,
private val adventure: BukkitAudiences, private val adventure: BukkitAudiences,
private val asyncManager: AsyncManager,
) : TabExecutor { ) : TabExecutor {
private val subCommands: List<String> = listOf("loadlevel") private val subCommands: List<String> = listOf("loadlevel")
private val helpMessages = listOf( private val helpMessages = listOf(
Component.text("fortern-helper v${plugin.description.version}", NamedTextColor.GREEN), Component.text("fortern-helper v${plugin.description.version}", NamedTextColor.GREEN),
@@ -97,16 +99,48 @@ class HelperCommand(
val i = args[1] val i = args[1]
val isNbt = if (args.size > 2) args[2] == "nbt" else false val isNbt = if (args.size > 2) args[2] == "nbt" else false
val itemNbtDir = File(plugin.dataFolder, "item-nbt") val itemNbtDir = File(plugin.dataFolder, "item-nbt")
val nbt = if (isNbt) {
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") val file = File(itemNbtDir, "${i}.nbt")
if (!file.exists()) return if (!file.exists()) {
NBT.readFile(file) fileExists = false
return@run
}
try {
readWriteNBT = NBT.readFile(file)
} catch (ex: Exception) {
plugin.logger.log(Level.WARNING, "Error reading nbt", ex)
parse = false
}
} else { } else {
val file = File(itemNbtDir, "${i}.txt") val file = File(itemNbtDir, "${i}.txt")
if (!file.exists()) return if (!file.exists()) {
NBT.parseNBT(FileReader(file).readAllAsString()) 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 return
} }
@@ -120,19 +154,46 @@ class HelperCommand(
val isNbt = if (args.size > 2) args[2] == "nbt" else false val isNbt = if (args.size > 2) args[2] == "nbt" else false
val world = sender.world val world = sender.world
val blockState = world.getBlockState(0, 128, 0) val blockState = world.getBlockState(0, 128, 0)
val itemNbtDir = File(plugin.dataFolder, "block-nbt") val blockNbtDir = File(plugin.dataFolder, "block-nbt")
val readWriteNBT = if (isNbt) {
val file = File(itemNbtDir, "${i}.nbt") var readWriteNBT: ReadWriteNBT? = null
if (!file.exists()) return var fileExists = true
NBT.readFile(file) var parse = true
} else {
val file = File(itemNbtDir, "${i}.txt") asyncManager.execInMainAfterAsync("read block nbt in helper command", 20, run@{
if (!file.exists()) return if (isNbt) {
NBT.parseNBT(FileReader(file).readAllAsString()) 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.modify(blockState) { nbt: ReadWriteNBT ->
nbt.mergeCompound(readWriteNBT) nbt.mergeCompound(readWriteNBT)
} }
}
}
}
return return
} }