diff --git a/src/main/java/com/sk89q/worldguard/bukkit/listener/RegionProtectionListener.java b/src/main/java/com/sk89q/worldguard/bukkit/listener/RegionProtectionListener.java index ee6bc082..d800380e 100644 --- a/src/main/java/com/sk89q/worldguard/bukkit/listener/RegionProtectionListener.java +++ b/src/main/java/com/sk89q/worldguard/bukkit/listener/RegionProtectionListener.java @@ -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(); diff --git a/src/main/java/com/sk89q/worldguard/protection/GlobalRegionManager.java b/src/main/java/com/sk89q/worldguard/protection/GlobalRegionManager.java index b5260092..2810df23 100644 --- a/src/main/java/com/sk89q/worldguard/protection/GlobalRegionManager.java +++ b/src/main/java/com/sk89q/worldguard/protection/GlobalRegionManager.java @@ -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 positions = new ArrayList(); + 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); } diff --git a/src/main/java/com/sk89q/worldguard/protection/ManagerContainer.java b/src/main/java/com/sk89q/worldguard/protection/ManagerContainer.java index 521f0fc1..2ce82afb 100644 --- a/src/main/java/com/sk89q/worldguard/protection/ManagerContainer.java +++ b/src/main/java/com/sk89q/worldguard/protection/ManagerContainer.java @@ -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 drivers = new EnumMap(DriverType.class); private final RegionStoreDriver defaultDriver; - private final Supplier indexFactory = new PriorityRTreeIndex.Factory(); + private final Supplier indexFactory = new ChunkHashTable.Factory(new PriorityRTreeIndex.Factory()); private final Timer timer = new Timer(); ManagerContainer(ConfigurationManager config) { diff --git a/src/main/java/com/sk89q/worldguard/protection/managers/RegionManager.java b/src/main/java/com/sk89q/worldguard/protection/managers/RegionManager.java index 3ccfe844..122c4e35 100644 --- a/src/main/java/com/sk89q/worldguard/protection/managers/RegionManager.java +++ b/src/main/java/com/sk89q/worldguard/protection/managers/RegionManager.java @@ -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 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. diff --git a/src/main/java/com/sk89q/worldguard/protection/managers/index/ChunkHashTable.java b/src/main/java/com/sk89q/worldguard/protection/managers/index/ChunkHashTable.java new file mode 100644 index 00000000..07edebc1 --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/protection/managers/index/ChunkHashTable.java @@ -0,0 +1,335 @@ +/* + * 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.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 states = new LongHashTable(); + 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 previousStates = states; + + previousExecutor.shutdownNow(); + states = new LongHashTable(); + executor = createExecutor(); + + List positions = new ArrayList(); + 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 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(); + executor = createExecutor(); + } + } + + @Override + public void add(ProtectedRegion region) { + index.add(region); + rebuild(); + } + + @Override + public void addAll(Collection regions) { + index.addAll(regions); + rebuild(); + } + + @Override + public Set remove(String id, RemovalStrategy strategy) { + Set 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 consumer) { + index.apply(consumer); + } + + @Override + public void applyContaining(Vector position, Predicate 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 consumer) { + index.applyIntersecting(region, consumer); + } + + @Override + public int size() { + return index.size(); + } + + @Override + public RegionDifference getAndClearDifference() { + return index.getAndClearDifference(); + } + + @Override + public Collection 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 positions; + + private EnumerateRegions(Vector2D position) { + this(Arrays.asList(checkNotNull(position))); + } + + private EnumerateRegions(List 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 regions = new ArrayList(); + 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 regions = Collections.emptyList(); + + private ChunkState(Vector2D position) { + this.position = position; + } + + public Vector2D getPosition() { + return position; + } + + public List getRegions() { + return regions; + } + + public void setRegions(List 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 { + private final Supplier supplier; + + public Factory(Supplier supplier) { + checkNotNull(supplier); + this.supplier = supplier; + } + + @Override + public ChunkHashTable get() { + return new ChunkHashTable(supplier.get()); + } + } + +} diff --git a/src/main/java/com/sk89q/worldguard/protection/managers/index/HashMapIndex.java b/src/main/java/com/sk89q/worldguard/protection/managers/index/HashMapIndex.java index aaa86a93..6d9f43ba 100644 --- a/src/main/java/com/sk89q/worldguard/protection/managers/index/HashMapIndex.java +++ b/src/main/java/com/sk89q/worldguard/protection/managers/index/HashMapIndex.java @@ -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 regions) { } } + @Override + public void bias(Vector2D chunkPosition) { + // Nothing to do + } + + @Override + public void biasAll(Collection 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) { diff --git a/src/main/java/com/sk89q/worldguard/protection/managers/index/RegionIndex.java b/src/main/java/com/sk89q/worldguard/protection/managers/index/RegionIndex.java index 76e43812..264dceae 100644 --- a/src/main/java/com/sk89q/worldguard/protection/managers/index/RegionIndex.java +++ b/src/main/java/com/sk89q/worldguard/protection/managers/index/RegionIndex.java @@ -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.). + * + *

Implementations may choose to do nothing.

+ * + * @param chunkPosition the chunk position + */ + void bias(Vector2D chunkPosition); + + /** + * Bias the given chunk for faster lookups (put it in a hash table, etc.). + * + *

Implementations may choose to do nothing.

+ * + * @param chunkPosition the chunk position + */ + void biasAll(Collection 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}). diff --git a/src/main/java/com/sk89q/worldguard/protection/managers/RegionCollectionConsumer.java b/src/main/java/com/sk89q/worldguard/protection/util/RegionCollectionConsumer.java similarity index 90% rename from src/main/java/com/sk89q/worldguard/protection/managers/RegionCollectionConsumer.java rename to src/main/java/com/sk89q/worldguard/protection/util/RegionCollectionConsumer.java index 0ab9d6fd..a36554ff 100644 --- a/src/main/java/com/sk89q/worldguard/protection/managers/RegionCollectionConsumer.java +++ b/src/main/java/com/sk89q/worldguard/protection/util/RegionCollectionConsumer.java @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -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.

