Skip to content

Commit

Permalink
misc: 调整皮肤获取机制
Browse files Browse the repository at this point in the history
    * 让SingleSkin的过期时间属性重新派上用场。
    * 当从缓存获取的皮肤处于过期状态,将继续联网查询皮肤的过程,并只在查无此人时使用缓存的皮肤
    * 若一个皮肤查无此人但是有存缓存,则缓存的皮肤将重新获得15天的过期期限
  • Loading branch information
MATRIX-feather committed Feb 1, 2024
1 parent bbe0384 commit c6d9243
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 139 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
116 changes: 55 additions & 61 deletions src/main/java/xiamomc/morph/misc/skins/PlayerSkinProvider.java
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -45,27 +34,23 @@ private CompletableFuture<Optional<GameProfile>> 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<GameProfile> 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<GameProfile>(null);

var lookupCallback = new ProfileLookupCallback()
Expand All @@ -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<Optional<GameProfile>> fetchSkinFromProfile(GameProfile profile)
{
if (profile.getProperties().containsKey("textures"))
{
var optional = Optional.of(profile);
skinStore.cache(profile);
skinCache.cache(profile);

return CompletableFuture.completedFuture(optional);
}
Expand All @@ -123,61 +105,73 @@ public CompletableFuture<Optional<GameProfile>> 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<String, CompletableFuture<Optional<GameProfile>>> onGoingRequests = new ConcurrentHashMap<>();

/**
* 尝试获取与给定名称对应的皮肤
* @param profileName 目标名称
* @return
*/
public CompletableFuture<Optional<GameProfile>> 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;
}
}
13 changes: 2 additions & 11 deletions src/main/java/xiamomc/morph/misc/skins/SingleSkin.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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;
Expand Down
74 changes: 74 additions & 0 deletions src/main/java/xiamomc/morph/misc/skins/SkinCache.java
Original file line number Diff line number Diff line change
@@ -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<SkinCacheRoot>
{
@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<GameProfile> 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import java.util.List;

public class SkinStoreRoot
public class SkinCacheRoot
{
@Expose
@SerializedName("profiles")
Expand Down
Loading

0 comments on commit c6d9243

Please sign in to comment.