Fix member inheritance for non-player associables (#1804)

* Add options to query region sets unsorted and without parents

* Fix member inheritance for non-player associables

* Add member inheritance for non-player associables

* Rename Option to QueryOption, remove functional definitions, bit of cleanup.

Co-authored-by: wizjany <wizjany@gmail.com>
This commit is contained in:
stonar96 2021-08-09 04:58:09 +02:00 committed by GitHub
parent c81f5892eb
commit 4644268214
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 263 additions and 52 deletions

View File

@ -19,6 +19,7 @@
package com.sk89q.worldguard.protection;
import com.google.common.collect.ImmutableSet;
import com.sk89q.worldguard.LocalPlayer;
import com.sk89q.worldguard.protection.association.RegionAssociable;
import com.sk89q.worldguard.protection.flags.Flag;
@ -29,7 +30,11 @@
import com.sk89q.worldguard.protection.util.NormativeOrders;
import javax.annotation.Nullable;
import java.util.*;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import static com.google.common.base.Preconditions.checkNotNull;
@ -64,7 +69,7 @@ public RegionResultSet(List<ProtectedRegion> applicable, @Nullable ProtectedRegi
*/
public RegionResultSet(Set<ProtectedRegion> applicable, @Nullable ProtectedRegion globalRegion) {
this(NormativeOrders.fromSet(applicable), globalRegion, true);
this.regionSet = applicable;
this.regionSet = ImmutableSet.copyOf(applicable);
}
/**
@ -83,7 +88,7 @@ public RegionResultSet(List<ProtectedRegion> applicable, @Nullable ProtectedRegi
if (!sorted) {
NormativeOrders.sort(applicable);
}
this.applicable = applicable;
this.applicable = Collections.unmodifiableList(applicable);
this.flagValueCalculator = new FlagValueCalculator(applicable, globalRegion);
}
@ -157,7 +162,7 @@ public Set<ProtectedRegion> getRegions() {
if (regionSet != null) {
return regionSet;
}
regionSet = Collections.unmodifiableSet(new HashSet<>(applicable));
regionSet = ImmutableSet.copyOf(applicable);
return regionSet;
}

View File

@ -87,31 +87,35 @@ private boolean checkNonplayerProtectionDomains(Iterable<? extends ProtectedRegi
public Association getAssociation(List<ProtectedRegion> regions) {
checkNotNull(source);
for (ProtectedRegion region : regions) {
if ((region.getId().equals(ProtectedRegion.GLOBAL_REGION) && source.isEmpty())) {
return Association.OWNER;
}
if (source.contains(region)) {
if (useMaxPriorityAssociation) {
int priority = region.getPriority();
if (priority == maxPriority) {
return Association.OWNER;
}
} else {
while (region != null) {
if ((region.getId().equals(ProtectedRegion.GLOBAL_REGION) && source.isEmpty())) {
return Association.OWNER;
}
}
Set<ProtectedRegion> source;
if (source.contains(region)) {
if (useMaxPriorityAssociation) {
int priority = region.getPriority();
if (priority == maxPriority) {
return Association.OWNER;
}
} else {
return Association.OWNER;
}
}
if (useMaxPriorityAssociation) {
source = maxPriorityRegions;
} else {
source = this.source;
}
Set<ProtectedRegion> source;
if (checkNonplayerProtectionDomains(source, region.getFlag(Flags.NONPLAYER_PROTECTION_DOMAINS))) {
return Association.OWNER;
if (useMaxPriorityAssociation) {
source = maxPriorityRegions;
} else {
source = this.source;
}
if (checkNonplayerProtectionDomains(source, region.getFlag(Flags.NONPLAYER_PROTECTION_DOMAINS))) {
return Association.OWNER;
}
region = region.getParent();
}
}

View File

@ -26,6 +26,7 @@
import com.sk89q.worldguard.protection.ApplicableRegionSet;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
import com.sk89q.worldguard.protection.regions.RegionQuery;
import com.sk89q.worldguard.protection.regions.RegionQuery.QueryOption;
import java.util.List;
@ -67,7 +68,7 @@ public DelayedRegionOverlapAssociation(RegionQuery query, Location location, boo
@Override
public Association getAssociation(List<ProtectedRegion> regions) {
if (source == null) {
ApplicableRegionSet result = query.getApplicableRegions(location);
ApplicableRegionSet result = query.getApplicableRegions(location, QueryOption.NONE);
source = result.getRegions();
calcMaxPriority();
}

View File

@ -34,7 +34,7 @@
import com.sk89q.worldguard.protection.managers.storage.RegionDatabase;
import com.sk89q.worldguard.protection.managers.storage.StorageException;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
import com.sk89q.worldguard.protection.util.RegionCollectionConsumer;
import com.sk89q.worldguard.protection.regions.RegionQuery.QueryOption;
import com.sk89q.worldguard.util.Normal;
import javax.annotation.Nullable;
@ -293,32 +293,60 @@ public Set<ProtectedRegion> removeRegion(String id, RemovalStrategy strategy) {
}
/**
* Query for effective flags and owners for the given positive.
* Query for effective flags and members for the given position.
*
* <p>{@link QueryOption#COMPUTE_PARENTS} is used.</p>
*
* @param position the position
* @return the query object
*/
public ApplicableRegionSet getApplicableRegions(BlockVector3 position) {
checkNotNull(position);
Set<ProtectedRegion> regions = Sets.newHashSet();
index.applyContaining(position, new RegionCollectionConsumer(regions, true));
return new RegionResultSet(regions, index.get("__global__"));
return getApplicableRegions(position, QueryOption.COMPUTE_PARENTS);
}
/**
* Query for effective flags and owners for the area represented
* Return a region set for the given position.
*
* @param position the position
* @param option the option
* @return a region set
*/
public ApplicableRegionSet getApplicableRegions(BlockVector3 position, QueryOption option) {
checkNotNull(position);
checkNotNull(option);
Set<ProtectedRegion> regions = Sets.newHashSet();
index.applyContaining(position, option.createIndexConsumer(regions));
return new RegionResultSet(option.constructResult(regions), index.get("__global__"), true);
}
/**
* Query for effective flags and members for the area represented
* by the given region.
*
* <p>{@link QueryOption#COMPUTE_PARENTS} is used.</p>
*
* @param region the region
* @return the query object
*/
public ApplicableRegionSet getApplicableRegions(ProtectedRegion region) {
return getApplicableRegions(region, QueryOption.COMPUTE_PARENTS);
}
/**
* Return a region set for the area represented by the given region.
*
* @param region the region
* @param option the option
* @return a region set
*/
public ApplicableRegionSet getApplicableRegions(ProtectedRegion region, QueryOption option) {
checkNotNull(region);
checkNotNull(option);
Set<ProtectedRegion> regions = Sets.newHashSet();
index.applyIntersecting(region, new RegionCollectionConsumer(regions, true));
return new RegionResultSet(regions, index.get("__global__"));
index.applyIntersecting(region, option.createIndexConsumer(regions));
return new RegionResultSet(option.constructResult(regions), index.get("__global__"), true);
}
/**

View File

@ -24,7 +24,9 @@
import com.sk89q.worldguard.protection.ApplicableRegionSet;
import com.sk89q.worldguard.protection.RegionResultSet;
import com.sk89q.worldguard.protection.managers.RegionManager;
import com.sk89q.worldguard.protection.regions.RegionQuery.QueryOption;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@ -38,7 +40,7 @@
*/
public class QueryCache {
private final ConcurrentMap<CacheKey, ApplicableRegionSet> cache = new ConcurrentHashMap<>(16, 0.75f, 2);
private final ConcurrentMap<CacheKey, Map<QueryOption, ApplicableRegionSet>> cache = new ConcurrentHashMap<>(16, 0.75f, 2);
/**
* Get from the cache a {@code ApplicableRegionSet} if an entry exists;
@ -46,20 +48,16 @@ public class QueryCache {
*
* @param manager the region manager
* @param location the location
* @param option the option
* @return a result
*/
public ApplicableRegionSet queryContains(RegionManager manager, Location location) {
public ApplicableRegionSet queryContains(RegionManager manager, Location location, QueryOption option) {
checkNotNull(manager);
checkNotNull(location);
checkNotNull(option);
CacheKey key = new CacheKey(location);
ApplicableRegionSet result = cache.get(key);
if (result == null) {
result = manager.getApplicableRegions(location.toVector().toBlockPoint());
cache.put(key, result);
}
return result;
return cache.compute(key, (k, v) -> option.createCache(manager, location, v)).get(option);
}
/**

View File

@ -21,6 +21,7 @@
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.ImmutableList;
import com.sk89q.worldedit.util.Location;
import com.sk89q.worldedit.world.World;
import com.sk89q.worldguard.LocalPlayer;
@ -39,8 +40,16 @@
import com.sk89q.worldguard.protection.flags.StateFlag;
import com.sk89q.worldguard.protection.flags.StateFlag.State;
import com.sk89q.worldguard.protection.managers.RegionManager;
import com.sk89q.worldguard.protection.managers.index.RegionIndex;
import com.sk89q.worldguard.protection.util.NormativeOrders;
import com.sk89q.worldguard.protection.util.RegionCollectionConsumer;
import java.util.Collection;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
@ -74,17 +83,37 @@ public RegionQuery(QueryCache cache) {
/**
* Query for regions containing the given location.
*
* <p>An instance of {@link RegionResultSet} will always be returned,
* <p>{@link QueryOption#COMPUTE_PARENTS} is used.</p>
*
* <p>An instance of {@link ApplicableRegionSet} will always be returned,
* even if regions are disabled or region data failed to load. An
* appropriate "virtual" set will be returned in such a case
* (for example, if regions are disabled, the returned set
* would permit all activities).</p>
* appropriate "virtual" set will be returned in such a case (for example,
* if regions are disabled, the returned set would permit all
* activities).</p>
*
* @param location the location
* @return a region set
*/
public ApplicableRegionSet getApplicableRegions(Location location) {
return getApplicableRegions(location, QueryOption.COMPUTE_PARENTS);
}
/**
* Query for regions containing the given location.
*
* <p>An instance of {@link ApplicableRegionSet} will always be returned,
* even if regions are disabled or region data failed to load. An
* appropriate "virtual" set will be returned in such a case (for example,
* if regions are disabled, the returned set would permit all
* activities).</p>
*
* @param location the location
* @param option the option
* @return a region set
*/
public ApplicableRegionSet getApplicableRegions(Location location, QueryOption option) {
checkNotNull(location);
checkNotNull(option);
World world = (World) location.getExtent();
WorldConfiguration worldConfig = config.get(world);
@ -95,7 +124,7 @@ public ApplicableRegionSet getApplicableRegions(Location location) {
RegionManager manager = WorldGuard.getInstance().getPlatform().getRegionContainer().get((World) location.getExtent());
if (manager != null) {
return cache.queryContains(manager, location);
return cache.queryContains(manager, location, option);
} else {
return FailedLoadRegionSet.getInstance();
}
@ -465,4 +494,150 @@ public <V> Collection<V> queryAllValues(Location location, @Nullable RegionAssoc
return getApplicableRegions(location).queryAllValues(associable, flag);
}
/**
* Options for constructing a region set via
* {@link #getApplicableRegions(Location, QueryOption)} for example.
*/
public enum QueryOption {
/**
* Constructs a region set that does not include parent regions and
* may be left unsorted (but a cached, sorted set of the same regions
* may be returned).
*/
NONE(false) {
@Override
public List<ProtectedRegion> constructResult(Set<ProtectedRegion> applicable) {
return ImmutableList.copyOf(applicable);
}
@Override
Map<QueryOption, ApplicableRegionSet> createCache(RegionManager manager, Location location, Map<QueryOption, ApplicableRegionSet> cache) {
if (cache == null) {
cache = new EnumMap<>(QueryOption.class);
cache.put(QueryOption.NONE, manager.getApplicableRegions(location.toVector().toBlockPoint(), QueryOption.NONE));
}
// If c != null, we can assume that Option.NONE is present.
return cache;
}
},
/**
* Constructs a region set that does not include parent regions and is
* sorted by {@link NormativeOrders}.
*/
SORT(false) {
@Override
Map<QueryOption, ApplicableRegionSet> createCache(RegionManager manager, Location location, Map<QueryOption, ApplicableRegionSet> cache) {
if (cache == null) {
Map<QueryOption, ApplicableRegionSet> newCache = new EnumMap<>(QueryOption.class);
ApplicableRegionSet result = manager.getApplicableRegions(location.toVector().toBlockPoint(), QueryOption.SORT);
newCache.put(QueryOption.NONE, result);
newCache.put(QueryOption.SORT, result);
return newCache;
} else {
// If c != null, we can assume that Option.NONE is present.
cache.computeIfAbsent(QueryOption.SORT, k -> new RegionResultSet(cache.get(QueryOption.NONE).getRegions(), manager.getRegion("__global__")));
return cache;
}
}
},
/**
* Constructs a region set that includes parent regions and is sorted by
* {@link NormativeOrders}.
*/
COMPUTE_PARENTS(true) {
@Override
Map<QueryOption, ApplicableRegionSet> createCache(RegionManager manager, Location location, Map<QueryOption, ApplicableRegionSet> cache) {
if (cache == null) {
Map<QueryOption, ApplicableRegionSet> newCache = new EnumMap<>(QueryOption.class);
ApplicableRegionSet noParResult = manager.getApplicableRegions(location.toVector().toBlockPoint(), QueryOption.NONE);
Set<ProtectedRegion> noParRegions = noParResult.getRegions();
Set<ProtectedRegion> regions = new HashSet<>();
noParRegions.forEach(new RegionCollectionConsumer(regions, true)::apply);
ApplicableRegionSet result = new RegionResultSet(regions, manager.getRegion("__global__"));
if (regions.size() == noParRegions.size()) {
newCache.put(QueryOption.NONE, result);
newCache.put(QueryOption.SORT, result);
} else {
newCache.put(QueryOption.NONE, noParResult);
}
newCache.put(QueryOption.COMPUTE_PARENTS, result);
return newCache;
}
cache.computeIfAbsent(QueryOption.COMPUTE_PARENTS, k -> {
Set<ProtectedRegion> regions = new HashSet<>();
ApplicableRegionSet result = cache.get(QueryOption.SORT);
boolean sorted = true;
if (result == null) {
// If c != null, we can assume that Option.NONE is present.
result = cache.get(QueryOption.NONE);
sorted = false;
}
Set<ProtectedRegion> noParRegions = result.getRegions();
noParRegions.forEach(new RegionCollectionConsumer(regions, true)::apply);
if (sorted && regions.size() == noParRegions.size()) {
return result;
}
result = new RegionResultSet(regions, manager.getRegion("__global__"));
if (regions.size() == noParRegions.size()) {
cache.put(QueryOption.SORT, result);
}
return result;
});
return cache;
}
};
private final boolean collectParents;
QueryOption(boolean collectParents) {
this.collectParents = collectParents;
}
/**
* Create a {@link RegionCollectionConsumer} with the given collection
* used for the {@link RegionIndex}. Internal API.
*
* @param collection the collection
* @return a region collection consumer
*/
public RegionCollectionConsumer createIndexConsumer(Collection<? super ProtectedRegion> collection) {
return new RegionCollectionConsumer(collection, collectParents);
}
/**
* Convert the set of regions to a list. Sort and add parents if
* necessary. Internal API.
*
* @param applicable the set of regions
* @return a list of regions
*/
public List<ProtectedRegion> constructResult(Set<ProtectedRegion> applicable) {
return NormativeOrders.fromSet(applicable);
}
/**
* Create (if null) or update the given cache map with at least an entry
* for this option if necessary and return it.
*
* @param manager the manager
* @param location the location
* @param cache the cache map
* @return a cache map
*/
abstract Map<QueryOption, ApplicableRegionSet> createCache(RegionManager manager, Location location, Map<QueryOption, ApplicableRegionSet> cache);
}
}

View File

@ -35,7 +35,7 @@
*/
public class RegionCollectionConsumer implements Predicate<ProtectedRegion> {
private final Collection<ProtectedRegion> collection;
private final Collection<? super ProtectedRegion> collection;
private final boolean addParents;
/**
@ -44,7 +44,7 @@ public class RegionCollectionConsumer implements Predicate<ProtectedRegion> {
* @param collection the collection to add regions to
* @param addParents true to also add the parents to the collection
*/
public RegionCollectionConsumer(Collection<ProtectedRegion> collection, boolean addParents) {
public RegionCollectionConsumer(Collection<? super ProtectedRegion> collection, boolean addParents) {
checkNotNull(collection);
this.collection = collection;

View File

@ -212,7 +212,7 @@ public void testNonPlayerBuildAccessInOneRegion(boolean useMaxPriorityAssociatio
assertTrue(appl.testState(assoc, Flags.BUILD));
// Inside fountain
appl = manager.getApplicableRegions(inFountain);
assertFalse(appl.testState(assoc, Flags.BUILD));
assertTrue(appl.testState(assoc, Flags.BUILD));
}
@ParameterizedTest(name = "useMaxPriorityAssociation={0}")