Add lookup cache, option to open self with no args (#115)
Add a cache for the last 10 offline lookups done by OpenInv#matchPlayer(String) to reduce the performance hit of repeat opens Add config option to open self with no-arg command
This commit is contained in:
		@@ -16,6 +16,8 @@
 | 
			
		||||
 | 
			
		||||
package com.lishid.openinv;
 | 
			
		||||
 | 
			
		||||
import com.google.common.cache.Cache;
 | 
			
		||||
import com.google.common.cache.CacheBuilder;
 | 
			
		||||
import com.lishid.openinv.commands.ContainerSettingCommand;
 | 
			
		||||
import com.lishid.openinv.commands.OpenInvCommand;
 | 
			
		||||
import com.lishid.openinv.commands.SearchContainerCommand;
 | 
			
		||||
@@ -28,7 +30,9 @@ import com.lishid.openinv.internal.ISpecialPlayerInventory;
 | 
			
		||||
import com.lishid.openinv.util.ConfigUpdater;
 | 
			
		||||
import com.lishid.openinv.util.LanguageManager;
 | 
			
		||||
import com.lishid.openinv.util.Permissions;
 | 
			
		||||
import com.lishid.openinv.util.StringMetric;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Iterator;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
@@ -37,6 +41,7 @@ import java.util.concurrent.ExecutionException;
 | 
			
		||||
import java.util.concurrent.Future;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
import java.util.function.Predicate;
 | 
			
		||||
import java.util.logging.Level;
 | 
			
		||||
import java.util.stream.Stream;
 | 
			
		||||
import net.md_5.bungee.api.ChatMessageType;
 | 
			
		||||
import net.md_5.bungee.api.chat.TextComponent;
 | 
			
		||||
@@ -52,6 +57,7 @@ import org.bukkit.inventory.Inventory;
 | 
			
		||||
import org.bukkit.inventory.InventoryView;
 | 
			
		||||
import org.bukkit.plugin.PluginManager;
 | 
			
		||||
import org.bukkit.plugin.java.JavaPlugin;
 | 
			
		||||
import org.bukkit.profile.PlayerProfile;
 | 
			
		||||
import org.jetbrains.annotations.NotNull;
 | 
			
		||||
import org.jetbrains.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
@@ -62,6 +68,7 @@ import org.jetbrains.annotations.Nullable;
 | 
			
		||||
 */
 | 
			
		||||
public class OpenInv extends JavaPlugin implements IOpenInv {
 | 
			
		||||
 | 
			
		||||
    private final Cache<String, PlayerProfile> offlineLookUpCache = CacheBuilder.newBuilder().maximumSize(10).build();
 | 
			
		||||
    private final Map<UUID, ISpecialPlayerInventory> inventories = new ConcurrentHashMap<>();
 | 
			
		||||
    private final Map<UUID, ISpecialEnderChest> enderChests = new ConcurrentHashMap<>();
 | 
			
		||||
 | 
			
		||||
@@ -190,6 +197,11 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
 | 
			
		||||
        return this.getConfig().getBoolean("settings.disable-offline-access", false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean noArgsOpensSelf() {
 | 
			
		||||
        return this.getConfig().getBoolean("settings.command.open.no-args-opens-self", false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public @NotNull IAnySilentContainer getAnySilentContainer() {
 | 
			
		||||
        return this.accessor.getAnySilentContainer();
 | 
			
		||||
@@ -311,6 +323,95 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
 | 
			
		||||
        return player;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public @Nullable OfflinePlayer matchPlayer(@NotNull String name) {
 | 
			
		||||
 | 
			
		||||
        // Warn if called on the main thread - if we resort to searching offline players, this may take several seconds.
 | 
			
		||||
        if (Bukkit.getServer().isPrimaryThread()) {
 | 
			
		||||
            this.getLogger().warning("Call to OpenInv#matchPlayer made on the main thread!");
 | 
			
		||||
            this.getLogger().warning("This can cause the server to hang, potentially severely.");
 | 
			
		||||
            this.getLogger().log(Level.WARNING, "Current stack trace", new Throwable("Current stack trace"));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        OfflinePlayer player;
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            UUID uuid = UUID.fromString(name);
 | 
			
		||||
            player = Bukkit.getOfflinePlayer(uuid);
 | 
			
		||||
            // Ensure player is an existing player.
 | 
			
		||||
            if (player.hasPlayedBefore() || player.isOnline()) {
 | 
			
		||||
                return player;
 | 
			
		||||
            }
 | 
			
		||||
            // Return null otherwise.
 | 
			
		||||
            return null;
 | 
			
		||||
        } catch (IllegalArgumentException ignored) {
 | 
			
		||||
            // Not a UUID
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Exact online match first.
 | 
			
		||||
        player = Bukkit.getServer().getPlayerExact(name);
 | 
			
		||||
 | 
			
		||||
        if (player != null) {
 | 
			
		||||
            return player;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Cached offline match.
 | 
			
		||||
        PlayerProfile cachedResult = offlineLookUpCache.getIfPresent(name);
 | 
			
		||||
        if (cachedResult != null && cachedResult.getUniqueId() != null) {
 | 
			
		||||
            player = Bukkit.getOfflinePlayer(cachedResult.getUniqueId());
 | 
			
		||||
            // Ensure player is an existing player.
 | 
			
		||||
            if (player.hasPlayedBefore() || player.isOnline()) {
 | 
			
		||||
                return player;
 | 
			
		||||
            }
 | 
			
		||||
            // Return null otherwise.
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Exact offline match second - ensure offline access works when matchable users are online.
 | 
			
		||||
        player = Bukkit.getServer().getOfflinePlayer(name);
 | 
			
		||||
 | 
			
		||||
        if (player.hasPlayedBefore()) {
 | 
			
		||||
            offlineLookUpCache.put(name, player.getPlayerProfile());
 | 
			
		||||
            return player;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Inexact online match.
 | 
			
		||||
        player = Bukkit.getServer().getPlayer(name);
 | 
			
		||||
 | 
			
		||||
        if (player != null) {
 | 
			
		||||
            return player;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Finally, inexact offline match.
 | 
			
		||||
        float bestMatch = 0;
 | 
			
		||||
        for (OfflinePlayer offline : Bukkit.getServer().getOfflinePlayers()) {
 | 
			
		||||
            if (offline.getName() == null) {
 | 
			
		||||
                // Loaded by UUID only, name has never been looked up.
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            float currentMatch = StringMetric.compareJaroWinkler(name, offline.getName());
 | 
			
		||||
 | 
			
		||||
            if (currentMatch == 1.0F) {
 | 
			
		||||
                return offline;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (currentMatch > bestMatch) {
 | 
			
		||||
                bestMatch = currentMatch;
 | 
			
		||||
                player = offline;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (player != null) {
 | 
			
		||||
            // If a match was found, store it.
 | 
			
		||||
            offlineLookUpCache.put(name, player.getPlayerProfile());
 | 
			
		||||
            return player;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // No players have ever joined the server.
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void unload(@NotNull final OfflinePlayer offline) {
 | 
			
		||||
        setPlayerOffline(offline, OfflineHandler.REMOVE_AND_CLOSE);
 | 
			
		||||
@@ -492,6 +593,45 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
 | 
			
		||||
    void setPlayerOnline(@NotNull Player player) {
 | 
			
		||||
        setPlayerOnline(inventories, player, player::updateInventory);
 | 
			
		||||
        setPlayerOnline(enderChests, player, null);
 | 
			
		||||
 | 
			
		||||
        if (player.hasPlayedBefore()) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // New player may have a name that already points to someone else in lookup cache.
 | 
			
		||||
        String name = player.getName();
 | 
			
		||||
        this.offlineLookUpCache.invalidate(name);
 | 
			
		||||
 | 
			
		||||
        // If no offline matches are mapped, don't hit scheduler.
 | 
			
		||||
        if (this.offlineLookUpCache.size() == 0) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // New player may also be a more exact match than one already in the cache.
 | 
			
		||||
        // I.e. new player "lava1" is a better match for "lava" than "lava123"
 | 
			
		||||
        // Player joins are already quite intensive, so this is run on a delay.
 | 
			
		||||
        this.getServer().getScheduler().runTaskLaterAsynchronously(this, () -> {
 | 
			
		||||
            Iterator<Map.Entry<String, PlayerProfile>> iterator = this.offlineLookUpCache.asMap().entrySet().iterator();
 | 
			
		||||
            while (iterator.hasNext()) {
 | 
			
		||||
                Map.Entry<String, PlayerProfile> entry = iterator.next();
 | 
			
		||||
                String oldMatch = entry.getValue().getName();
 | 
			
		||||
 | 
			
		||||
                // Shouldn't be possible - all profiles should be complete.
 | 
			
		||||
                if (oldMatch == null) {
 | 
			
		||||
                    iterator.remove();
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                String lookup = entry.getKey();
 | 
			
		||||
                float oldMatchScore = StringMetric.compareJaroWinkler(lookup, oldMatch);
 | 
			
		||||
                float newMatchScore = StringMetric.compareJaroWinkler(lookup, name);
 | 
			
		||||
 | 
			
		||||
                // If new match exceeds old match, delete old match.
 | 
			
		||||
                if (newMatchScore > oldMatchScore) {
 | 
			
		||||
                    iterator.remove();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }, 101L); // Odd delay for pseudo load balancing; Player tasks are usually scheduled with full seconds.
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void setPlayerOnline(
 | 
			
		||||
 
 | 
			
		||||
@@ -57,19 +57,23 @@ public class OpenInvCommand implements TabExecutor {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // History management
 | 
			
		||||
        String history = (openInv ? this.openInvHistory : this.openEnderHistory).get(player);
 | 
			
		||||
        String noArgValue;
 | 
			
		||||
        if (plugin.noArgsOpensSelf()) {
 | 
			
		||||
            noArgValue = player.getUniqueId().toString();
 | 
			
		||||
        } else {
 | 
			
		||||
            // History management
 | 
			
		||||
            noArgValue = (openInv ? this.openInvHistory : this.openEnderHistory).get(player);
 | 
			
		||||
 | 
			
		||||
        if (history == null || history.isEmpty()) {
 | 
			
		||||
            history = player.getName();
 | 
			
		||||
            (openInv ? this.openInvHistory : this.openEnderHistory).put(player, history);
 | 
			
		||||
            if (noArgValue == null || noArgValue.isEmpty()) {
 | 
			
		||||
                noArgValue = player.getUniqueId().toString();
 | 
			
		||||
                (openInv ? this.openInvHistory : this.openEnderHistory).put(player, noArgValue);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        final String name;
 | 
			
		||||
 | 
			
		||||
        // Read from history if target is not named
 | 
			
		||||
        if (args.length < 1) {
 | 
			
		||||
            name = history;
 | 
			
		||||
            name = noArgValue;
 | 
			
		||||
        } else {
 | 
			
		||||
            name = args[0];
 | 
			
		||||
        }
 | 
			
		||||
@@ -185,8 +189,10 @@ public class OpenInvCommand implements TabExecutor {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Record the target
 | 
			
		||||
        (openinv ? this.openInvHistory : this.openEnderHistory).put(player, target.getUniqueId().toString());
 | 
			
		||||
        if (!plugin.noArgsOpensSelf()) {
 | 
			
		||||
            // Record the target
 | 
			
		||||
            (openinv ? this.openInvHistory : this.openEnderHistory).put(player, target.getUniqueId().toString());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Create the inventory
 | 
			
		||||
        final ISpecialInventory inv;
 | 
			
		||||
 
 | 
			
		||||
@@ -57,6 +57,9 @@ public record ConfigUpdater(OpenInv plugin) {
 | 
			
		||||
            if (version < 5) {
 | 
			
		||||
                updateConfig4To5();
 | 
			
		||||
            }
 | 
			
		||||
            if (version < 6) {
 | 
			
		||||
                updateConfig5To6();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            plugin.getServer().getScheduler().runTask(plugin, () -> {
 | 
			
		||||
                plugin.saveConfig();
 | 
			
		||||
@@ -65,6 +68,13 @@ public record ConfigUpdater(OpenInv plugin) {
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void updateConfig5To6() {
 | 
			
		||||
        plugin.getServer().getScheduler().runTask(plugin, () -> {
 | 
			
		||||
            plugin.getConfig().set("settings.command.open.no-args-opens-self", false);
 | 
			
		||||
            plugin.getConfig().set("config-version", 6);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void updateConfig4To5() {
 | 
			
		||||
        plugin.getServer().getScheduler().runTask(plugin, () -> {
 | 
			
		||||
            plugin.getConfig().set("settings.disable-offline-access", false);
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user