From c6d9243cc9a795c7adaabdf1a31464de59b46668 Mon Sep 17 00:00:00 2001 From: MATRIX-feather Date: Thu, 1 Feb 2024 13:33:08 +0800 Subject: [PATCH] =?UTF-8?q?misc:=20=E8=B0=83=E6=95=B4=E7=9A=AE=E8=82=A4?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E6=9C=BA=E5=88=B6=20=20=20=20=20*=20?= =?UTF-8?q?=E8=AE=A9SingleSkin=E7=9A=84=E8=BF=87=E6=9C=9F=E6=97=B6?= =?UTF-8?q?=E9=97=B4=E5=B1=9E=E6=80=A7=E9=87=8D=E6=96=B0=E6=B4=BE=E4=B8=8A?= =?UTF-8?q?=E7=94=A8=E5=9C=BA=E3=80=82=20=20=20=20=20*=20=E5=BD=93?= =?UTF-8?q?=E4=BB=8E=E7=BC=93=E5=AD=98=E8=8E=B7=E5=8F=96=E7=9A=84=E7=9A=AE?= =?UTF-8?q?=E8=82=A4=E5=A4=84=E4=BA=8E=E8=BF=87=E6=9C=9F=E7=8A=B6=E6=80=81?= =?UTF-8?q?=EF=BC=8C=E5=B0=86=E7=BB=A7=E7=BB=AD=E8=81=94=E7=BD=91=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E7=9A=AE=E8=82=A4=E7=9A=84=E8=BF=87=E7=A8=8B=EF=BC=8C?= =?UTF-8?q?=E5=B9=B6=E5=8F=AA=E5=9C=A8=E6=9F=A5=E6=97=A0=E6=AD=A4=E4=BA=BA?= =?UTF-8?q?=E6=97=B6=E4=BD=BF=E7=94=A8=E7=BC=93=E5=AD=98=E7=9A=84=E7=9A=AE?= =?UTF-8?q?=E8=82=A4=20=20=20=20=20*=20=E8=8B=A5=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E7=9A=AE=E8=82=A4=E6=9F=A5=E6=97=A0=E6=AD=A4=E4=BA=BA=E4=BD=86?= =?UTF-8?q?=E6=98=AF=E6=9C=89=E5=AD=98=E7=BC=93=E5=AD=98=EF=BC=8C=E5=88=99?= =?UTF-8?q?=E7=BC=93=E5=AD=98=E7=9A=84=E7=9A=AE=E8=82=A4=E5=B0=86=E9=87=8D?= =?UTF-8?q?=E6=96=B0=E8=8E=B7=E5=BE=9715=E5=A4=A9=E7=9A=84=E8=BF=87?= =?UTF-8?q?=E6=9C=9F=E6=9C=9F=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../network/listeners/SpawnPacketHandler.java | 2 + .../morph/misc/skins/PlayerSkinProvider.java | 116 +++++++++--------- .../xiamomc/morph/misc/skins/SingleSkin.java | 13 +- .../xiamomc/morph/misc/skins/SkinCache.java | 74 +++++++++++ ...{SkinStoreRoot.java => SkinCacheRoot.java} | 2 +- .../xiamomc/morph/misc/skins/SkinStore.java | 65 ---------- .../providers/PlayerDisguiseProvider.java | 3 +- 7 files changed, 136 insertions(+), 139 deletions(-) create mode 100644 src/main/java/xiamomc/morph/misc/skins/SkinCache.java rename src/main/java/xiamomc/morph/misc/skins/{SkinStoreRoot.java => SkinCacheRoot.java} (92%) delete mode 100644 src/main/java/xiamomc/morph/misc/skins/SkinStore.java diff --git a/src/main/java/xiamomc/morph/backends/server/renderer/network/listeners/SpawnPacketHandler.java b/src/main/java/xiamomc/morph/backends/server/renderer/network/listeners/SpawnPacketHandler.java index b7db2d8c..24d0342c 100644 --- a/src/main/java/xiamomc/morph/backends/server/renderer/network/listeners/SpawnPacketHandler.java +++ b/src/main/java/xiamomc/morph/backends/server/renderer/network/listeners/SpawnPacketHandler.java @@ -141,6 +141,8 @@ private void refreshStateForPlayer(@Nullable Player player, @NotNull DisplayPara var targetPlayer = Bukkit.getPlayerExact(disguiseName); + //皮肤在其他地方(例如PlayerDisguiseProvider#makeWrapper)中有做获取处理 + //因此这里只根据情况从缓存或者找到的玩家获取皮肤 var cachedProfile = targetPlayer == null ? PlayerSkinProvider.getInstance().getCachedProfile(disguiseName) : NmsRecord.ofPlayer(targetPlayer).gameProfile; diff --git a/src/main/java/xiamomc/morph/misc/skins/PlayerSkinProvider.java b/src/main/java/xiamomc/morph/misc/skins/PlayerSkinProvider.java index 44754633..458d49b3 100644 --- a/src/main/java/xiamomc/morph/misc/skins/PlayerSkinProvider.java +++ b/src/main/java/xiamomc/morph/misc/skins/PlayerSkinProvider.java @@ -1,33 +1,22 @@ package xiamomc.morph.misc.skins; -import com.destroystokyo.paper.profile.PaperAuthenticationService; import com.mojang.authlib.GameProfile; import com.mojang.authlib.GameProfileRepository; import com.mojang.authlib.ProfileLookupCallback; import com.mojang.authlib.exceptions.AuthenticationUnavailableException; -import com.mojang.authlib.minecraft.MinecraftSessionService; import com.mojang.authlib.yggdrasil.ProfileNotFoundException; -import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.minecraft.Util; import net.minecraft.server.MinecraftServer; -import net.minecraft.server.Services; -import net.minecraft.server.players.GameProfileCache; import net.minecraft.world.entity.player.Player; import org.bukkit.Bukkit; import org.jetbrains.annotations.Nullable; -import xiamomc.morph.MorphPlugin; import xiamomc.morph.MorphPluginObject; import xiamomc.morph.misc.NmsRecord; -import xiamomc.pluginbase.Annotations.Initializer; -import java.io.File; -import java.net.Proxy; -import java.net.URI; import java.util.Map; import java.util.Optional; -import java.util.UUID; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReference; public class PlayerSkinProvider extends MorphPluginObject @@ -45,27 +34,23 @@ private CompletableFuture> getProfileAsyncV2(String name) { var executor = Util.PROFILE_EXECUTOR; - var task = CompletableFuture.supplyAsync(() -> - { - return this.fetchProfileV2(name); - }, executor).whenCompleteAsync((optional, throwable) -> - { - }, executor); - - return task; + return CompletableFuture.supplyAsync(() -> this.fetchProfileV2(name), executor) + .whenCompleteAsync((optional, throwable) -> {}, executor); } - private final SkinStore skinStore = new SkinStore(); + private final SkinCache skinCache = new SkinCache(); + /** + * 根据给定的名称搜索对应的Profile(不包含皮肤) + * @apiNote 此方法返回的GameProfile不包含皮肤,若要获取于此对应的皮肤,请使用 {@link PlayerSkinProvider#fetchSkinFromProfile(GameProfile)} + * @param name + * @return + */ private Optional fetchProfileV2(String name) { if (!Player.isValidUsername(name)) return Optional.empty(); - var cached = skinStore.get(name); - if (cached != null) - return Optional.of(cached); - var profileRef = new AtomicReference(null); var lookupCallback = new ProfileLookupCallback() @@ -88,30 +73,27 @@ else if (exception instanceof AuthenticationUnavailableException) else { logger.info("Failed to lookup '%s': '%s'".formatted(profileName, exception.getMessage())); - exception.printStackTrace(); } } }; - GameProfileRepository gameProfileRepository = MinecraftServer.getServer().getProfileRepository(); - gameProfileRepository.findProfilesByNames(new String[]{name}, lookupCallback); + GameProfileRepository profileRepo = MinecraftServer.getServer().getProfileRepository(); + profileRepo.findProfilesByNames(new String[]{name}, lookupCallback); var profile = profileRef.get(); return profile == null ? Optional.empty() : Optional.of(profile); } - @Nullable - public GameProfile getCachedProfile(String name) - { - return skinStore.get(name); - } - + /** + * 通过给定的Profile获取与其对应的皮肤 + * @param profile 目标GameProfile + */ public CompletableFuture> fetchSkinFromProfile(GameProfile profile) { if (profile.getProperties().containsKey("textures")) { var optional = Optional.of(profile); - skinStore.cache(profile); + skinCache.cache(profile); return CompletableFuture.completedFuture(optional); } @@ -123,61 +105,73 @@ public CompletableFuture> fetchSkinFromProfile(GameProfile var result = sessionService.fetchProfile(profile.getId(), true); if (result != null) - skinStore.cache(result.profile()); + skinCache.cache(result.profile()); return result == null ? Optional.of(profile) : Optional.of(result.profile()); }); } } - private static class ProfileMeta + @Nullable + public GameProfile getCachedProfile(String name) { - private final GameProfile profile; - private long lastAccess; - - public ProfileMeta(GameProfile profile, long creationTime) - { - this.profile = profile; - lastAccess = creationTime; - } - - public static ProfileMeta of(GameProfile profile) - { - return new ProfileMeta( - profile, MorphPlugin.getInstance().getCurrentTick() - ); - } + return skinCache.get(name).profileOptional().orElse(null); } - private int performCount; + private final Map>> onGoingRequests = new ConcurrentHashMap<>(); + /** + * 尝试获取与给定名称对应的皮肤 + * @param profileName 目标名称 + * @return + */ public CompletableFuture> fetchSkin(String profileName) { - performCount++; - var player = Bukkit.getPlayerExact(profileName); if (player != null) { var profile = NmsRecord.ofPlayer(player).gameProfile; - skinStore.remove(profileName); + skinCache.cache(profile); + return CompletableFuture.completedFuture(Optional.of(profile)); } - var cachedSkin = getCachedProfile(profileName); - if (cachedSkin != null) - return CompletableFuture.completedFuture(Optional.of(cachedSkin)); + var cachedSkin = skinCache.get(profileName); + + //如果Record的皮肤没有过期并且有值,那么使用此Record + //否则仍然进行获取流程 + if (!cachedSkin.expired() && cachedSkin.profileOptional().isPresent()) + return CompletableFuture.completedFuture(cachedSkin.profileOptional()); - return getProfileAsyncV2(profileName) + //如果此profile有正在进行的请求,那么直接复用 + var prevReq = onGoingRequests.getOrDefault(profileName, null); + if (prevReq != null) + return prevReq; + + var req = getProfileAsyncV2(profileName) .thenCompose(rawProfileOptional -> { - if (rawProfileOptional.isPresent()) + if (rawProfileOptional.isPresent()) //如果查有此人,那么继续流程 { return fetchSkinFromProfile(rawProfileOptional.get()); } + else if (cachedSkin.profileOptional().isPresent()) //否则,如果本地有缓存,那就使用本地缓存 + { + //重新缓存让此皮肤脱离过期状态 + //因为已经查无此人了,没有必要再短时间内重新查询此人的皮肤 + skinCache.cache(cachedSkin.profileOptional().get()); + return CompletableFuture.completedFuture(cachedSkin.profileOptional()); + } else { return CompletableFuture.completedFuture(Optional.empty()); } }); + + req.thenRun(() -> onGoingRequests.remove(profileName)); + + onGoingRequests.put(profileName, req); + + return req; } } diff --git a/src/main/java/xiamomc/morph/misc/skins/SingleSkin.java b/src/main/java/xiamomc/morph/misc/skins/SingleSkin.java index 36b566de..5531c1c7 100644 --- a/src/main/java/xiamomc/morph/misc/skins/SingleSkin.java +++ b/src/main/java/xiamomc/morph/misc/skins/SingleSkin.java @@ -3,17 +3,8 @@ import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; import com.mojang.authlib.GameProfile; -import net.minecraft.Util; -import net.minecraft.commands.arguments.GameProfileArgument; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.NbtIo; import org.jetbrains.annotations.Nullable; -import xiamomc.morph.misc.MorphGameProfile; -import xiamomc.morph.utilities.DisguiseUtils; import xiamomc.morph.utilities.NbtUtils; -import xiamomc.morph.utilities.NmsUtils; - -import java.util.UUID; public class SingleSkin { @@ -36,13 +27,13 @@ public static SingleSkin fromProfile(GameProfile profile) instance.name = profile.getName(); instance.snbt = NbtUtils.getCompoundString(NbtUtils.toCompoundTag(profile)); instance.expiresAt = System.currentTimeMillis() + 15 * 24 * 60 * 60 * 1000; - // D H M S MS + // MS D H M S MS return instance; } @Nullable - public GameProfile toGameProfile() + public GameProfile generateGameProfile() { if (this.snbt == null || this.snbt.equalsIgnoreCase("{}")) return null; diff --git a/src/main/java/xiamomc/morph/misc/skins/SkinCache.java b/src/main/java/xiamomc/morph/misc/skins/SkinCache.java new file mode 100644 index 00000000..66481515 --- /dev/null +++ b/src/main/java/xiamomc/morph/misc/skins/SkinCache.java @@ -0,0 +1,74 @@ +package xiamomc.morph.misc.skins; + +import com.mojang.authlib.GameProfile; +import org.bukkit.Bukkit; +import org.jetbrains.annotations.NotNull; +import xiamomc.morph.storage.MorphJsonBasedStorage; + +import java.util.Optional; + +public class SkinCache extends MorphJsonBasedStorage +{ + @Override + protected @NotNull String getFileName() + { + return "stored_skins.json"; + } + + @Override + protected @NotNull SkinCacheRoot createDefault() + { + return new SkinCacheRoot(); + } + + @Override + protected @NotNull String getDisplayName() + { + return "Server renderer skin store"; + } + + public synchronized void cache(GameProfile profile) + { + drop(profile.getName()); + storingObject.storedSkins.add(SingleSkin.fromProfile(profile)); + + saveConfiguration(); + } + + public synchronized void drop(String name) + { + storingObject.storedSkins.removeIf(ss -> ss.name.equalsIgnoreCase(name)); + + saveConfiguration(); + } + + public synchronized void drop(GameProfile profile) + { + drop(profile.getName()); + } + + public synchronized void drop(SingleSkin singleSkin) + { + storingObject.storedSkins.remove(singleSkin); + + saveConfiguration(); + } + + public record SkinRecord(Optional profileOptional, boolean expired) + { + } + + @NotNull + public SkinRecord get(String name) + { + var single = storingObject.storedSkins.stream().filter(ss -> + ss.name.equalsIgnoreCase(name)).findFirst().orElse(null); + + if (single == null) return new SkinRecord(Optional.empty(), true); + + var profile = single.generateGameProfile(); + return new SkinRecord( + (profile == null ? Optional.empty() : Optional.of(profile)), + System.currentTimeMillis() > single.expiresAt); + } +} diff --git a/src/main/java/xiamomc/morph/misc/skins/SkinStoreRoot.java b/src/main/java/xiamomc/morph/misc/skins/SkinCacheRoot.java similarity index 92% rename from src/main/java/xiamomc/morph/misc/skins/SkinStoreRoot.java rename to src/main/java/xiamomc/morph/misc/skins/SkinCacheRoot.java index bf97eb67..d6384455 100644 --- a/src/main/java/xiamomc/morph/misc/skins/SkinStoreRoot.java +++ b/src/main/java/xiamomc/morph/misc/skins/SkinCacheRoot.java @@ -6,7 +6,7 @@ import java.util.List; -public class SkinStoreRoot +public class SkinCacheRoot { @Expose @SerializedName("profiles") diff --git a/src/main/java/xiamomc/morph/misc/skins/SkinStore.java b/src/main/java/xiamomc/morph/misc/skins/SkinStore.java deleted file mode 100644 index 683f99e3..00000000 --- a/src/main/java/xiamomc/morph/misc/skins/SkinStore.java +++ /dev/null @@ -1,65 +0,0 @@ -package xiamomc.morph.misc.skins; - -import com.mojang.authlib.GameProfile; -import com.mojang.authlib.properties.Property; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import xiamomc.morph.storage.MorphJsonBasedStorage; - -import java.text.DateFormat; -import java.util.Date; -import java.util.concurrent.TimeUnit; - -public class SkinStore extends MorphJsonBasedStorage -{ - @Override - protected @NotNull String getFileName() - { - return "stored_skins.json"; - } - - @Override - protected @NotNull SkinStoreRoot createDefault() - { - return new SkinStoreRoot(); - } - - @Override - protected @NotNull String getDisplayName() - { - return "Server renderer skin store"; - } - - public synchronized void cache(GameProfile profile) - { - storingObject.storedSkins.removeIf(ss -> ss.name.equalsIgnoreCase(profile.getName())); - storingObject.storedSkins.add(SingleSkin.fromProfile(profile)); - - saveConfiguration(); - } - - public void remove(String name) - { - storingObject.storedSkins.removeIf(ss -> ss.name.equalsIgnoreCase(name)); - } - - @Nullable - public GameProfile get(String name) - { - var single = storingObject.storedSkins.stream().filter(ss -> - ss.name.equalsIgnoreCase(name)).findFirst().orElse(null); - - if (single == null) return null; - -/* - if (System.currentTimeMillis() > single.expiresAt) - { - this.addSchedule(() -> storingObject.storedSkins.remove(single)); - - return null; - } -*/ - - return single.toGameProfile(); - } -} diff --git a/src/main/java/xiamomc/morph/providers/PlayerDisguiseProvider.java b/src/main/java/xiamomc/morph/providers/PlayerDisguiseProvider.java index 957c39e6..6123c9fb 100644 --- a/src/main/java/xiamomc/morph/providers/PlayerDisguiseProvider.java +++ b/src/main/java/xiamomc/morph/providers/PlayerDisguiseProvider.java @@ -3,6 +3,7 @@ import com.mojang.authlib.GameProfile; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import net.kyori.adventure.text.Component; +import net.minecraft.Util; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtUtils; import org.bukkit.Bukkit; @@ -87,7 +88,7 @@ public boolean isValid(String rawIdentifier) { if (wrapper.disposed()) return null; - GameProfile outcomingProfile = new GameProfile(UUID.randomUUID(), disguiseMeta.playerDisguiseTargetName); + GameProfile outcomingProfile = new GameProfile(Util.NIL_UUID, disguiseMeta.playerDisguiseTargetName); if (optional.isPresent()) outcomingProfile = optional.get(); wrapper.applySkin(outcomingProfile);