Skip to content

Commit

Permalink
[6.2.0][dev] 新增 ProxyPlayer#onQuit 函数,新增 Debounce 和 Throttle 工具
Browse files Browse the repository at this point in the history
  • Loading branch information
Bkm016 committed Oct 3, 2024
1 parent d2bd6c4 commit 3a0979f
Show file tree
Hide file tree
Showing 9 changed files with 384 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -332,4 +332,12 @@ interface ProxyPlayer : ProxyCommandSender {
* @param exp 经验
*/
fun giveExp(exp: Int)

/**
* 注册一个当玩家离开游戏时的回调
* 此方法可以重复调用,但不能取消
*
* @param callback 回调
*/
fun onQuit(callback: Runnable)
}
149 changes: 149 additions & 0 deletions common-util/src/main/kotlin/taboolib/common/function/Debounce.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package taboolib.common.function

import taboolib.common.Inject
import taboolib.common.LifeCycle
import taboolib.common.platform.Awake
import java.util.concurrent.*

@Inject
class DebounceFunction<K, T>(
private val delay: Long,
private val executor: ScheduledExecutorService = Executors.newSingleThreadScheduledExecutor(),
private val action: (K?, T) -> Unit,
private val autoShutdown: Boolean = true
) {
private val globalFuture = ConcurrentHashMap.newKeySet<ScheduledFuture<*>>()
private val futureMap = ConcurrentHashMap<K, ScheduledFuture<*>>()

init {
// 将此实例添加到全局列表中
addDebounceFunction(this)
}

/**
* 全局防抖调用
*
* @param param 要传递给 action 的参数
*/
fun invoke(param: T) {
invoke(null, param)
}

/**
* 针对特定键的防抖调用
*
* @param key 防抖的键,如果为 null 则使用全局防抖
* @param param 要传递给 action 的参数
*/
fun invoke(key: K?, param: T) {
val future = executor.schedule({ action(key, param) }, delay, TimeUnit.MILLISECONDS)
when (key) {
null -> {
globalFuture.forEach { it.cancel(false) }
globalFuture.clear()
globalFuture.add(future)
}

else -> {
futureMap[key]?.cancel(false)
futureMap[key] = future
}
}
}

/**
* 移除指定键的防抖状态
*/
fun removeKey(key: K) {
futureMap.remove(key)?.cancel(false)
}

/**
* 清除所有防抖状态
*/
fun clearAll() {
globalFuture.forEach { it.cancel(false) }
globalFuture.clear()
futureMap.values.forEach { it.cancel(false) }
futureMap.clear()
}

/**
* 关闭执行器
*/
fun shutdown() {
clearAll()
executor.shutdown()
}

private companion object {

// 所有被创建的防抖函数
private val allDebounceFunctions = mutableListOf<DebounceFunction<*, *>>()

@Awake(LifeCycle.DISABLE)
fun onDisable() {
// 关闭所有需要自动关闭的执行器
allDebounceFunctions.forEach { debounceFunction ->
if (debounceFunction.autoShutdown) {
debounceFunction.shutdown()
}
}
// 清空列表
allDebounceFunctions.clear()
}

// 添加防抖函数到列表
fun addDebounceFunction(debounceFunction: DebounceFunction<*, *>) {
allDebounceFunctions.add(debounceFunction)
}
}
}

/**
* 创建防抖函数:
* 可以全局使用,也可以针对特定对象(如玩家)使用。在指定时间内只执行一次函数,如果在这段时间内再次调用函数,则重新计时。
*
* 示例:
* ```kotlin
* // 创建一个 500 毫秒的防抖函数
* val debouncedAction = debounce<Player?, String>(500) { player, message ->
* when (player) {
* null -> println("全局防抖后输出:$message")
* else -> println("玩家 ${player.name} 的防抖后输出:$message")
* }
* }
*
* // 全局使用
* debouncedAction(null, "全局测试1")
* debouncedAction(null, "全局测试2")
*
* // 针对玩家使用
* debouncedAction(player1, "测试1")
* debouncedAction(player1, "测试2")
* debouncedAction(player2, "测试A")
*
* // 等待 600 毫秒
* Thread.sleep(600)
*
* // 最终会输出:
* // 全局防抖后输出:全局测试2
* // 玩家 player1 的防抖后输出:测试2
* // 玩家 player2 的防抖后输出:测试A
* ```
*
* @param K 键类型(可以是 Player 或其他对象类型)
* @param T 参数类型
* @param delay 防抖时间(单位:毫秒)
* @param executor 自定义的执行器,默认使用单线程调度执行器
* @param autoShutdown 是否在插件禁用时自动关闭执行器,默认为 true
*/
fun <K, T> debounce(
delay: Long,
executor: ScheduledExecutorService = Executors.newSingleThreadScheduledExecutor(),
autoShutdown: Boolean = true,
action: (K?, T) -> Unit
): (K?, T) -> Unit {
val debounceFunction = DebounceFunction(delay, executor, action, autoShutdown)
return { key: K?, param: T -> debounceFunction.invoke(key, param) }
}
154 changes: 154 additions & 0 deletions common-util/src/main/kotlin/taboolib/common/function/Throttle.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package taboolib.common.function

import taboolib.common.Inject
import taboolib.common.LifeCycle
import taboolib.common.platform.Awake
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean

