diff --git a/common/src/main/java/me/lucko/luckperms/common/model/NodeMap.java b/common/src/main/java/me/lucko/luckperms/common/model/NodeMap.java index ea6a99e5..948fe2fa 100644 --- a/common/src/main/java/me/lucko/luckperms/common/model/NodeMap.java +++ b/common/src/main/java/me/lucko/luckperms/common/model/NodeMap.java @@ -57,7 +57,11 @@ import javax.annotation.Nullable; /** * A map of nodes held by a {@link PermissionHolder}. * - * Each holder has two of these maps, one for enduring and transient nodes. + *

Permissions are stored in Multimaps, with the context of the node being the key, and the actual Node object being + * the value. The keys (context sets) are ordered according to their weight {@link ContextSetComparator}, and the values + * are ordered according to the priority of the node, according to {@link NodeComparator}.

+ * + *

Each holder has two of these maps, one for enduring and transient nodes.

*/ public final class NodeMap { diff --git a/common/src/main/java/me/lucko/luckperms/common/model/PermissionHolder.java b/common/src/main/java/me/lucko/luckperms/common/model/PermissionHolder.java index e3dcddf6..27c64a8b 100644 --- a/common/src/main/java/me/lucko/luckperms/common/model/PermissionHolder.java +++ b/common/src/main/java/me/lucko/luckperms/common/model/PermissionHolder.java @@ -44,11 +44,9 @@ import me.lucko.luckperms.common.caching.HolderCachedData; import me.lucko.luckperms.common.caching.handlers.StateListener; import me.lucko.luckperms.common.caching.type.MetaAccumulator; import me.lucko.luckperms.common.config.ConfigKeys; -import me.lucko.luckperms.common.contexts.ContextSetComparator; import me.lucko.luckperms.common.node.ImmutableLocalizedNode; import me.lucko.luckperms.common.node.InheritanceInfo; import me.lucko.luckperms.common.node.MetaType; -import me.lucko.luckperms.common.node.NodeComparator; import me.lucko.luckperms.common.node.NodeFactory; import me.lucko.luckperms.common.node.NodeTools; import me.lucko.luckperms.common.node.NodeWithContextComparator; @@ -79,63 +77,74 @@ import java.util.stream.Collectors; /** * Represents an object that can hold permissions, (a user or group) * - *

Permissions are stored in Multimaps, with the context of the node being the key, and the actual Node object being - * the value. The keys (context sets) are ordered according to their weight {@link ContextSetComparator}, and the values - * are ordered according to the priority of the node, according to {@link NodeComparator}.

+ *

Data is stored in {@link NodeMap}s. A holder has two of these, one for + * enduring nodes and one for transient nodes.

* - *

This class also provides a number of methods to perform inheritance lookups. These lookup methods initially use - * Lists of nodes populated with the inheritance tree. Nodes at the start of this list have priority over nodes at the - * end. Nodes higher up the tree appear at the end of these lists. In order to remove duplicate elements, the lists are - * flattened using the methods in {@link NodeTools}. This is significantly faster than trying to prevent duplicates - * throughout the process of accumulation, and reduces the need for too much caching.

+ *

This class provides a number of methods to perform inheritance lookups. + * These lookup methods initially use Lists of nodes populated with the + * inheritance tree. Nodes at the start of this list have priority over nodes at + * the end. Nodes higher up the tree appear at the end of these lists. In order + * to remove duplicate elements, the lists are flattened using the methods in + * {@link NodeTools}. This is significantly faster than trying to prevent + * duplicates throughout the process of accumulation, and reduces the need for + * too much caching.

* - *

Cached state is avoided in these instances to cut down on memory footprint. The nodes are stored indexed to the - * contexts they apply in, so doing context specific querying should be fast. Caching would be ineffective here, due to - * the potentially vast amount of contexts being used by nodes, and the potential for very large inheritance trees.

+ *

Cached state is avoided in these instances to cut down on memory + * footprint. The nodes are stored indexed to the contexts they apply in, so + * doing context specific querying should be fast. Caching would be ineffective + * here, due to the potentially vast amount of contexts being used by nodes, + * and the potential for very large inheritance trees.

*/ public abstract class PermissionHolder { /** - * The name of this PermissionHolder object. + * The name of this object. * - *

Used to prevent circular inheritance issues.

+ *

Used as a base for identifying permission holding objects. Also acts + * as a method for preventing circular inheritance issues.

* - *

For users, this value is a String representation of their {@link User#getUuid()}. For groups, it's just the - * {@link Group#getName()}.

+ * @see User#getUuid() + * @see Group#getName() + * @see #getObjectName() */ private final String objectName; /** * Reference to the main plugin instance + * @see #getPlugin() */ private final LuckPermsPlugin plugin; /** * The holders persistent nodes. + * + *

These (unlike transient nodes) are saved to the storage backing.

+ * + * @see #getEnduringData() */ private final NodeMap enduringNodes = new NodeMap(this); /** * The holders transient nodes. * - *

These are nodes which are never stored or persisted to a file, and only - * last until the end of the objects lifetime. (for a group, that's when the server stops, and for a user, it's when - * they log out, or get unloaded.)

+ *

These are nodes which are never stored or persisted to a file, and + * only last until the end of the objects lifetime. (for a group, that's + * when the server stops, and for a user, it's when they log out, or get + * unloaded.)

+ * + * @see #getTransientData() */ private final NodeMap transientNodes = new NodeMap(this); /** - * Caches the holders weight lookup + * Caches the holders weight + * @see #getWeight() */ - private final Cache weightCache = new Cache() { - @Override - protected OptionalInt supply() { - return calculateWeight(); - } - }; + private final Cache weightCache = WeightCache.getFor(this); /** * Lock used by Storage implementations to prevent concurrent read/writes + * @see #getIoLock() */ private final Lock ioLock = new ReentrantLock(); @@ -1003,34 +1012,6 @@ public abstract class PermissionHolder { return this.weightCache.get(); } - private OptionalInt calculateWeight() { - if (this.getType().isUser()) return OptionalInt.empty(); - - boolean seen = false; - int best = 0; - for (Node n : getOwnNodes(ImmutableContextSet.empty())) { - Integer weight = NodeFactory.parseWeightNode(n.getPermission()); - if (weight == null) { - continue; - } - - if (!seen || weight > best) { - seen = true; - best = weight; - } - } - OptionalInt weight = seen ? OptionalInt.of(best) : OptionalInt.empty(); - - if (!weight.isPresent()) { - Integer w = this.plugin.getConfiguration().get(ConfigKeys.GROUP_WEIGHTS).get(getObjectName().toLowerCase()); - if (w != null) { - weight = OptionalInt.of(w); - } - } - - return weight; - } - public Set getGroupReferences() { return getOwnNodes().stream() .filter(Node::isGroupNode) diff --git a/common/src/main/java/me/lucko/luckperms/common/model/WeightCache.java b/common/src/main/java/me/lucko/luckperms/common/model/WeightCache.java new file mode 100644 index 00000000..5b033f08 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/model/WeightCache.java @@ -0,0 +1,89 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * 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.common.model; + +import me.lucko.luckperms.api.Node; +import me.lucko.luckperms.api.context.ImmutableContextSet; +import me.lucko.luckperms.common.buffers.Cache; +import me.lucko.luckperms.common.config.ConfigKeys; +import me.lucko.luckperms.common.node.NodeFactory; + +import java.util.Map; +import java.util.OptionalInt; + +/** + * Cache instance to supply the weight of a {@link PermissionHolder}. + */ +public class WeightCache extends Cache { + private static final Cache NULL = new Cache() { + @Override + protected OptionalInt supply() { + return OptionalInt.empty(); + } + }; + + public static Cache getFor(PermissionHolder holder) { + if (holder.getType().isUser()) { + return NULL; + } + + return new WeightCache(((Group) holder)); + } + + private final Group group; + + private WeightCache(Group group) { + this.group = group; + } + + @Override + protected OptionalInt supply() { + boolean seen = false; + int best = 0; + for (Node n : this.group.getOwnNodes(ImmutableContextSet.empty())) { + Integer weight = NodeFactory.parseWeightNode(n.getPermission()); + if (weight == null) { + continue; + } + + if (!seen || weight > best) { + seen = true; + best = weight; + } + } + OptionalInt weight = seen ? OptionalInt.of(best) : OptionalInt.empty(); + + if (!weight.isPresent()) { + Map configWeights = this.group.getPlugin().getConfiguration().get(ConfigKeys.GROUP_WEIGHTS); + Integer w = configWeights.get(this.group.getObjectName().toLowerCase()); + if (w != null) { + weight = OptionalInt.of(w); + } + } + + return weight; + } +}