Add a hash table index for regions that hashes on chunk coordinates.

This commit is contained in:
sk89q 2014-08-13 00:34:36 -07:00
parent eb23e28c16
commit 38587a1c61
13 changed files with 947 additions and 5 deletions

View File

@ -19,6 +19,7 @@
package com.sk89q.worldguard.bukkit.listener;
import com.sk89q.worldedit.Vector2D;
import com.sk89q.worldguard.bukkit.WorldGuardPlugin;
import com.sk89q.worldguard.bukkit.event.block.BreakBlockEvent;
import com.sk89q.worldguard.bukkit.event.block.PlaceBlockEvent;
@ -30,13 +31,19 @@
import com.sk89q.worldguard.bukkit.util.Materials;
import com.sk89q.worldguard.bukkit.util.RegionQuery;
import com.sk89q.worldguard.protection.flags.DefaultFlag;
import com.sk89q.worldguard.protection.managers.RegionManager;
import org.bukkit.ChatColor;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.world.ChunkLoadEvent;
import org.bukkit.event.world.ChunkUnloadEvent;
import org.bukkit.event.world.WorldLoadEvent;
import org.bukkit.event.world.WorldUnloadEvent;
/**
* Handle events that need to be processed by region protection.
@ -56,6 +63,34 @@ private void tellErrorMessage(CommandSender sender, Object subject) {
sender.sendMessage(ChatColor.DARK_RED + "You don't have permission for this area.");
}
@EventHandler
public void onWorldLoad(WorldLoadEvent event) {
getPlugin().getGlobalRegionManager().load(event.getWorld());
}
@EventHandler
public void onWorldUnload(WorldUnloadEvent event) {
getPlugin().getGlobalRegionManager().unload(event.getWorld());
}
@EventHandler
public void onChunkLoad(ChunkLoadEvent event) {
RegionManager manager = getPlugin().getGlobalRegionManager().get(event.getWorld());
if (manager != null) {
Chunk chunk = event.getChunk();
manager.loadChunk(new Vector2D(chunk.getX(), chunk.getZ()));
}
}
@EventHandler
public void onChunkUnload(ChunkUnloadEvent event) {
RegionManager manager = getPlugin().getGlobalRegionManager().get(event.getWorld());
if (manager != null) {
Chunk chunk = event.getChunk();
manager.unloadChunk(new Vector2D(chunk.getX(), chunk.getZ()));
}
}
@EventHandler(ignoreCancelled = true)
public void onPlaceBlock(PlaceBlockEvent event) {
Player player = event.getCause().getPlayerRootCause();

View File

@ -19,6 +19,7 @@
package com.sk89q.worldguard.protection;
import com.sk89q.worldedit.Vector2D;
import com.sk89q.worldguard.LocalPlayer;
import com.sk89q.worldguard.bukkit.BukkitUtil;
import com.sk89q.worldguard.bukkit.ConfigurationManager;
@ -26,14 +27,17 @@
import com.sk89q.worldguard.bukkit.WorldGuardPlugin;
import com.sk89q.worldguard.protection.flags.StateFlag;
import com.sk89q.worldguard.protection.managers.RegionManager;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.sk89q.worldguard.bukkit.BukkitUtil.toVector;
public class GlobalRegionManager {
@ -50,7 +54,16 @@ public GlobalRegionManager(WorldGuardPlugin plugin) {
@Nullable
public RegionManager load(World world) {
return container.load(world.getName());
checkNotNull(world);
RegionManager manager = container.load(world.getName());
if (manager != null) {
List<Vector2D> positions = new ArrayList<Vector2D>();
for (Chunk chunk : world.getLoadedChunks()) {
positions.add(new Vector2D(chunk.getX(), chunk.getZ()));
}
manager.loadChunks(positions);
}
return manager;
}
public void preload() {
@ -59,6 +72,10 @@ public void preload() {
}
}
public void unload(World world) {
unload(world.getName());
}
public void unload(String name) {
container.unload(name);
}

View File

@ -22,6 +22,7 @@
import com.google.common.base.Supplier;
import com.sk89q.worldguard.bukkit.ConfigurationManager;
import com.sk89q.worldguard.protection.managers.RegionManager;
import com.sk89q.worldguard.protection.managers.index.ChunkHashTable;
import com.sk89q.worldguard.protection.managers.index.ConcurrentRegionIndex;
import com.sk89q.worldguard.protection.managers.index.PriorityRTreeIndex;
import com.sk89q.worldguard.protection.managers.storage.RegionStore;
@ -57,7 +58,7 @@ class ManagerContainer {
private final Object lock = new Object();
private final EnumMap<DriverType, RegionStoreDriver> drivers = new EnumMap<DriverType, RegionStoreDriver>(DriverType.class);
private final RegionStoreDriver defaultDriver;
private final Supplier<? extends ConcurrentRegionIndex> indexFactory = new PriorityRTreeIndex.Factory();
private final Supplier<? extends ConcurrentRegionIndex> indexFactory = new ChunkHashTable.Factory(new PriorityRTreeIndex.Factory());
private final Timer timer = new Timer();
ManagerContainer(ConfigurationManager config) {

View File

@ -22,6 +22,7 @@
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.Vector2D;
import com.sk89q.worldguard.LocalPlayer;
import com.sk89q.worldguard.protection.ApplicableRegionSet;
import com.sk89q.worldguard.protection.managers.index.ConcurrentRegionIndex;
@ -29,6 +30,7 @@
import com.sk89q.worldguard.protection.managers.storage.DifferenceSaveException;
import com.sk89q.worldguard.protection.managers.storage.RegionStore;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
import com.sk89q.worldguard.protection.util.RegionCollectionConsumer;
import com.sk89q.worldguard.util.Normal;
import javax.annotation.Nullable;
@ -113,6 +115,33 @@ public void saveChanges() throws IOException {
}
}
/**
* Load the regions for a chunk.
*
* @param position the position
*/
public void loadChunk(Vector2D position) {
index.bias(position);
}
/**
* Load the regions for a chunk.
*
* @param positions a collection of positions
*/
public void loadChunks(Collection<Vector2D> positions) {
index.biasAll(positions);
}
/**
* Unload the regions for a chunk.
*
* @param position the position
*/
public void unloadChunk(Vector2D position) {
index.forget(position);
}
/**
* Get an unmodifiable map of regions containing the state of the
* index at the time of call.

View File

@ -0,0 +1,335 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldguard.protection.managers.index;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.sk89q.odeum.concurrent.EvenMoreExecutors;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.Vector2D;
import com.sk89q.worldguard.protection.managers.RegionDifference;
import com.sk89q.worldguard.protection.managers.RemovalStrategy;
import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
import com.sk89q.worldguard.protection.util.RegionCollectionConsumer;
import com.sk89q.worldguard.util.collect.LongHashTable;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Maintains a hash table for each chunk containing a list of regions that
* are contained within that chunk, allowing for fast spatial lookup.
*/
public class ChunkHashTable implements ConcurrentRegionIndex {
private ListeningExecutorService executor = createExecutor();
private LongHashTable<ChunkState> states = new LongHashTable<ChunkState>();
private final RegionIndex index;
private final Object lock = new Object();
/**
* Create a new instance.
*
* @param index the index
*/
public ChunkHashTable(RegionIndex index) {
checkNotNull(index);
this.index = index;
}
/**
* Create an executor.
*
* @return an executor service
*/
private ListeningExecutorService createExecutor() {
return MoreExecutors.listeningDecorator(
EvenMoreExecutors.newBoundedCachedThreadPool(0, 4, Integer.MAX_VALUE));
}
/**
* Get a state object at the given position.
*
* @param position the position
* @param create true to create an entry if one does not exist
* @return a chunk state object, or {@code null} (only if {@code create} is false)
*/
@Nullable
private ChunkState get(Vector2D position, boolean create) {
ChunkState state;
synchronized (lock) {
state = states.get(position.getBlockX(), position.getBlockZ());
if (state == null && create) {
state = new ChunkState(position);
states.put(position.getBlockX(), position.getBlockZ(), state);
executor.submit(new EnumerateRegions(position));
}
}
return state;
}
/**
* Get a state at the given position or create a new entry if one does
* not exist.
*
* @param position the position
* @return a state
*/
private ChunkState getOrCreate(Vector2D position) {
return get(position, true);
}
/**
* Clear the current hash table and rebuild it in the background.
*/
private void rebuild() {
synchronized (lock) {
ListeningExecutorService previousExecutor = executor;
LongHashTable<ChunkState> previousStates = states;
previousExecutor.shutdownNow();
states = new LongHashTable<ChunkState>();
executor = createExecutor();
List<Vector2D> positions = new ArrayList<Vector2D>();
for (ChunkState state : previousStates.values()) {
Vector2D position = state.getPosition();
positions.add(position);
states.put(position.getBlockX(), position.getBlockZ(), new ChunkState(position));
}
if (!positions.isEmpty()) {
executor.submit(new EnumerateRegions(positions));
}
}
}
@Override
public void bias(Vector2D chunkPosition) {
checkNotNull(chunkPosition);
getOrCreate(chunkPosition);
}
@Override
public void biasAll(Collection<Vector2D> chunkPositions) {
synchronized (lock) {
for (Vector2D position : chunkPositions) {
bias(position);
}
}
}
@Override
public void forget(Vector2D chunkPosition) {
checkNotNull(chunkPosition);
synchronized (lock) {
states.remove(chunkPosition.getBlockX(), chunkPosition.getBlockZ());
}
}
@Override
public void forgetAll() {
synchronized (lock) {
executor.shutdownNow();
states = new LongHashTable<ChunkState>();
executor = createExecutor();
}
}
@Override
public void add(ProtectedRegion region) {
index.add(region);
rebuild();
}
@Override
public void addAll(Collection<ProtectedRegion> regions) {
index.addAll(regions);
rebuild();
}
@Override
public Set<ProtectedRegion> remove(String id, RemovalStrategy strategy) {
Set<ProtectedRegion> removed = index.remove(id, strategy);
rebuild();
return removed;
}
@Override
public boolean contains(String id) {
return index.contains(id);
}
@Nullable
@Override
public ProtectedRegion get(String id) {
return index.get(id);
}
@Override
public void apply(Predicate<ProtectedRegion> consumer) {
index.apply(consumer);
}
@Override
public void applyContaining(Vector position, Predicate<ProtectedRegion> consumer) {
checkNotNull(position);
checkNotNull(consumer);
ChunkState state = get(new Vector2D(position.getBlockX() >> 4, position.getBlockZ() >> 4), false);
if (state != null && state.isLoaded()) {
for (ProtectedRegion region : state.getRegions()) {
if (region.contains(position)) {
consumer.apply(region);
}
}
} else {
index.applyContaining(position, consumer);
}
}
@Override
public void applyIntersecting(ProtectedRegion region, Predicate<ProtectedRegion> consumer) {
index.applyIntersecting(region, consumer);
}
@Override
public int size() {
return index.size();
}
@Override
public RegionDifference getAndClearDifference() {
return index.getAndClearDifference();
}
@Override
public Collection<ProtectedRegion> values() {
return index.values();
}
@Override
public boolean isDirty() {
return index.isDirty();
}
@Override
public void setDirty(boolean dirty) {
index.setDirty(dirty);
}
/**
* A task to enumerate the regions for a list of provided chunks.
*/
private class EnumerateRegions implements Runnable {
private final List<Vector2D> positions;
private EnumerateRegions(Vector2D position) {
this(Arrays.asList(checkNotNull(position)));
}
private EnumerateRegions(List<Vector2D> positions) {
checkNotNull(positions);
checkArgument(!positions.isEmpty(), "List of positions can't be empty");
this.positions = positions;
}
@Override
public void run() {
for (Vector2D position : positions) {
ChunkState state = get(position, false);
if (state != null) {
List<ProtectedRegion> regions = new ArrayList<ProtectedRegion>();
ProtectedRegion chunkRegion = new ProtectedCuboidRegion(
"_",
position.toVector(0).toBlockVector(),
position.add(16, 16).toVector(Integer.MAX_VALUE).toBlockVector());
index.applyIntersecting(chunkRegion, new RegionCollectionConsumer(regions, false));
state.setRegions(Collections.unmodifiableList(regions));
if (Thread.currentThread().isInterrupted()) {
return;
}
}
}
}
}
/**
* Stores a cache of region data for a chunk.
*/
private class ChunkState {
private final Vector2D position;
private boolean loaded = false;
private List<ProtectedRegion> regions = Collections.emptyList();
private ChunkState(Vector2D position) {
this.position = position;
}
public Vector2D getPosition() {
return position;
}
public List<ProtectedRegion> getRegions() {
return regions;
}
public void setRegions(List<ProtectedRegion> regions) {
this.regions = regions;
this.loaded = true;
}
public boolean isLoaded() {
return loaded;
}
}
/**
* A factory for instances of {@code ChunkHashCache}.
*/
public static class Factory implements Supplier<ChunkHashTable> {
private final Supplier<? extends ConcurrentRegionIndex> supplier;
public Factory(Supplier<? extends ConcurrentRegionIndex> supplier) {
checkNotNull(supplier);
this.supplier = supplier;
}
@Override
public ChunkHashTable get() {
return new ChunkHashTable(supplier.get());
}
}
}