@Inject
class ThrottleFunction<K, T>(
private val delay: Long,
private val executor: ScheduledExecutorService = Executors.newSingleThreadScheduledExecutor(),
private val action: (K?, T) -> Unit,
private val autoShutdown: Boolean = true
) {
private val globalThrottle = AtomicBoolean(false)
private val throttleMap = ConcurrentHashMap<K, AtomicBoolean>()

init {
// 将此实例添加到全局列表中
addThrottleFunction(this)
}

/**
* 全局节流调用
*
* @param param 要传递给 action 的参数
*/
fun invoke(param: T) {
invoke(null, param)
}

/**
* 针对特定键的节流调用
*
* @param key 节流的键,如果为 null 则使用全局节流
* @param param 要传递给 action 的参数
*/
fun invoke(key: K?, param: T) {
val isThrottled = when (key) {
null -> globalThrottle
else -> throttleMap.computeIfAbsent(key) { AtomicBoolean(false) }
}

if (isThrottled.compareAndSet(false, true)) {
action(key, param)
executor.schedule({ isThrottled.set(false) }, delay, TimeUnit.MILLISECONDS)
}
}

/**
* 移除指定键的节流状态
*/
fun removeKey(key: K) {
throttleMap.remove(key)
}

/**
* 清除所有节流状态
*/
fun clearAll() {
globalThrottle.set(false)
throttleMap.clear()
}

/**
* 关闭执行器
*/
fun shutdown() {
executor.shutdown()
}

private companion object {

// 所有被创建的节流函数
private val allThrottleFunctions = mutableListOf<ThrottleFunction<*, *>>()

@Awake(LifeCycle.DISABLE)
fun onDisable() {
// 关闭所有需要自动关闭的执行器
allThrottleFunctions.forEach { throttleFunction ->
if (throttleFunction.autoShutdown) {
throttleFunction.shutdown()
}
}
// 清空列表
allThrottleFunctions.clear()
}

// 添加节流函数到列表
fun addThrottleFunction(throttleFunction: ThrottleFunction<*, *>) {
allThrottleFunctions.add(throttleFunction)
}
}
}

/**
* 创建节流函数:
* 可以全局使用,也可以针对特定对象(如玩家)使用。在指定时间内只执行一次函数,如果在这段时间内再次调用函数,则忽略该调用。
*
* 示例:
* ```kotlin
* // 创建一个 500 毫秒的节流函数
* val throttledAction = throttle<Player?, String>(500) { player, message ->
* when (player) {
* null -> println("全局节流后输出:$message")
* else -> println("玩家 ${player.name} 的节流后输出:$message")
* }
* }
*
* // 全局使用
* throttledAction(null, "全局测试1")
* throttledAction(null, "全局测试2")
*
* // 针对玩家使用
* throttledAction(player1, "测试1")
* throttledAction(player1, "测试2")
* throttledAction(player2, "测试A")
*
* // 等待 600 毫秒
* Thread.sleep(600)
*
* // 再次调用
* throttledAction(null, "全局测试3")
* throttledAction(player1, "测试3")
* throttledAction(player2, "测试B")
*
* // 最终会输出:
* // 全局节流后输出:全局测试1
* // 玩家 player1 的节流后输出:测试1
* // 玩家 player2 的节流后输出:测试A
* // 全局节流后输出:全局测试3
* // 玩家 player1 的节流后输出:测试3
* // 玩家 player2 的节流后输出:测试B
* ```
*
* @param K 键类型(可以是 Player 或其他对象类型)
* @param T 参数类型
* @param delay 节流时间(单位:毫秒)
* @param executor 自定义的执行器,默认使用单线程调度执行器
* @param autoShutdown 是否在插件禁用时自动关闭执行器,默认为 true
*/
fun <K, T> throttle(
delay: Long,
executor: ScheduledExecutorService = Executors.newSingleThreadScheduledExecutor(),
autoShutdown: Boolean = true,
action: (K?, T) -> Unit
): (K?, T) -> Unit {
val throttleFunction = ThrottleFunction(delay, executor, action, autoShutdown)
return { key: K?, param: T -> throttleFunction.invoke(key, param) }
}
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
group=taboolib
version=6.2.0-beta15
version=6.2.0-beta16
kotlin.incremental=true
kotlin.incremental.java=true
kotlin.caching.enabled=true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import taboolib.platform.type.AfyBrokerPlayer
@PlatformSide(Platform.AFYBROKER)
class AfyBrokerAdapter : PlatformAdapter {


override fun console(): ProxyCommandSender {
return AfyBrokerCommandSender
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import taboolib.common.util.Location
import taboolib.common.util.Vector
import java.net.InetSocketAddress
import java.util.*
import java.util.concurrent.CopyOnWriteArrayList

/**
* TabooLib
Expand All @@ -30,7 +31,7 @@ class AfyBrokerPlayer(val player: BrokerPlayer) : ProxyPlayer {
override val name: String
get() = player.name

override val address: InetSocketAddress?
override val address: InetSocketAddress
get() = error("Unsupported")

override val uniqueId: UUID
Expand Down Expand Up @@ -346,4 +347,8 @@ class AfyBrokerPlayer(val player: BrokerPlayer) : ProxyPlayer {
override fun giveExp(exp: Int) {
error("Unsupported")
}

override fun onQuit(callback: Runnable) {
error("Unsupported")
}
}
Loading

0 comments on commit 3a0979f

Please sign in to comment.