*/ -class RegionCollectionConsumer implements Predicate { +public class RegionCollectionConsumer implements Predicate { private final Collection collection; private final boolean addParents; @@ -44,7 +44,7 @@ class RegionCollectionConsumer implements Predicate { * @param collection the collection to add regions to * @param addParents true to also add the parents to the collection */ - RegionCollectionConsumer(Collection collection, boolean addParents) { + public RegionCollectionConsumer(Collection collection, boolean addParents) { checkNotNull(collection); this.collection = collection; diff --git a/src/main/java/com/sk89q/worldguard/util/collect/EntryBase.java b/src/main/java/com/sk89q/worldguard/util/collect/EntryBase.java new file mode 100644 index 00000000..41a58ec5 --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/util/collect/EntryBase.java @@ -0,0 +1,30 @@ +/* + * 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.util.collect; + +public class EntryBase { + + protected long key; + + public EntryBase(long key) { + this.key = key; + } + +} diff --git a/src/main/java/com/sk89q/worldguard/util/collect/LongBaseHashTable.java b/src/main/java/com/sk89q/worldguard/util/collect/LongBaseHashTable.java new file mode 100644 index 00000000..0ae50759 --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/util/collect/LongBaseHashTable.java @@ -0,0 +1,130 @@ +/* + * 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.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 entries() { + ArrayList ret = new ArrayList(); + + 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; + } +} diff --git a/src/main/java/com/sk89q/worldguard/util/collect/LongHash.java b/src/main/java/com/sk89q/worldguard/util/collect/LongHash.java new file mode 100644 index 00000000..89554007 --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/util/collect/LongHash.java @@ -0,0 +1,48 @@ +/* + * 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.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); + +} diff --git a/src/main/java/com/sk89q/worldguard/util/collect/LongHashSet.java b/src/main/java/com/sk89q/worldguard/util/collect/LongHashSet.java new file mode 100644 index 00000000..a23789d1 --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/util/collect/LongHashSet.java @@ -0,0 +1,199 @@ +/* + * 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.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(); + } + } + +} diff --git a/src/main/java/com/sk89q/worldguard/util/collect/LongHashTable.java b/src/main/java/com/sk89q/worldguard/util/collect/LongHashTable.java new file mode 100644 index 00000000..46462562 --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/util/collect/LongHashTable.java @@ -0,0 +1,65 @@ +/* + * 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.util.collect; + +import java.util.ArrayList; + +public class LongHashTable 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 values() { + ArrayList ret = new ArrayList(); + + ArrayList 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; + } + } + +}