View File

@ -22,6 +22,7 @@
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.Vector2D;
import com.sk89q.worldguard.protection.managers.RegionDifference;
import com.sk89q.worldguard.protection.managers.RemovalStrategy;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
@ -102,6 +103,26 @@ public void addAll(Collection<ProtectedRegion> regions) {
}
}
@Override
public void bias(Vector2D chunkPosition) {
// Nothing to do
}
@Override
public void biasAll(Collection<Vector2D> chunkPositions) {
// Nothing to do
}
@Override
public void forget(Vector2D chunkPosition) {
// Nothing to do
}
@Override
public void forgetAll() {
// Nothing to do
}
@Override
public void add(ProtectedRegion region) {
synchronized (lock) {

View File

@ -21,6 +21,7 @@
import com.google.common.base.Predicate;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.Vector2D;
import com.sk89q.worldguard.protection.managers.RegionDifference;
import com.sk89q.worldguard.protection.managers.RemovalStrategy;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
@ -39,6 +40,37 @@
*/
public interface RegionIndex extends ChangeTracked {
/**
* Bias the given chunk for faster lookups (put it in a hash table, etc.).
*
* <p>Implementations may choose to do nothing.</p>
*
* @param chunkPosition the chunk position
*/
void bias(Vector2D chunkPosition);
/**
* Bias the given chunk for faster lookups (put it in a hash table, etc.).
*
* <p>Implementations may choose to do nothing.</p>
*
* @param chunkPosition the chunk position
*/
void biasAll(Collection<Vector2D> chunkPosition);
/**
* No longer bias the given chunk for faster lookup.
*
* @param chunkPosition the chunk position
*/
void forget(Vector2D chunkPosition);
/**
* Clearly all extra cache data created by any calls to
* {@link #bias(Vector2D)}.
*/
void forgetAll();
/**
* Add a region to this index, replacing any existing one with the same
* name (equality determined using {@link Normal}).

View File

@ -17,7 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldguard.protection.managers;
package com.sk89q.worldguard.protection.util;
import com.google.common.base.Predicate;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
@ -33,7 +33,7 @@
* to the collection, although it may result in duplicates in the collection
* if the collection is not a set.</p>
*/
class RegionCollectionConsumer implements Predicate<ProtectedRegion> {
public class RegionCollectionConsumer implements Predicate<ProtectedRegion> {
private final Collection<ProtectedRegion> collection;
private final boolean addParents;
@ -44,7 +44,7 @@ class RegionCollectionConsumer implements Predicate<ProtectedRegion> {
* @param collection the collection to add regions to
* @param addParents true to also add the parents to the collection
*/
RegionCollectionConsumer(Collection<ProtectedRegion> collection, boolean addParents) {
public RegionCollectionConsumer(Collection<ProtectedRegion> collection, boolean addParents) {
checkNotNull(collection);
this.collection = collection;

View File

@ -0,0 +1,30 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldguard.util.collect;
public class EntryBase {
protected long key;
public EntryBase(long key) {
this.key = key;
}
}

View File

@ -0,0 +1,130 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldguard.util.collect;
import java.util.ArrayList;
import java.util.Arrays;
public class LongBaseHashTable extends LongHash {
EntryBase[][][] values = new EntryBase[256][][];
EntryBase cache = null;
public void put(int msw, int lsw, EntryBase entry) {
put(entry);
}
public EntryBase getEntry(int msw, int lsw) {
return getEntry(toLong(msw, lsw));
}
public synchronized void put(EntryBase entry) {
int mainIdx = (int) (entry.key & 255);
EntryBase[][] outer = this.values[mainIdx];
if (outer == null) this.values[mainIdx] = outer = new EntryBase[256][];
int outerIdx = (int) ((entry.key >> 32) & 255);
EntryBase[] inner = outer[outerIdx];
if (inner == null) {
outer[outerIdx] = inner = new EntryBase[5];
inner[0] = this.cache = entry;
} else {
int i;
for (i = 0; i < inner.length; i++) {
if (inner[i] == null || inner[i].key == entry.key) {
inner[i] = this.cache = entry;
return;
}
}
outer[outerIdx] = inner = Arrays.copyOf(inner, i + i);
inner[i] = entry;
}
}
public synchronized EntryBase getEntry(long key) {
return containsKey(key) ? cache : null;
}
public synchronized boolean containsKey(long key) {
if (this.cache != null && cache.key == key) return true;
int outerIdx = (int) ((key >> 32) & 255);
EntryBase[][] outer = this.values[(int) (key & 255)];
if (outer == null) return false;
EntryBase[] inner = outer[outerIdx];
if (inner == null) return false;
for (int i = 0; i < inner.length; i++) {
EntryBase e = inner[i];
if (e == null) {
return false;
} else if (e.key == key) {
this.cache = e;
return true;
}
}
return false;
}
public synchronized void remove(long key) {
EntryBase[][] outer = this.values[(int) (key & 255)];
if (outer == null) return;
EntryBase[] inner = outer[(int) ((key >> 32) & 255)];
if (inner == null) return;
for (int i = 0; i < inner.length; i++) {
if (inner[i] == null) continue;
if (inner[i].key == key) {
for (i++; i < inner.length; i++) {
if (inner[i] == null) break;
inner[i - 1] = inner[i];
}
inner[i-1] = null;
this.cache = null;
return;
}
}
}
public synchronized ArrayList<EntryBase> entries() {
ArrayList<EntryBase> ret = new ArrayList<EntryBase>();
for (EntryBase[][] outer : this.values) {
if (outer == null) continue;
for (EntryBase[] inner : outer) {
if (inner == null) continue;
for (EntryBase entry : inner) {
if (entry == null) break;
ret.add(entry);
}
}
}
return ret;
}
}

View File

@ -0,0 +1,48 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldguard.util.collect;
public abstract class LongHash {
public static long toLong(int msw, int lsw) {
return ((long) msw << 32) + lsw - Integer.MIN_VALUE;
}
public static int msw(long l) {
return (int) (l >> 32);
}
public static int lsw(long l) {
return (int) (l & 0xFFFFFFFF) + Integer.MIN_VALUE;
}
public boolean containsKey(int msw, int lsw) {
return containsKey(toLong(msw, lsw));
}
public void remove(int msw, int lsw) {
remove(toLong(msw, lsw));
}
public abstract boolean containsKey(long key);
public abstract void remove(long key);
}

View File

@ -0,0 +1,199 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldguard.util.collect;
import java.util.Arrays;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
public class LongHashSet extends LongHash {
protected long[][][] values = new long[256][][];
protected int count = 0;
protected ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
protected ReadLock rl = rwl.readLock();
protected WriteLock wl = rwl.writeLock();
public boolean isEmpty() {
rl.lock();
try {
return this.count == 0;
} finally {
rl.unlock();
}
}
public int size() {
return count;
}
public void add(int msw, int lsw) {
add(toLong(msw, lsw));
}
public void add(long key) {
wl.lock();
try {
int mainIdx = (int) (key & 255);
long outer[][] = this.values[mainIdx];
if (outer == null) this.values[mainIdx] = outer = new long[256][];
int outerIdx = (int) ((key >> 32) & 255);
long inner[] = outer[outerIdx];
if (inner == null) {
synchronized (this) {
outer[outerIdx] = inner = new long[1];
inner[0] = key;
this.count++;
}
} else {
int i;
for (i = 0; i < inner.length; i++) {
if (inner[i] == key) {
return;
}
}
inner = Arrays.copyOf(inner, i + 1);
outer[outerIdx] = inner;
inner[i] = key;
this.count++;
}
} finally {
wl.unlock();
}
}
public boolean containsKey(long key) {
rl.lock();
try {
long[][] outer = this.values[(int) (key & 255)];
if (outer == null) return false;
long[] inner = outer[(int) ((key >> 32) & 255)];
if (inner == null) return false;
for (long entry : inner) {
if (entry == key) return true;
}
return false;
} finally {
rl.unlock();
}
}
public void remove(long key) {
wl.lock();
try {
long[][] outer = this.values[(int) (key & 255)];
if (outer == null) return;
long[] inner = outer[(int) ((key >> 32) & 255)];
if (inner == null) return;
int max = inner.length - 1;
for (int i = 0; i <= max; i++) {
if (inner[i] == key) {
this.count--;
if (i != max) {
inner[i] = inner[max];
}
outer[(int) ((key >> 32) & 255)] = (max == 0 ? null : Arrays.copyOf(inner, max));
return;
}
}
} finally {
wl.unlock();
}
}
public long popFirst() {
wl.lock();
try {
for (long[][] outer: this.values) {
if (outer == null) continue;
for (int i = 0; i < outer.length; i++) {
long[] inner = outer[i];
if (inner == null || inner.length == 0) continue;
this.count--;
long ret = inner[inner.length - 1];
outer[i] = Arrays.copyOf(inner, inner.length - 1);
return ret;
}
}
} finally {
wl.unlock();
}
return 0;
}
public long[] popAll() {
int index = 0;
wl.lock();
try {
long[] ret = new long[this.count];
for (long[][] outer : this.values) {
if (outer == null) continue;
for (int oIdx = outer.length - 1; oIdx >= 0; oIdx--) {
long[] inner = outer[oIdx];
if (inner == null) continue;
for (long entry: inner) {
ret[index++] = entry;
}
outer[oIdx] = null;
}
}
count = 0;
return ret;
} finally {
wl.unlock();
}
}
public long[] keys() {
int index = 0;
rl.lock();
try {
long[] ret = new long[this.count];
for (long[][] outer : this.values) {
if (outer == null) continue;
for (long[] inner : outer) {
if (inner == null) continue;
for (long entry : inner) {
ret[index++] = entry;
}
}
}
return ret;
} finally {
rl.unlock();
}
}
}

View File

@ -0,0 +1,65 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldguard.util.collect;
import java.util.ArrayList;
public class LongHashTable<V> extends LongBaseHashTable {
public void put(int msw, int lsw, V value) {
put(toLong(msw, lsw), value);
}
public V get(int msw, int lsw) {
return get(toLong(msw, lsw));
}
public synchronized void put(long key, V value) {
put(new Entry(key, value));
}
@SuppressWarnings("unchecked")
public synchronized V get(long key) {
Entry entry = ((Entry) getEntry(key));
return entry != null ? entry.value : null;
}
@SuppressWarnings("unchecked")
public synchronized ArrayList<V> values() {
ArrayList<V> ret = new ArrayList<V>();
ArrayList<EntryBase> entries = entries();
for (EntryBase entry : entries) {
ret.add(((Entry) entry).value);
}
return ret;
}
private class Entry extends EntryBase {
V value;
Entry(long k, V v) {
super(k);
this.value = v;
}
}
}