@@ -14,11 +14,13 @@ import taboolib.library.reflex.Reflex.Companion.invokeMethod
1414import taboolib.library.xseries.XMaterial
1515import taboolib.module.nms.MinecraftVersion
1616import taboolib.platform.util.BukkitSkull
17+ import taboolib.common.platform.function.submit
1718import trplugins.menu.module.internal.hook.HookPlugin
1819import java.io.BufferedReader
1920import java.io.InputStreamReader
2021import java.net.URL
2122import java.util.*
23+ import java.util.concurrent.ConcurrentHashMap
2224
2325/* *
2426 * @author Arasple
@@ -43,6 +45,16 @@ object Heads {
4345 private val NAME = if (MinecraftVersion .major >= 1.20 ) " name" else " getName"
4446 private val USE_PROFILE = runCatching { OfflinePlayer ::class .java.getDeclaredMethod(" getPlayerProfile" ) }.isSuccess
4547
48+ // 异步材质缓存: 玩家名 -> 材质URL
49+ private val textureCache = ConcurrentHashMap <String , String >()
50+ // 正在异步加载中的玩家名集合,防止重复请求
51+ private val loading = ConcurrentHashMap .newKeySet<String >()
52+ // 获取失败计数: 玩家名 -> 失败次数
53+ private val failedCount = ConcurrentHashMap <String , Int >()
54+ // 失败超过3次后加入黑名单,不再请求
55+ private val blacklisted = ConcurrentHashMap .newKeySet<String >()
56+ private const val MAX_RETRIES = 3
57+
4658 fun cacheSize (): Int {
4759 return CACHED_SKULLS .size
4860 }
@@ -87,7 +99,7 @@ object Heads {
8799 if (player != null ) {
88100 return getPlayerHead(player)
89101 }
90- val texture = seekTexture (name)
102+ val texture = seekTextureAsync (name)
91103 return if (texture == null ) DEFAULT_HEAD else getCustomHead(texture)
92104 }
93105
@@ -101,7 +113,7 @@ object Heads {
101113 return getCustomHead(texture.getProperty<String >(VALUE )!! )
102114 }
103115 }
104- val texture = seekTexture (player.name)
116+ val texture = seekTextureAsync (player.name)
105117 return if (texture == null ) DEFAULT_HEAD else getCustomHead(texture)
106118 }
107119 }
@@ -119,7 +131,50 @@ object Heads {
119131 return null
120132 }
121133
122- fun seekTexture (name : String ): String? {
134+ /* *
135+ * 非阻塞获取材质:返回缓存值或 null,并在后台异步请求 Mojang API
136+ * 首次调用返回 null(显示默认头颅),下次菜单刷新时从缓存获取
137+ * 失败超过 3 次后将该名称加入黑名单,不再请求
138+ */
139+ private fun seekTextureAsync (name : String ): String? {
140+ val key = name.lowercase()
141+ // 已缓存直接返回
142+ textureCache[key]?.let { return it }
143+ // 已被黑名单标记,不再请求
144+ if (key in blacklisted) return null
145+ // 未在加载中则发起异步请求
146+ if (loading.add(key)) {
147+ submit(async = true ) {
148+ try {
149+ val texture = fetchTexture(name)
150+ if (texture != null ) {
151+ textureCache[key] = texture
152+ failedCount.remove(key)
153+ } else {
154+ recordFailure(key)
155+ }
156+ } catch (_: Exception ) {
157+ recordFailure(key)
158+ } finally {
159+ loading.remove(key)
160+ }
161+ }
162+ }
163+ return null
164+ }
165+
166+ private fun recordFailure (key : String ) {
167+ val count = failedCount.merge(key, 1 ) { old, inc -> old + inc } ? : 1
168+ if (count >= MAX_RETRIES ) {
169+ blacklisted.add(key)
170+ failedCount.remove(key)
171+ }
172+ }
173+
174+ /* *
175+ * 实际执行 HTTP 请求获取材质(仅在异步线程调用)
176+ */
177+ private fun fetchTexture (name : String ): String? {
123178 val user = urlJson(USER_API + name)
124179 if (user != null && user.has(" id" )) {
125180 val uuid = user[" id" ].asString
@@ -140,6 +195,10 @@ object Heads {
140195 return null
141196 }
142197
198+ fun seekTexture (name : String ): String? {
199+ return textureCache[name.lowercase()]
200+ }
201+
143202 private fun urlJson (url : String ): JsonObject ? {
144203 val text = urlText(url)
145204 return if (text.trim { it <= ' ' }.isEmpty()) {
0 commit comments