diff --git a/src/main/java/com/sk89q/worldguard/bukkit/QueryCache.java b/src/main/java/com/sk89q/worldguard/bukkit/QueryCache.java new file mode 100644 index 00000000..2f969deb --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/bukkit/QueryCache.java @@ -0,0 +1,112 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit; + +import com.sk89q.worldguard.protection.ApplicableRegionSet; +import com.sk89q.worldguard.protection.managers.RegionManager; +import org.bukkit.Location; +import org.bukkit.World; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Keeps a cache of {@link ApplicableRegionSet}s. The contents of the cache + * must be externally invalidated occasionally (and frequently). + * + *

This class is fully concurrent.

+ */ +class QueryCache { + + private final ConcurrentMap cache = new ConcurrentHashMap(); + + /** + * Get from the cache a {@code ApplicableRegionSet} if an entry exists; + * otherwise, query the given manager for a result and cache it. + * + * @param manager the region manager + * @param location the location + * @return a result + */ + public ApplicableRegionSet queryContains(RegionManager manager, Location location) { + checkNotNull(manager); + checkNotNull(location); + + CacheKey key = new CacheKey(location); + ApplicableRegionSet result = cache.get(key); + if (result == null) { + result = manager.getApplicableRegions(location); + cache.put(key, result); + } + + return result; + } + + /** + * Invalidate the cache and clear its contents. + */ + public void invalidateAll() { + cache.clear(); + } + + /** + * Key object for the map. + */ + private static class CacheKey { + private final World world; + private final int x; + private final int y; + private final int z; + + private CacheKey(Location location) { + this.world = location.getWorld(); + this.x = location.getBlockX(); + this.y = location.getBlockY(); + this.z = location.getBlockZ(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + CacheKey cacheKey = (CacheKey) o; + + if (x != cacheKey.x) return false; + if (y != cacheKey.y) return false; + if (z != cacheKey.z) return false; + if (!world.equals(cacheKey.world)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = world.hashCode(); + result = 31 * result + x; + result = 31 * result + y; + result = 31 * result + z; + return result; + } + } + +} diff --git a/src/main/java/com/sk89q/worldguard/bukkit/RegionContainer.java b/src/main/java/com/sk89q/worldguard/bukkit/RegionContainer.java index bd8f1348..42fc522e 100644 --- a/src/main/java/com/sk89q/worldguard/bukkit/RegionContainer.java +++ b/src/main/java/com/sk89q/worldguard/bukkit/RegionContainer.java @@ -53,9 +53,15 @@ */ public class RegionContainer { + /** + * Invalidation frequency in ticks. + */ + private static final int CACHE_INVALIDATION_INTERVAL = 2; + private final Object lock = new Object(); private final WorldGuardPlugin plugin; private final ManagerContainer container; + private final QueryCache cache = new QueryCache(); /** * Create a new instance. @@ -106,6 +112,13 @@ public void onChunkUnload(ChunkUnloadEvent event) { } } }, plugin); + + Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin, new Runnable() { + @Override + public void run() { + cache.invalidateAll(); + } + }, CACHE_INVALIDATION_INTERVAL, CACHE_INVALIDATION_INTERVAL); } /** @@ -216,7 +229,7 @@ public List getLoaded() { * @return a new query */ public RegionQuery createAnonymousQuery() { - return new RegionQuery(plugin, (Player) null); + return new RegionQuery(plugin, cache, (Player) null); } /** @@ -226,7 +239,7 @@ public RegionQuery createAnonymousQuery() { * @return a new query */ public RegionQuery createQuery(@Nullable Player player) { - return new RegionQuery(plugin, player); + return new RegionQuery(plugin, cache, player); } /** @@ -236,7 +249,7 @@ public RegionQuery createQuery(@Nullable Player player) { * @return a new query */ public RegionQuery createQuery(@Nullable LocalPlayer player) { - return new RegionQuery(plugin, player); + return new RegionQuery(plugin, cache, player); } } diff --git a/src/main/java/com/sk89q/worldguard/bukkit/RegionQuery.java b/src/main/java/com/sk89q/worldguard/bukkit/RegionQuery.java index 56f18664..967397b5 100644 --- a/src/main/java/com/sk89q/worldguard/bukkit/RegionQuery.java +++ b/src/main/java/com/sk89q/worldguard/bukkit/RegionQuery.java @@ -42,6 +42,7 @@ public class RegionQuery { private final ConfigurationManager config; private final GlobalRegionManager globalManager; + private final QueryCache cache; @Nullable private final LocalPlayer localPlayer; @@ -49,22 +50,26 @@ public class RegionQuery { * Create a new instance. * * @param plugin the plugin + * @param cache the query cache * @param player an optional player */ - RegionQuery(WorldGuardPlugin plugin, @Nullable Player player) { - this(plugin, player != null ? plugin.wrapPlayer(player) : null); + RegionQuery(WorldGuardPlugin plugin, QueryCache cache, @Nullable Player player) { + this(plugin, cache, player != null ? plugin.wrapPlayer(player) : null); } /** * Create a new instance. * * @param plugin the plugin + * @param cache the query cache * @param player an optional player */ - RegionQuery(WorldGuardPlugin plugin, @Nullable LocalPlayer player) { + RegionQuery(WorldGuardPlugin plugin, QueryCache cache, @Nullable LocalPlayer player) { checkNotNull(plugin); + checkNotNull(cache); this.config = plugin.getGlobalStateManager(); + this.cache = cache; //noinspection deprecation this.globalManager = plugin.getGlobalRegionManager(); this.localPlayer = player; @@ -102,7 +107,7 @@ public boolean testPermission(Location location) { return true; } else { RegionManager manager = globalManager.get(location.getWorld()); - return manager == null || manager.getApplicableRegions(BukkitUtil.toVector(location)).canBuild(localPlayer); + return manager == null || cache.queryContains(manager, location).canBuild(localPlayer); } } @@ -141,7 +146,7 @@ public boolean testPermission(Location location, StateFlag... flags) { RegionManager manager = globalManager.get(location.getWorld()); if (manager != null) { - ApplicableRegionSet result = manager.getApplicableRegions(BukkitUtil.toVector(location)); + ApplicableRegionSet result = cache.queryContains(manager, location); if (result.canBuild(localPlayer)) { return true; @@ -185,11 +190,11 @@ public boolean testEnabled(Location location, StateFlag flag) { return true; } - if (globalManager.hasBypass(localPlayer, world)) { + if (localPlayer != null && globalManager.hasBypass(localPlayer, world)) { return true; } else { RegionManager manager = globalManager.get(location.getWorld()); - return manager == null || manager.getApplicableRegions(BukkitUtil.toVector(location)).allows(flag, localPlayer); + return manager == null || cache.queryContains(manager, location).allows(flag, localPlayer); } }