From f2e06b56e78ad5bfa9192be8bf2d8d0188367208 Mon Sep 17 00:00:00 2001 From: Luck Date: Fri, 16 Sep 2016 21:11:12 +0100 Subject: [PATCH] Cache permission lookups --- .../lucko/luckperms/inject/LPPermissible.java | 60 ++++++++---- .../me/lucko/luckperms/users/BukkitUser.java | 1 + .../me/lucko/luckperms/BungeeListener.java | 31 ++---- .../me/lucko/luckperms/BungeePlayerCache.java | 97 +++++++++++++++++++ .../me/lucko/luckperms/LPBungeePlugin.java | 6 +- .../me/lucko/luckperms/users/BungeeUser.java | 55 ++++++++++- .../api/sponge/LuckPermsUserSubject.java | 21 ++++ .../me/lucko/luckperms/users/SpongeUser.java | 5 +- 8 files changed, 229 insertions(+), 47 deletions(-) create mode 100644 bungee/src/main/java/me/lucko/luckperms/BungeePlayerCache.java diff --git a/bukkit/src/main/java/me/lucko/luckperms/inject/LPPermissible.java b/bukkit/src/main/java/me/lucko/luckperms/inject/LPPermissible.java index 699c53673..db4021423 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/inject/LPPermissible.java +++ b/bukkit/src/main/java/me/lucko/luckperms/inject/LPPermissible.java @@ -46,11 +46,12 @@ public class LPPermissible extends PermissibleBase { private final CommandSender parent; private final LuckPermsPlugin plugin; + @Getter + private final Map luckPermsPermissions = new ConcurrentHashMap<>(); private final List attachments = new LinkedList<>(); private final Map attachmentPermissions = new HashMap<>(); - @Getter - private final Map luckPermsPermissions = new ConcurrentHashMap<>(); + private final Map lookupCache = new HashMap<>(); public LPPermissible(@NonNull CommandSender sender, LuckPermsPlugin plugin) { super(sender); @@ -58,24 +59,10 @@ public class LPPermissible extends PermissibleBase { this.plugin = plugin; } - @Override - public boolean isOp() { - return parent.isOp(); - } - - @Override - public void setOp(boolean value) { - parent.setOp(value); - } - - @Override - public boolean isPermissionSet(@NonNull String name) { - return luckPermsPermissions.containsKey(name.toLowerCase()) || attachmentPermissions.containsKey(name.toLowerCase()); - } - - @Override - public boolean isPermissionSet(@NonNull Permission perm) { - return isPermissionSet(perm.getName()); + public void invalidateCache() { + synchronized (lookupCache) { + lookupCache.clear(); + } } private Tristate getPermissionValue(String permission) { @@ -84,7 +71,18 @@ public class LPPermissible extends PermissibleBase { } permission = permission.toLowerCase(); + synchronized (lookupCache) { + if (lookupCache.containsKey(permission)) { + return lookupCache.get(permission); + } else { + Tristate t = lookupPermissionValue(permission); + lookupCache.put(permission, t); + return t; + } + } + } + private Tristate lookupPermissionValue(String permission) { if (luckPermsPermissions.containsKey(permission)) { return Tristate.fromBoolean(luckPermsPermissions.get(permission)); } @@ -124,6 +122,26 @@ public class LPPermissible extends PermissibleBase { return Tristate.UNDEFINED; } + @Override + public boolean isOp() { + return parent.isOp(); + } + + @Override + public void setOp(boolean value) { + parent.setOp(value); + } + + @Override + public boolean isPermissionSet(@NonNull String name) { + return luckPermsPermissions.containsKey(name.toLowerCase()) || attachmentPermissions.containsKey(name.toLowerCase()); + } + + @Override + public boolean isPermissionSet(@NonNull Permission perm) { + return isPermissionSet(perm.getName()); + } + @Override public boolean hasPermission(@NonNull String name) { Tristate ts = getPermissionValue(name); @@ -239,6 +257,8 @@ public class LPPermissible extends PermissibleBase { for (PermissionAttachment attachment : attachments) { calculateChildPermissions(attachment.getPermissions(), false, attachment); } + + invalidateCache(); } @Override diff --git a/bukkit/src/main/java/me/lucko/luckperms/users/BukkitUser.java b/bukkit/src/main/java/me/lucko/luckperms/users/BukkitUser.java index 4025ee68a..25d79ad70 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/users/BukkitUser.java +++ b/bukkit/src/main/java/me/lucko/luckperms/users/BukkitUser.java @@ -87,6 +87,7 @@ public class BukkitUser extends User { if (!different) return; existing.clear(); + lpPermissible.invalidateCache(); existing.putAll(toApply); if (plugin.getConfiguration().getAutoOp()) { diff --git a/bungee/src/main/java/me/lucko/luckperms/BungeeListener.java b/bungee/src/main/java/me/lucko/luckperms/BungeeListener.java index 9e6b0dc6e..ea56158a4 100644 --- a/bungee/src/main/java/me/lucko/luckperms/BungeeListener.java +++ b/bungee/src/main/java/me/lucko/luckperms/BungeeListener.java @@ -33,8 +33,6 @@ import net.md_5.bungee.api.event.*; import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.event.EventHandler; -import java.util.Collections; -import java.util.Map; import java.util.UUID; import java.util.concurrent.TimeUnit; @@ -51,30 +49,18 @@ public class BungeeListener extends AbstractListener implements Listener { @EventHandler public void onPlayerPermissionCheck(PermissionCheckEvent e) { if (!(e.getSender() instanceof ProxiedPlayer)) { + e.setHasPermission(true); return; } final ProxiedPlayer player = ((ProxiedPlayer) e.getSender()); - final User user = plugin.getUserManager().get(plugin.getUuidCache().getUUID(player.getUniqueId())); - if (user == null) return; - - final String server = player.getServer() == null ? null : (player.getServer().getInfo() == null ? null : player.getServer().getInfo().getName()); - Map local = user.exportNodes( - plugin.getConfiguration().getServer(), - server, - null, - plugin.getConfiguration().getIncludeGlobalPerms(), - true, - Collections.singletonList(e.getPermission()) - ); - - for (Map.Entry en : local.entrySet()) { - if (en.getKey().equalsIgnoreCase(e.getPermission())) { - e.setHasPermission(en.getValue()); - return; - } + BungeePlayerCache playerCache = plugin.getPlayerCache().get(player.getUniqueId()); + if (playerCache == null) { + return; } + + e.setHasPermission(playerCache.getPermissionValue(e.getPermission())); } @EventHandler @@ -116,17 +102,20 @@ public class BungeeListener extends AbstractListener implements Listener { @EventHandler public void onPlayerPostLogin(PostLoginEvent e) { final ProxiedPlayer player = e.getPlayer(); - final User user = plugin.getUserManager().get(plugin.getUuidCache().getUUID(e.getPlayer().getUniqueId())); + final UUID internal = plugin.getUuidCache().getUUID(e.getPlayer().getUniqueId()); + final User user = plugin.getUserManager().get(internal); if (user == null) { plugin.getProxy().getScheduler().schedule(plugin, () -> player.sendMessage(WARN_MESSAGE), 3, TimeUnit.SECONDS); } else { + plugin.getPlayerCache().put(internal, new BungeePlayerCache(plugin, internal, e.getPlayer().getName())); user.refreshPermissions(); } } @EventHandler public void onPlayerQuit(PlayerDisconnectEvent e) { + plugin.getPlayerCache().remove(plugin.getUuidCache().getUUID(e.getPlayer().getUniqueId())); onLeave(e.getPlayer().getUniqueId()); } diff --git a/bungee/src/main/java/me/lucko/luckperms/BungeePlayerCache.java b/bungee/src/main/java/me/lucko/luckperms/BungeePlayerCache.java new file mode 100644 index 000000000..456a6043f --- /dev/null +++ b/bungee/src/main/java/me/lucko/luckperms/BungeePlayerCache.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2016 Lucko (Luck) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms; + +import com.google.common.base.Splitter; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +@RequiredArgsConstructor +public class BungeePlayerCache { + private final LuckPermsPlugin plugin; + private final UUID uuid; + private final String name; + + @Getter + private final Map permissions = new ConcurrentHashMap<>(); + private final Map lookupCache = new HashMap<>(); + + public void invalidateCache() { + synchronized (lookupCache) { + lookupCache.clear(); + } + } + + public boolean getPermissionValue(String permission) { + if (plugin.getConfiguration().getDebugPermissionChecks()) { + plugin.getLog().info("Checking if " + name + " has permission: " + permission); + } + + permission = permission.toLowerCase(); + synchronized (lookupCache) { + if (lookupCache.containsKey(permission)) { + return lookupCache.get(permission); + } else { + boolean t = lookupPermissionValue(permission); + lookupCache.put(permission, t); + return t; + } + } + } + + private boolean lookupPermissionValue(String permission) { + if (permissions.containsKey(permission)) { + return permissions.get(permission); + } + + if (plugin.getConfiguration().getApplyWildcards()) { + if (permissions.containsKey("*")) { + return permissions.get("*"); + } + if (permissions.containsKey("'*'")) { + return permissions.get("'*'"); + } + + String node = ""; + Iterable permParts = Splitter.on('.').split(permission); + for (String s : permParts) { + if (node.equals("")) { + node = s; + } else { + node = node + "." + s; + } + + if (permissions.containsKey(node + ".*")) { + return permissions.get(node + ".*"); + } + } + } + + return false; + } +} diff --git a/bungee/src/main/java/me/lucko/luckperms/LPBungeePlugin.java b/bungee/src/main/java/me/lucko/luckperms/LPBungeePlugin.java index b6fbd1393..828f4c298 100644 --- a/bungee/src/main/java/me/lucko/luckperms/LPBungeePlugin.java +++ b/bungee/src/main/java/me/lucko/luckperms/LPBungeePlugin.java @@ -47,16 +47,14 @@ import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.plugin.Plugin; import java.io.File; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.UUID; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @Getter public class LPBungeePlugin extends Plugin implements LuckPermsPlugin { + private final Map playerCache = new ConcurrentHashMap<>(); private final Set ignoringLogs = ConcurrentHashMap.newKeySet(); private LPConfiguration configuration; private UserManager userManager; diff --git a/bungee/src/main/java/me/lucko/luckperms/users/BungeeUser.java b/bungee/src/main/java/me/lucko/luckperms/users/BungeeUser.java index 762aa6e02..634125fad 100644 --- a/bungee/src/main/java/me/lucko/luckperms/users/BungeeUser.java +++ b/bungee/src/main/java/me/lucko/luckperms/users/BungeeUser.java @@ -22,21 +22,74 @@ package me.lucko.luckperms.users; +import me.lucko.luckperms.BungeePlayerCache; import me.lucko.luckperms.LPBungeePlugin; +import me.lucko.luckperms.api.event.events.UserPermissionRefreshEvent; +import me.lucko.luckperms.api.implementation.internal.UserLink; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import java.util.Collections; +import java.util.Map; import java.util.UUID; public class BungeeUser extends User { + private final LPBungeePlugin plugin; + BungeeUser(UUID uuid, LPBungeePlugin plugin) { super(uuid, plugin); + this.plugin = plugin; } BungeeUser(UUID uuid, String username, LPBungeePlugin plugin) { super(uuid, username, plugin); + this.plugin = plugin; } @Override public void refreshPermissions() { - // Do nothing. Permissions are applied when needed in a listener. + ProxiedPlayer player = plugin.getProxy().getPlayer(plugin.getUuidCache().getExternalUUID(getUuid())); + if (player == null) { + return; + } + + BungeePlayerCache playerCache = plugin.getPlayerCache().get(getUuid()); + if (playerCache == null) { + return; + } + + final String server = player.getServer() == null ? null : (player.getServer().getInfo() == null ? null : player.getServer().getInfo().getName()); + + // Calculate the permissions that should be applied. This is done async. + Map toApply = exportNodes( + plugin.getConfiguration().getServer(), + server, + null, + plugin.getConfiguration().getIncludeGlobalPerms(), + true, + Collections.emptyList() + ); + + Map existing = playerCache.getPermissions(); + + boolean different = false; + if (toApply.size() != existing.size()) { + different = true; + } else { + for (Map.Entry e : existing.entrySet()) { + if (toApply.containsKey(e.getKey()) && toApply.get(e.getKey()) == e.getValue()) { + continue; + } + different = true; + break; + } + } + + if (!different) return; + + existing.clear(); + playerCache.invalidateCache(); + existing.putAll(toApply); + + plugin.getApiProvider().fireEventAsync(new UserPermissionRefreshEvent(new UserLink(this))); } } diff --git a/sponge/src/main/java/me/lucko/luckperms/api/sponge/LuckPermsUserSubject.java b/sponge/src/main/java/me/lucko/luckperms/api/sponge/LuckPermsUserSubject.java index 73a4f3274..f3ad2054c 100644 --- a/sponge/src/main/java/me/lucko/luckperms/api/sponge/LuckPermsUserSubject.java +++ b/sponge/src/main/java/me/lucko/luckperms/api/sponge/LuckPermsUserSubject.java @@ -29,6 +29,7 @@ import me.lucko.luckperms.users.User; import org.spongepowered.api.service.context.Context; import org.spongepowered.api.util.Tristate; +import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -44,11 +45,20 @@ public class LuckPermsUserSubject extends LuckPermsSubject { @Getter private final Map permissionCache = new ConcurrentHashMap<>(); + @Getter + private final Map lookupCache = new HashMap<>(); + private LuckPermsUserSubject(User user, LuckPermsService service) { super(user, service); this.user = user; } + public void invalidateCache() { + synchronized (lookupCache) { + lookupCache.clear(); + } + } + // TODO don't ignore context @Override public Tristate getPermissionValue(@NonNull Set contexts, @NonNull String permission) { @@ -57,7 +67,18 @@ public class LuckPermsUserSubject extends LuckPermsSubject { } permission = permission.toLowerCase(); + synchronized (lookupCache) { + if (lookupCache.containsKey(permission)) { + return lookupCache.get(permission); + } else { + Tristate t = lookupPermissionValue(contexts, permission); + lookupCache.put(permission, t); + return t; + } + } + } + private Tristate lookupPermissionValue(Set contexts, String permission) { if (permissionCache.containsKey(permission)) { return Tristate.fromBoolean(permissionCache.get(permission)); } diff --git a/sponge/src/main/java/me/lucko/luckperms/users/SpongeUser.java b/sponge/src/main/java/me/lucko/luckperms/users/SpongeUser.java index 264a9ddce..6c5ef6f94 100644 --- a/sponge/src/main/java/me/lucko/luckperms/users/SpongeUser.java +++ b/sponge/src/main/java/me/lucko/luckperms/users/SpongeUser.java @@ -25,6 +25,7 @@ package me.lucko.luckperms.users; import me.lucko.luckperms.LPSpongePlugin; import me.lucko.luckperms.api.event.events.UserPermissionRefreshEvent; import me.lucko.luckperms.api.implementation.internal.UserLink; +import me.lucko.luckperms.api.sponge.LuckPermsUserSubject; import me.lucko.luckperms.api.sponge.collections.UserCollection; import java.util.Collections; @@ -62,7 +63,8 @@ class SpongeUser extends User { ); try { - Map existing = uc.getUsers().get(getUuid()).getPermissionCache(); + LuckPermsUserSubject us = uc.getUsers().get(getUuid()); + Map existing = us.getPermissionCache(); boolean different = false; if (toApply.size() != existing.size()) { @@ -80,6 +82,7 @@ class SpongeUser extends User { if (!different) return; existing.clear(); + us.invalidateCache(); existing.putAll(toApply); plugin.getApiProvider().fireEventAsync(new UserPermissionRefreshEvent(new UserLink(this)));