1
0
mirror of https://github.com/lucko/LuckPerms.git synced 2025-08-31 01:59:48 +02:00

Improve context manager caching (#4050)

This commit is contained in:
lucko
2025-03-20 19:20:16 +00:00
committed by GitHub
parent 9bc8a61e2d
commit 6b7283ac83
29 changed files with 267 additions and 307 deletions

View File

@@ -25,85 +25,46 @@
package me.lucko.luckperms.bukkit.context;
import com.github.benmanes.caffeine.cache.LoadingCache;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import me.lucko.luckperms.common.cache.LoadingMap;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.context.manager.ContextManager;
import me.lucko.luckperms.common.context.manager.QueryOptionsCache;
import me.lucko.luckperms.common.util.CaffeineFactory;
import net.luckperms.api.context.ImmutableContextSet;
import me.lucko.luckperms.bukkit.inject.permissible.LuckPermsPermissible;
import me.lucko.luckperms.bukkit.inject.permissible.PermissibleInjector;
import me.lucko.luckperms.common.context.manager.DetachedContextManager;
import me.lucko.luckperms.common.context.manager.QueryOptionsSupplier;
import net.luckperms.api.query.OptionKey;
import net.luckperms.api.query.QueryOptions;
import org.bukkit.entity.Player;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class BukkitContextManager extends ContextManager<Player, Player> {
public class BukkitContextManager extends DetachedContextManager<Player, Player> {
public static final OptionKey<Boolean> OP_OPTION = OptionKey.of("op", Boolean.class);
// cache the creation of ContextsCache instances for online players with no expiry
private final LoadingMap<Player, QueryOptionsCache<Player>> onlineSubjectCaches = LoadingMap.of(key -> new QueryOptionsCache<>(key, this));
// cache the creation of ContextsCache instances for offline players with a 1m expiry
private final LoadingCache<Player, QueryOptionsCache<Player>> offlineSubjectCaches = CaffeineFactory.newBuilder()
.expireAfterAccess(1, TimeUnit.MINUTES)
.build(key -> {
QueryOptionsCache<Player> cache = this.onlineSubjectCaches.getIfPresent(key);
if (cache != null) {
return cache;
}
return new QueryOptionsCache<>(key, this);
});
public BukkitContextManager(LPBukkitPlugin plugin) {
super(plugin, Player.class, Player.class);
}
public void onPlayerQuit(Player player) {
this.onlineSubjectCaches.remove(player);
}
@Override
public UUID getUniqueId(Player player) {
return player.getUniqueId();
}
@Override
public QueryOptionsCache<Player> getCacheFor(Player subject) {
if (subject == null) {
throw new NullPointerException("subject");
}
if (subject.isOnline()) {
return this.onlineSubjectCaches.get(subject);
} else {
return this.offlineSubjectCaches.get(subject);
public @Nullable QueryOptionsSupplier getQueryOptionsSupplier(Player subject) {
Objects.requireNonNull(subject, "subject");
LuckPermsPermissible permissible = PermissibleInjector.get(subject);
if (permissible != null) {
return permissible.getQueryOptionsSupplier();
}
return null;
}
@Override
protected void invalidateCache(Player subject) {
QueryOptionsCache<Player> cache = this.onlineSubjectCaches.getIfPresent(subject);
if (cache != null) {
cache.invalidate();
}
cache = this.offlineSubjectCaches.getIfPresent(subject);
if (cache != null) {
cache.invalidate();
}
}
@Override
public QueryOptions formQueryOptions(Player subject, ImmutableContextSet contextSet) {
QueryOptions.Builder queryOptions = this.plugin.getConfiguration().get(ConfigKeys.GLOBAL_QUERY_OPTIONS).toBuilder();
public void customizeQueryOptions(Player subject, QueryOptions.Builder builder) {
if (subject.isOp()) {
queryOptions.option(OP_OPTION, true);
builder.option(OP_OPTION, true);
}
return queryOptions.context(contextSet).build();
}
}

View File

@@ -32,7 +32,7 @@ import me.lucko.luckperms.bukkit.calculator.OpProcessor;
import me.lucko.luckperms.bukkit.calculator.PermissionMapProcessor;
import me.lucko.luckperms.common.cacheddata.result.TristateResult;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.context.manager.QueryOptionsCache;
import me.lucko.luckperms.common.context.manager.QueryOptionsSupplier;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.verbose.event.CheckOrigin;
import net.luckperms.api.query.QueryOptions;
@@ -68,7 +68,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
* "Hot" method calls, (namely #hasPermission) are significantly faster than the base implementation.
*
* This class is **thread safe**. This means that when LuckPerms is installed on the server,
* is is safe to call Player#hasPermission asynchronously.
* it is safe to call Player#hasPermission asynchronously.
*/
public class LuckPermsPermissible extends PermissibleBase {
@@ -93,7 +93,7 @@ public class LuckPermsPermissible extends PermissibleBase {
private final LPBukkitPlugin plugin;
// caches context lookups for the player
private final QueryOptionsCache<Player> queryOptionsSupplier;
private final QueryOptionsSupplier queryOptionsSupplier;
// the players previous permissible. (the one they had before this one was injected)
private PermissibleBase oldPermissible = null;
@@ -110,7 +110,7 @@ public class LuckPermsPermissible extends PermissibleBase {
this.user = Objects.requireNonNull(user, "user");
this.player = Objects.requireNonNull(player, "player");
this.plugin = Objects.requireNonNull(plugin, "plugin");
this.queryOptionsSupplier = plugin.getContextManager().getCacheFor(player);
this.queryOptionsSupplier = plugin.getContextManager().createQueryOptionsSupplier(player);
injectFakeAttachmentsList();
}
@@ -293,7 +293,7 @@ public class LuckPermsPermissible extends PermissibleBase {
// the query options cache when op status changes.
// (#invalidate is a fast call)
if (this.queryOptionsSupplier != null) { // this method is called by the super class constructor, before this class has fully initialised
this.queryOptionsSupplier.invalidate();
this.queryOptionsSupplier.invalidateCache();
}
// but we don't need to do anything else in this method, unlike the CB impl.
@@ -316,6 +316,10 @@ public class LuckPermsPermissible extends PermissibleBase {
return this.plugin;
}
public QueryOptionsSupplier getQueryOptionsSupplier() {
return this.queryOptionsSupplier;
}
PermissibleBase getOldPermissible() {
return this.oldPermissible;
}
@@ -409,9 +413,7 @@ public class LuckPermsPermissible extends PermissibleBase {
@Override public PermissionAttachment remove(int index) { throw new UnsupportedOperationException(); }
@Override public int indexOf(Object o) { throw new UnsupportedOperationException(); }
@Override public int lastIndexOf(Object o) { throw new UnsupportedOperationException(); }
@Override
public @NonNull ListIterator<PermissionAttachment> listIterator(int index) { throw new UnsupportedOperationException(); }
@Override
public @NonNull List<PermissionAttachment> subList(int fromIndex, int toIndex) { throw new UnsupportedOperationException(); }
@Override public @NonNull ListIterator<PermissionAttachment> listIterator(int index) { throw new UnsupportedOperationException(); }
@Override public @NonNull List<PermissionAttachment> subList(int fromIndex, int toIndex) { throw new UnsupportedOperationException(); }
}
}

View File

@@ -30,6 +30,7 @@ import me.lucko.luckperms.common.plugin.logging.PluginLogger;
import org.bukkit.entity.Player;
import org.bukkit.permissions.PermissibleBase;
import org.bukkit.permissions.PermissionAttachment;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.lang.reflect.Field;
import java.util.List;
@@ -161,6 +162,19 @@ public final class PermissibleInjector {
}
}
public static @Nullable LuckPermsPermissible get(Player player) {
PermissibleBase permissibleBase;
try {
permissibleBase = (PermissibleBase) HUMAN_ENTITY_PERMISSIBLE_FIELD.get(player);
} catch (IllegalAccessException e) {
return null;
}
if (permissibleBase instanceof LuckPermsPermissible) {
return (LuckPermsPermissible) permissibleBase;
}
return null;
}
public static void checkInjected(Player player, PluginLogger logger) {
PermissibleBase permissibleBase;
try {

View File

@@ -253,9 +253,6 @@ public class BukkitConnectionListener extends AbstractConnectionListener impleme
if (this.plugin.getConfiguration().get(ConfigKeys.AUTO_OP)) {
player.setOp(false);
}
// remove their contexts cache
this.plugin.getContextManager().onPlayerQuit(player);
}, 1L);
}

View File

@@ -26,12 +26,12 @@
package me.lucko.luckperms.bungee.context;
import me.lucko.luckperms.bungee.LPBungeePlugin;
import me.lucko.luckperms.common.context.manager.InlineContextManager;
import me.lucko.luckperms.common.context.manager.SimpleContextManager;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import java.util.UUID;
public class BungeeContextManager extends InlineContextManager<ProxiedPlayer, ProxiedPlayer> {
public class BungeeContextManager extends SimpleContextManager<ProxiedPlayer, ProxiedPlayer> {
public BungeeContextManager(LPBungeePlugin plugin) {
super(plugin, ProxiedPlayer.class, ProxiedPlayer.class);
}

View File

@@ -46,7 +46,7 @@ import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
/**
* Base implementation of {@link ContextManager} which caches content lookups.
* Base implementation of {@link ContextManager}.
*
* @param <S> the subject type
* @param <P> the player type
@@ -78,14 +78,10 @@ public abstract class ContextManager<S, P extends S> {
public abstract UUID getUniqueId(P player);
public abstract QueryOptionsSupplier getCacheFor(S subject);
public QueryOptions getQueryOptions(S subject) {
return getCacheFor(subject).getQueryOptions();
}
public abstract QueryOptions getQueryOptions(S subject);
public ImmutableContextSet getContext(S subject) {
return getCacheFor(subject).getContextSet();
return getQueryOptions(subject).context();
}
public QueryOptions getStaticQueryOptions() {
@@ -96,11 +92,13 @@ public abstract class ContextManager<S, P extends S> {
return getStaticQueryOptions().context();
}
public QueryOptions formQueryOptions(ImmutableContextSet contextSet) {
return this.plugin.getConfiguration().get(ConfigKeys.GLOBAL_QUERY_OPTIONS).toBuilder().context(contextSet).build();
public void customizeStaticQueryOptions(QueryOptions.Builder builder) {
// overridden
}
public abstract QueryOptions formQueryOptions(S subject, ImmutableContextSet contextSet);
public void customizeQueryOptions(S subject, QueryOptions.Builder builder) {
// overridden
}
public void signalContextUpdate(S subject) {
if (subject == null) {
@@ -114,7 +112,7 @@ public abstract class ContextManager<S, P extends S> {
this.plugin.getEventDispatcher().dispatchContextUpdate(subject);
}
protected abstract void invalidateCache(S subject);
public abstract void invalidateCache(S subject);
public void registerCalculator(ContextCalculator<? super S> calculator) {
String calculatorClass = calculator.getClass().getName();
@@ -158,7 +156,9 @@ public abstract class ContextManager<S, P extends S> {
callContextCalculator(calculator, subject, consumer);
}
return formQueryOptions(subject, accumulator.build());
QueryOptions.Builder builder = this.plugin.getConfiguration().get(ConfigKeys.GLOBAL_QUERY_OPTIONS).toBuilder().context(accumulator.build());
customizeQueryOptions(subject, builder);
return builder.build();
}
private QueryOptions calculateStatic() {
@@ -169,7 +169,9 @@ public abstract class ContextManager<S, P extends S> {
callStaticContextCalculator(calculator, consumer);
}
return formQueryOptions(accumulator.build());
QueryOptions.Builder builder = this.plugin.getConfiguration().get(ConfigKeys.GLOBAL_QUERY_OPTIONS).toBuilder().context(accumulator.build());
customizeStaticQueryOptions(builder);
return builder.build();
}
public ImmutableContextSet getPotentialContexts() {

View File

@@ -0,0 +1,73 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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.context.manager;
import com.github.benmanes.caffeine.cache.LoadingCache;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.util.CaffeineFactory;
import net.luckperms.api.query.QueryOptions;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.concurrent.TimeUnit;
/**
* Implementation of {@link ContextManager} which utilises 'detached' supplier caches stored alongside the subject instances.
*/
public abstract class DetachedContextManager<S, P extends S> extends ContextManager<S, P> {
private final LoadingCache<S, QueryOptions> fallbackContextsCache = CaffeineFactory.newBuilder()
.expireAfterWrite(50, TimeUnit.MILLISECONDS)
.build(this::calculate);
protected DetachedContextManager(LuckPermsPlugin plugin, Class<S> subjectClass, Class<P> playerClass) {
super(plugin, subjectClass, playerClass);
}
@Override
public QueryOptions getQueryOptions(S subject) {
QueryOptionsSupplier supplier = getQueryOptionsSupplier(subject);
if (supplier != null) {
return supplier.getQueryOptions();
}
return this.fallbackContextsCache.get(subject);
}
@Override
public void invalidateCache(S subject) {
QueryOptionsSupplier queryOptionsSupplier = getQueryOptionsSupplier(subject);
if (queryOptionsSupplier != null) {
queryOptionsSupplier.invalidateCache();
}
this.fallbackContextsCache.invalidate(subject);
}
public QueryOptionsSupplier createQueryOptionsSupplier(S subject) {
return new QueryOptionsCache<>(subject, this);
}
public abstract @Nullable QueryOptionsSupplier getQueryOptionsSupplier(S subject);
}

View File

@@ -37,11 +37,11 @@ import java.util.concurrent.TimeUnit;
*
* @param <T> the player type
*/
public final class QueryOptionsCache<T> extends ExpiringCache<QueryOptions> implements QueryOptionsSupplier {
final class QueryOptionsCache<T> extends ExpiringCache<QueryOptions> implements QueryOptionsSupplier {
private final T subject;
private final ContextManager<T, ?> contextManager;
public QueryOptionsCache(T subject, ContextManager<T, ?> contextManager) {
QueryOptionsCache(T subject, ContextManager<T, ?> contextManager) {
super(50L, TimeUnit.MILLISECONDS); // expire roughly every tick
this.subject = subject;
this.contextManager = contextManager;
@@ -61,4 +61,9 @@ public final class QueryOptionsCache<T> extends ExpiringCache<QueryOptions> impl
public ImmutableContextSet getContextSet() {
return get().context();
}
@Override
public void invalidateCache() {
invalidate();
}
}

View File

@@ -39,4 +39,6 @@ public interface QueryOptionsSupplier {
return getQueryOptions().context();
}
void invalidateCache();
}

View File

@@ -28,63 +28,27 @@ package me.lucko.luckperms.common.context.manager;
import com.github.benmanes.caffeine.cache.LoadingCache;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.util.CaffeineFactory;
import net.luckperms.api.context.ImmutableContextSet;
import net.luckperms.api.query.QueryOptions;
import java.util.concurrent.TimeUnit;
public abstract class InlineContextManager<S, P extends S> extends ContextManager<S, P> {
public abstract class SimpleContextManager<S, P extends S> extends ContextManager<S, P> {
private final LoadingCache<S, QueryOptions> contextsCache = CaffeineFactory.newBuilder()
.expireAfterWrite(50, TimeUnit.MILLISECONDS)
.build(this::calculate);
protected InlineContextManager(LuckPermsPlugin plugin, Class<S> subjectClass, Class<P> playerClass) {
protected SimpleContextManager(LuckPermsPlugin plugin, Class<S> subjectClass, Class<P> playerClass) {
super(plugin, subjectClass, playerClass);
}
@Override
public final QueryOptionsSupplier getCacheFor(S subject) {
if (subject == null) {
throw new NullPointerException("subject");
}
return new InlineQueryOptionsSupplier<>(subject, this.contextsCache);
}
// override getContext, getQueryOptions and invalidateCache to skip the QueryOptionsSupplier
@Override
public final ImmutableContextSet getContext(S subject) {
return getQueryOptions(subject).context();
}
@Override
public final QueryOptions getQueryOptions(S subject) {
public QueryOptions getQueryOptions(S subject) {
return this.contextsCache.get(subject);
}
@Override
protected final void invalidateCache(S subject) {
public void invalidateCache(S subject) {
this.contextsCache.invalidate(subject);
}
@Override
public QueryOptions formQueryOptions(S subject, ImmutableContextSet contextSet) {
return formQueryOptions(contextSet);
}
private static final class InlineQueryOptionsSupplier<T> implements QueryOptionsSupplier {
private final T key;
private final LoadingCache<T, QueryOptions> cache;
InlineQueryOptionsSupplier(T key, LoadingCache<T, QueryOptions> cache) {
this.key = key;
this.cache = cache;
}
@Override
public QueryOptions getQueryOptions() {
return this.cache.get(this.key);
}
}
}

View File

@@ -25,19 +25,19 @@
package me.lucko.luckperms.fabric.context;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.context.manager.ContextManager;
import me.lucko.luckperms.common.context.manager.QueryOptionsCache;
import me.lucko.luckperms.common.context.manager.DetachedContextManager;
import me.lucko.luckperms.common.context.manager.QueryOptionsSupplier;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.fabric.model.MixinUser;
import net.luckperms.api.context.ImmutableContextSet;
import net.luckperms.api.query.OptionKey;
import net.luckperms.api.query.QueryOptions;
import net.minecraft.server.network.ServerPlayerEntity;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Objects;
import java.util.UUID;
public class FabricContextManager extends ContextManager<ServerPlayerEntity, ServerPlayerEntity> {
public class FabricContextManager extends DetachedContextManager<ServerPlayerEntity, ServerPlayerEntity> {
public static final OptionKey<Boolean> INTEGRATED_SERVER_OWNER = OptionKey.of("integrated_server_owner", Boolean.class);
public FabricContextManager(LuckPermsPlugin plugin) {
@@ -49,32 +49,17 @@ public class FabricContextManager extends ContextManager<ServerPlayerEntity, Ser
return player.getUuid();
}
public QueryOptionsCache<ServerPlayerEntity> newQueryOptionsCache(ServerPlayerEntity player) {
return new QueryOptionsCache<>(player, this);
}
@Override
public QueryOptionsCache<ServerPlayerEntity> getCacheFor(ServerPlayerEntity subject) {
if (subject == null) {
throw new NullPointerException("subject");
}
public @Nullable QueryOptionsSupplier getQueryOptionsSupplier(ServerPlayerEntity subject) {
Objects.requireNonNull(subject, "subject");
return ((MixinUser) subject).luckperms$getQueryOptionsCache(this);
}
@Override
public void invalidateCache(ServerPlayerEntity subject) {
getCacheFor(subject).invalidate();
}
@Override
public QueryOptions formQueryOptions(ServerPlayerEntity subject, ImmutableContextSet contextSet) {
QueryOptions.Builder queryOptions = this.plugin.getConfiguration().get(ConfigKeys.GLOBAL_QUERY_OPTIONS).toBuilder();
public void customizeQueryOptions(ServerPlayerEntity subject, QueryOptions.Builder builder) {
if (subject.getServer().isHost(subject.getGameProfile())) {
queryOptions.option(INTEGRATED_SERVER_OWNER, true);
builder.option(INTEGRATED_SERVER_OWNER, true);
}
return queryOptions.context(contextSet).build();
}
}

View File

@@ -27,7 +27,7 @@ package me.lucko.luckperms.fabric.mixin;
import me.lucko.luckperms.common.cacheddata.type.MetaCache;
import me.lucko.luckperms.common.cacheddata.type.PermissionCache;
import me.lucko.luckperms.common.context.manager.QueryOptionsCache;
import me.lucko.luckperms.common.context.manager.QueryOptionsSupplier;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.verbose.event.CheckOrigin;
import me.lucko.luckperms.fabric.context.FabricContextManager;
@@ -61,7 +61,7 @@ public abstract class ServerPlayerEntityMixin implements MixinUser {
* having to maintain a map of Player->Cache.
*/
@Unique
private QueryOptionsCache<ServerPlayerEntity> luckperms$queryOptions;
private QueryOptionsSupplier luckperms$queryOptions;
// Used by PlayerChangeWorldCallback hook below.
@Shadow public abstract ServerWorld getServerWorld();
@@ -72,9 +72,9 @@ public abstract class ServerPlayerEntityMixin implements MixinUser {
}
@Override
public QueryOptionsCache<ServerPlayerEntity> luckperms$getQueryOptionsCache(FabricContextManager contextManager) {
public QueryOptionsSupplier luckperms$getQueryOptionsCache(FabricContextManager contextManager) {
if (this.luckperms$queryOptions == null) {
this.luckperms$queryOptions = contextManager.newQueryOptionsCache((ServerPlayerEntity) (Object) this);
this.luckperms$queryOptions = contextManager.createQueryOptionsSupplier((ServerPlayerEntity) (Object) this);
}
return this.luckperms$queryOptions;
}

View File

@@ -25,7 +25,7 @@
package me.lucko.luckperms.fabric.model;
import me.lucko.luckperms.common.context.manager.QueryOptionsCache;
import me.lucko.luckperms.common.context.manager.QueryOptionsSupplier;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.fabric.context.FabricContextManager;
import net.luckperms.api.query.QueryOptions;
@@ -41,12 +41,12 @@ public interface MixinUser {
User luckperms$getUser();
/**
* Gets (or creates using the manager) the objects {@link QueryOptionsCache}.
* Gets (or creates using the manager) the objects {@link QueryOptionsSupplier}.
*
* @param contextManager the contextManager
* @return the cache
*/
QueryOptionsCache<ServerPlayerEntity> luckperms$getQueryOptionsCache(FabricContextManager contextManager);
QueryOptionsSupplier luckperms$getQueryOptionsCache(FabricContextManager contextManager);
/**
* Initialises permissions for this player using the given {@link User}.

View File

@@ -93,7 +93,7 @@ public class LPForgePlugin extends AbstractLuckPermsPlugin {
ForgePlatformListener platformListener = new ForgePlatformListener(this);
this.bootstrap.registerListeners(platformListener);
UserCapabilityListener userCapabilityListener = new UserCapabilityListener();
UserCapabilityListener userCapabilityListener = new UserCapabilityListener(this);
this.bootstrap.registerListeners(userCapabilityListener);
ForgePermissionHandlerListener permissionHandlerListener = new ForgePermissionHandlerListener(this);

View File

@@ -26,11 +26,11 @@
package me.lucko.luckperms.forge.capabilities;
import me.lucko.luckperms.common.cacheddata.type.PermissionCache;
import me.lucko.luckperms.common.context.manager.QueryOptionsCache;
import me.lucko.luckperms.common.context.manager.QueryOptionsSupplier;
import me.lucko.luckperms.common.locale.TranslationManager;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.verbose.event.CheckOrigin;
import me.lucko.luckperms.forge.context.ForgeContextManager;
import me.lucko.luckperms.forge.LPForgePlugin;
import net.luckperms.api.query.QueryOptions;
import net.luckperms.api.util.Tristate;
import net.minecraft.server.level.ServerPlayer;
@@ -77,7 +77,7 @@ public class UserCapabilityImpl implements UserCapability {
private boolean initialised = false;
private User user;
private QueryOptionsCache<ServerPlayer> queryOptionsCache;
private QueryOptionsSupplier queryOptionsSupplier;
private String language;
private Locale locale;
@@ -85,17 +85,17 @@ public class UserCapabilityImpl implements UserCapability {
}
public void initialise(UserCapabilityImpl previous) {
public void initialise(UserCapabilityImpl previous, ServerPlayer player, LPForgePlugin plugin) {
this.user = previous.user;
this.queryOptionsCache = previous.queryOptionsCache;
this.queryOptionsSupplier = plugin.getContextManager().createQueryOptionsSupplier(player);
this.language = previous.language;
this.locale = previous.locale;
this.initialised = true;
}
public void initialise(User user, ServerPlayer player, ForgeContextManager contextManager) {
public void initialise(User user, ServerPlayer player, LPForgePlugin plugin) {
this.user = user;
this.queryOptionsCache = new QueryOptionsCache<>(player, contextManager);
this.queryOptionsSupplier = plugin.getContextManager().createQueryOptionsSupplier(player);
this.initialised = true;
}
@@ -113,7 +113,7 @@ public class UserCapabilityImpl implements UserCapability {
throw new NullPointerException("permission");
}
return checkPermission(permission, this.queryOptionsCache.getQueryOptions());
return checkPermission(permission, this.queryOptionsSupplier.getQueryOptions());
}
@Override
@@ -139,12 +139,12 @@ public class UserCapabilityImpl implements UserCapability {
@Override
public QueryOptions getQueryOptions() {
return getQueryOptionsCache().getQueryOptions();
return getQueryOptionsSupplier().getQueryOptions();
}
public QueryOptionsCache<ServerPlayer> getQueryOptionsCache() {
public QueryOptionsSupplier getQueryOptionsSupplier() {
assertInitialised();
return this.queryOptionsCache;
return this.queryOptionsSupplier;
}
public Locale getLocale(ServerPlayer player) {

View File

@@ -25,6 +25,7 @@
package me.lucko.luckperms.forge.capabilities;
import me.lucko.luckperms.forge.LPForgePlugin;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
@@ -41,6 +42,12 @@ import org.jetbrains.annotations.Nullable;
public class UserCapabilityListener {
private final LPForgePlugin plugin;
public UserCapabilityListener(LPForgePlugin plugin) {
this.plugin = plugin;
}
@SubscribeEvent
public void onRegisterCapabilities(RegisterCapabilitiesEvent event) {
event.register(UserCapabilityImpl.class);
@@ -60,13 +67,17 @@ public class UserCapabilityListener {
Player previousPlayer = event.getOriginal();
Player currentPlayer = event.getEntity();
if (!(currentPlayer instanceof ServerPlayer)) {
return;
}
previousPlayer.reviveCaps();
try {
UserCapabilityImpl previous = UserCapabilityImpl.get(previousPlayer);
UserCapabilityImpl current = UserCapabilityImpl.get(currentPlayer);
current.initialise(previous);
current.getQueryOptionsCache().invalidate();
current.initialise(previous, ((ServerPlayer) currentPlayer), this.plugin);
current.getQueryOptionsSupplier().invalidateCache();
} catch (IllegalStateException e) {
// continue on if we cannot copy original data
} finally {

View File

@@ -25,19 +25,19 @@
package me.lucko.luckperms.forge.context;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.context.manager.ContextManager;
import me.lucko.luckperms.common.context.manager.QueryOptionsCache;
import me.lucko.luckperms.common.context.manager.DetachedContextManager;
import me.lucko.luckperms.common.context.manager.QueryOptionsSupplier;
import me.lucko.luckperms.forge.LPForgePlugin;
import me.lucko.luckperms.forge.capabilities.UserCapabilityImpl;
import net.luckperms.api.context.ImmutableContextSet;
import net.luckperms.api.query.OptionKey;
import net.luckperms.api.query.QueryOptions;
import net.minecraft.server.level.ServerPlayer;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Objects;
import java.util.UUID;
public class ForgeContextManager extends ContextManager<ServerPlayer, ServerPlayer> {
public class ForgeContextManager extends DetachedContextManager<ServerPlayer, ServerPlayer> {
public static final OptionKey<Boolean> INTEGRATED_SERVER_OWNER = OptionKey.of("integrated_server_owner", Boolean.class);
public ForgeContextManager(LPForgePlugin plugin) {
@@ -50,29 +50,19 @@ public class ForgeContextManager extends ContextManager<ServerPlayer, ServerPlay
}
@Override
public QueryOptionsCache<ServerPlayer> getCacheFor(ServerPlayer subject) {
if (subject == null) {
throw new NullPointerException("subject");
}
return UserCapabilityImpl.get(subject).getQueryOptionsCache();
}
@Override
public QueryOptions formQueryOptions(ServerPlayer subject, ImmutableContextSet contextSet) {
QueryOptions.Builder builder = this.plugin.getConfiguration().get(ConfigKeys.GLOBAL_QUERY_OPTIONS).toBuilder();
if (subject.getServer() != null && subject.getServer().isSingleplayerOwner(subject.getGameProfile())) {
builder.option(INTEGRATED_SERVER_OWNER, true);
}
return builder.context(contextSet).build();
}
@Override
public void invalidateCache(ServerPlayer subject) {
public @Nullable QueryOptionsSupplier getQueryOptionsSupplier(ServerPlayer subject) {
Objects.requireNonNull(subject, "subject");
UserCapabilityImpl capability = UserCapabilityImpl.getNullable(subject);
if (capability != null) {
capability.getQueryOptionsCache().invalidate();
return capability.getQueryOptionsSupplier();
}
return null;
}
@Override
public void customizeQueryOptions(ServerPlayer subject, QueryOptions.Builder builder) {
if (subject.getServer() != null && subject.getServer().isSingleplayerOwner(subject.getGameProfile())) {
builder.option(INTEGRATED_SERVER_OWNER, true);
}
}

View File

@@ -148,7 +148,7 @@ public class ForgeConnectionListener extends AbstractConnectionListener {
// initialise capability
UserCapabilityImpl userCapability = UserCapabilityImpl.get(player);
userCapability.initialise(user, player, this.plugin.getContextManager());
userCapability.initialise(user, player, this.plugin);
this.plugin.getContextManager().signalContextUpdate(player);
}

View File

@@ -82,7 +82,7 @@ public class ForgePermissionHandler implements IPermissionHandler {
if (capability != null) {
User user = capability.getUser();
QueryOptions queryOptions = capability.getQueryOptionsCache().getQueryOptions();
QueryOptions queryOptions = capability.getQueryOptionsSupplier().getQueryOptions();
T value = getPermissionValue(user, queryOptions, node, context);
if (value != null) {

View File

@@ -25,17 +25,15 @@
package me.lucko.luckperms.neoforge.context;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.context.manager.InlineContextManager;
import me.lucko.luckperms.common.context.manager.SimpleContextManager;
import me.lucko.luckperms.neoforge.LPNeoForgePlugin;
import net.luckperms.api.context.ImmutableContextSet;
import net.luckperms.api.query.OptionKey;
import net.luckperms.api.query.QueryOptions;
import net.minecraft.server.level.ServerPlayer;
import java.util.UUID;
public class NeoForgeContextManager extends InlineContextManager<ServerPlayer, ServerPlayer> {
public class NeoForgeContextManager extends SimpleContextManager<ServerPlayer, ServerPlayer> {
public static final OptionKey<Boolean> INTEGRATED_SERVER_OWNER = OptionKey.of("integrated_server_owner", Boolean.class);
public NeoForgeContextManager(LPNeoForgePlugin plugin) {
@@ -48,12 +46,9 @@ public class NeoForgeContextManager extends InlineContextManager<ServerPlayer, S
}
@Override
public QueryOptions formQueryOptions(ServerPlayer subject, ImmutableContextSet contextSet) {
QueryOptions.Builder builder = this.plugin.getConfiguration().get(ConfigKeys.GLOBAL_QUERY_OPTIONS).toBuilder();
public void customizeQueryOptions(ServerPlayer subject, QueryOptions.Builder builder) {
if (subject.getServer() != null && subject.getServer().isSingleplayerOwner(subject.getGameProfile())) {
builder.option(INTEGRATED_SERVER_OWNER, true);
}
return builder.context(contextSet).build();
}
}

View File

@@ -26,84 +26,45 @@
package me.lucko.luckperms.nukkit.context;
import cn.nukkit.Player;
import com.github.benmanes.caffeine.cache.LoadingCache;
import me.lucko.luckperms.common.cache.LoadingMap;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.context.manager.ContextManager;
import me.lucko.luckperms.common.context.manager.QueryOptionsCache;
import me.lucko.luckperms.common.util.CaffeineFactory;
import me.lucko.luckperms.common.context.manager.DetachedContextManager;
import me.lucko.luckperms.common.context.manager.QueryOptionsSupplier;
import me.lucko.luckperms.nukkit.LPNukkitPlugin;
import net.luckperms.api.context.ImmutableContextSet;
import me.lucko.luckperms.nukkit.inject.permissible.LuckPermsPermissible;
import me.lucko.luckperms.nukkit.inject.permissible.PermissibleInjector;
import net.luckperms.api.query.OptionKey;
import net.luckperms.api.query.QueryOptions;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class NukkitContextManager extends ContextManager<Player, Player> {
public class NukkitContextManager extends DetachedContextManager<Player, Player> {
public static final OptionKey<Boolean> OP_OPTION = OptionKey.of("op", Boolean.class);
// cache the creation of ContextsCache instances for online players with no expiry
private final LoadingMap<Player, QueryOptionsCache<Player>> onlineSubjectCaches = LoadingMap.of(key -> new QueryOptionsCache<>(key, this));
// cache the creation of ContextsCache instances for offline players with a 1m expiry
private final LoadingCache<Player, QueryOptionsCache<Player>> offlineSubjectCaches = CaffeineFactory.newBuilder()
.expireAfterAccess(1, TimeUnit.MINUTES)
.build(key -> {
QueryOptionsCache<Player> cache = this.onlineSubjectCaches.getIfPresent(key);
if (cache != null) {
return cache;
}
return new QueryOptionsCache<>(key, this);
});
public NukkitContextManager(LPNukkitPlugin plugin) {
super(plugin, Player.class, Player.class);
}
public void onPlayerQuit(Player player) {
this.onlineSubjectCaches.remove(player);
}
@Override
public UUID getUniqueId(Player player) {
return player.getUniqueId();
}
@Override
public QueryOptionsCache<Player> getCacheFor(Player subject) {
if (subject == null) {
throw new NullPointerException("subject");
}
if (subject.isOnline()) {
return this.onlineSubjectCaches.get(subject);
} else {
return this.offlineSubjectCaches.get(subject);
public @Nullable QueryOptionsSupplier getQueryOptionsSupplier(Player subject) {
Objects.requireNonNull(subject, "subject");
LuckPermsPermissible permissible = PermissibleInjector.get(subject);
if (permissible != null) {
return permissible.getQueryOptionsSupplier();
}
return null;
}
@Override
protected void invalidateCache(Player subject) {
QueryOptionsCache<Player> cache = this.onlineSubjectCaches.getIfPresent(subject);
if (cache != null) {
cache.invalidate();
}
cache = this.offlineSubjectCaches.getIfPresent(subject);
if (cache != null) {
cache.invalidate();
}
}
@Override
public QueryOptions formQueryOptions(Player subject, ImmutableContextSet contextSet) {
QueryOptions.Builder queryOptions = this.plugin.getConfiguration().get(ConfigKeys.GLOBAL_QUERY_OPTIONS).toBuilder();
public void customizeQueryOptions(Player subject, QueryOptions.Builder builder) {
if (subject.isOp()) {
queryOptions.option(OP_OPTION, true);
builder.option(OP_OPTION, true);
}
return queryOptions.context(contextSet).build();
}
}

View File

@@ -35,7 +35,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import me.lucko.luckperms.common.cacheddata.result.TristateResult;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.context.manager.QueryOptionsCache;
import me.lucko.luckperms.common.context.manager.QueryOptionsSupplier;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.verbose.event.CheckOrigin;
import me.lucko.luckperms.nukkit.LPNukkitPlugin;
@@ -93,7 +93,7 @@ public class LuckPermsPermissible extends PermissibleBase {
private final LPNukkitPlugin plugin;
// caches context lookups for the player
private final QueryOptionsCache<Player> queryOptionsSupplier;
private final QueryOptionsSupplier queryOptionsSupplier;
// the players previous permissible. (the one they had before this one was injected)
private PermissibleBase oldPermissible = null;
@@ -110,7 +110,7 @@ public class LuckPermsPermissible extends PermissibleBase {
this.user = Objects.requireNonNull(user, "user");
this.player = Objects.requireNonNull(player, "player");
this.plugin = Objects.requireNonNull(plugin, "plugin");
this.queryOptionsSupplier = plugin.getContextManager().getCacheFor(player);
this.queryOptionsSupplier = plugin.getContextManager().createQueryOptionsSupplier(player);
injectFakeAttachmentsList();
}
@@ -276,7 +276,7 @@ public class LuckPermsPermissible extends PermissibleBase {
// the query options cache when op status changes.
// (#invalidate is a fast call)
if (this.queryOptionsSupplier != null) { // this method is called by the super class constructor, before this class has fully initialised
this.queryOptionsSupplier.invalidate();
this.queryOptionsSupplier.invalidateCache();
}
// but we don't need to do anything else in this method, unlike the Nukkit impl.
@@ -299,6 +299,10 @@ public class LuckPermsPermissible extends PermissibleBase {
return this.plugin;
}
public QueryOptionsSupplier getQueryOptionsSupplier() {
return this.queryOptionsSupplier;
}
PermissibleBase getOldPermissible() {
return this.oldPermissible;
}

View File

@@ -28,6 +28,7 @@ package me.lucko.luckperms.nukkit.inject.permissible;
import cn.nukkit.Player;
import cn.nukkit.permission.PermissibleBase;
import cn.nukkit.permission.PermissionAttachment;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.lang.reflect.Field;
import java.util.Set;
@@ -141,4 +142,17 @@ public final class PermissibleInjector {
}
}
public static @Nullable LuckPermsPermissible get(Player player) {
PermissibleBase permissibleBase;
try {
permissibleBase = (PermissibleBase) PLAYER_PERMISSIBLE_FIELD.get(player);
} catch (IllegalAccessException e) {
return null;
}
if (permissibleBase instanceof LuckPermsPermissible) {
return (LuckPermsPermissible) permissibleBase;
}
return null;
}
}

View File

@@ -215,9 +215,6 @@ public class NukkitConnectionListener extends AbstractConnectionListener impleme
if (this.plugin.getConfiguration().get(ConfigKeys.AUTO_OP)) {
player.setOp(false);
}
// remove their contexts cache
this.plugin.getContextManager().onPlayerQuit(player);
}, 1, true);
}

View File

@@ -25,20 +25,23 @@
package me.lucko.luckperms.sponge.context;
import me.lucko.luckperms.common.context.manager.InlineContextManager;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.context.manager.SimpleContextManager;
import me.lucko.luckperms.sponge.LPSpongePlugin;
import me.lucko.luckperms.sponge.service.model.ContextCalculatorProxy;
import me.lucko.luckperms.sponge.service.model.TemporaryCauseHolderSubject;
import net.luckperms.api.context.ContextCalculator;
import net.luckperms.api.context.ContextConsumer;
import net.luckperms.api.context.ContextSet;
import net.luckperms.api.context.StaticContextCalculator;
import net.luckperms.api.query.QueryOptions;
import org.spongepowered.api.entity.living.player.server.ServerPlayer;
import org.spongepowered.api.event.Cause;
import org.spongepowered.api.service.permission.Subject;
import java.util.UUID;
public class SpongeContextManager extends InlineContextManager<Subject, ServerPlayer> {
public class SpongeContextManager extends SimpleContextManager<Subject, ServerPlayer> {
public SpongeContextManager(LPSpongePlugin plugin) {
super(plugin, Subject.class, ServerPlayer.class);
@@ -75,4 +78,11 @@ public class SpongeContextManager extends InlineContextManager<Subject, ServerPl
public UUID getUniqueId(ServerPlayer player) {
return player.uniqueId();
}
public QueryOptions formQueryOptions(ContextSet contexts) {
QueryOptions.Builder builder = this.plugin.getConfiguration().get(ConfigKeys.GLOBAL_QUERY_OPTIONS).toBuilder().context(contexts);
customizeStaticQueryOptions(builder);
return builder.build();
}
}

View File

@@ -25,20 +25,13 @@
package me.lucko.luckperms.standalone.stub;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.context.manager.ContextManager;
import me.lucko.luckperms.common.context.manager.QueryOptionsCache;
import me.lucko.luckperms.common.context.manager.SimpleContextManager;
import me.lucko.luckperms.standalone.LPStandalonePlugin;
import me.lucko.luckperms.standalone.app.integration.StandaloneSender;
import me.lucko.luckperms.standalone.app.integration.StandaloneUser;
import net.luckperms.api.context.ImmutableContextSet;
import net.luckperms.api.query.QueryOptions;
import java.util.UUID;
public class StandaloneContextManager extends ContextManager<StandaloneSender, StandaloneSender> {
private final QueryOptionsCache<StandaloneSender> singletonCache = new QueryOptionsCache<>(StandaloneUser.INSTANCE, this);
public class StandaloneContextManager extends SimpleContextManager<StandaloneSender, StandaloneSender> {
public StandaloneContextManager(LPStandalonePlugin plugin) {
super(plugin, StandaloneSender.class, StandaloneSender.class);
}
@@ -47,28 +40,4 @@ public class StandaloneContextManager extends ContextManager<StandaloneSender, S
public UUID getUniqueId(StandaloneSender player) {
return player.getUniqueId();
}
@Override
public QueryOptionsCache<StandaloneSender> getCacheFor(StandaloneSender subject) {
if (subject == null) {
throw new NullPointerException("subject");
}
if (subject == StandaloneUser.INSTANCE) {
return this.singletonCache;
}
// just return a new one every time - not optimal but this case should only be hit using unit tests anyway
return new QueryOptionsCache<>(subject, this);
}
@Override
protected void invalidateCache(StandaloneSender subject) {
this.singletonCache.invalidate();
}
@Override
public QueryOptions formQueryOptions(StandaloneSender subject, ImmutableContextSet contextSet) {
QueryOptions.Builder queryOptions = this.plugin.getConfiguration().get(ConfigKeys.GLOBAL_QUERY_OPTIONS).toBuilder();
return queryOptions.context(contextSet).build();
}
}

View File

@@ -26,12 +26,12 @@
package me.lucko.luckperms.velocity.context;
import com.velocitypowered.api.proxy.Player;
import me.lucko.luckperms.common.context.manager.InlineContextManager;
import me.lucko.luckperms.common.context.manager.SimpleContextManager;
import me.lucko.luckperms.velocity.LPVelocityPlugin;
import java.util.UUID;
public class VelocityContextManager extends InlineContextManager<Player, Player> {
public class VelocityContextManager extends SimpleContextManager<Player, Player> {
public VelocityContextManager(LPVelocityPlugin plugin) {
super(plugin, Player.class, Player.class);
}

View File

@@ -38,6 +38,7 @@ import me.lucko.luckperms.common.locale.TranslationManager;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.plugin.util.AbstractConnectionListener;
import me.lucko.luckperms.velocity.LPVelocityPlugin;
import me.lucko.luckperms.velocity.context.VelocityContextManager;
import me.lucko.luckperms.velocity.service.PlayerPermissionProvider;
import me.lucko.luckperms.velocity.util.AdventureCompat;
@@ -90,7 +91,8 @@ public class VelocityConnectionListener extends AbstractConnectionListener {
try {
User user = loadUser(p.getUniqueId(), p.getUsername());
recordConnection(p.getUniqueId());
e.setProvider(new PlayerPermissionProvider(p, user, this.plugin.getContextManager().getCacheFor(p)));
VelocityContextManager contextManager = this.plugin.getContextManager();
e.setProvider(new PlayerPermissionProvider(p, user, contextManager::getQueryOptions));
this.plugin.getEventDispatcher().dispatchPlayerLoginProcess(p.getUniqueId(), p.getUsername(), user);
} catch (Exception ex) {
this.plugin.getLogger().severe("Exception occurred whilst loading data for " + p.getUniqueId() + " - " + p.getUsername(), ex);

View File

@@ -25,23 +25,24 @@
package me.lucko.luckperms.velocity.service;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.permission.PermissionFunction;
import com.velocitypowered.api.permission.PermissionProvider;
import com.velocitypowered.api.permission.PermissionSubject;
import com.velocitypowered.api.permission.Tristate;
import com.velocitypowered.api.proxy.Player;
import me.lucko.luckperms.common.context.manager.QueryOptionsSupplier;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.verbose.event.CheckOrigin;
import net.luckperms.api.query.QueryOptions;
import org.checkerframework.checker.nullness.qual.NonNull;
public class PlayerPermissionProvider implements PermissionProvider, PermissionFunction {
private final Player player;
private final User user;
private final QueryOptionsSupplier queryOptionsSupplier;
private final Function<Player, QueryOptions> queryOptionsSupplier;
public PlayerPermissionProvider(Player player, User user, QueryOptionsSupplier queryOptionsSupplier) {
public PlayerPermissionProvider(Player player, User user, Function<Player, QueryOptions> queryOptionsSupplier) {
this.player = player;
this.user = user;
this.queryOptionsSupplier = queryOptionsSupplier;
@@ -55,6 +56,7 @@ public class PlayerPermissionProvider implements PermissionProvider, PermissionF
@Override
public @NonNull Tristate getPermissionValue(@NonNull String permission) {
return CompatibilityUtil.convertTristate(this.user.getCachedData().getPermissionData(this.queryOptionsSupplier.getQueryOptions()).checkPermission(permission, CheckOrigin.PLATFORM_API_HAS_PERMISSION).result());
QueryOptions queryOptions = this.queryOptionsSupplier.apply(this.player);
return CompatibilityUtil.convertTristate(this.user.getCachedData().getPermissionData(queryOptions).checkPermission(permission, CheckOrigin.PLATFORM_API_HAS_PERMISSION).result());
}
}