/*
* Copyright 2011 Tyler Blair. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and contributors and should not be interpreted as representing official policies,
* either expressed or implied, of anybody else.
*/
package com.griefcraft.cache;
import com.griefcraft.lwc.LWC;
import com.griefcraft.model.Protection;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
public class ProtectionCache {
/**
* The amount the cache is increased by each time a high-intensity area requests it if needed
*/
private final static int ADAPTIVE_CACHE_TICK = 10;
/**
* The max number of protections the adaptive cache will add
*/
private final static int ADAPTIVE_CACHE_MAX = 100000;
/**
* The LWC instance this set belongs to
*/
private final LWC lwc;
/**
* Hard references to protections still cached
*/
private final LRUCache<Protection, Object> references;
/**
* Weak references to protections and their cache key (protection.getCacheKey())
*/
private final WeakLRUCache<String, Protection> byCacheKey;
/**
* Weak references to protections and their protection id
*/
private final WeakLRUCache<Integer, Protection> byId;
/**
* A block that isn't the protected block itself but matches it in a protection matcher
*/
private final WeakLRUCache<String, Protection> byKnownBlock;
/**
* A cache of blocks that are known to not have a protection
*/
private final LRUCache<String, Object> byKnownNulls;
/**
* The capacity of the cache
*/
private int capacity;
/**
* The number of protections that were added via adaptive cache
*/
private int adaptiveCapacity = 0;
/**
* The method counter
*/
private final MethodCounter counter = new MethodCounter();
/**
* Used for byKnownNulls
*/
private final static Object FAKE_VALUE = new Object();
public ProtectionCache(LWC lwc) {
this.lwc = lwc;
this.capacity = lwc.getConfiguration().getInt("core.cacheSize", 10000);
this.references = new LRUCache<Protection, Object>(capacity);
this.byCacheKey = new WeakLRUCache<String, Protection>(capacity);
this.byId = new WeakLRUCache<Integer, Protection>(capacity);
this.byKnownBlock = new WeakLRUCache<String, Protection>(capacity);
this.byKnownNulls = new LRUCache<String, Object>(Math.min(10000, capacity)); // enforce a min size so we have a known buffer
}
/**
* Called from specific potentially high-intensity access areas. These areas preferably need(!) free space in the
* cache and otherwise could cause "lag" or other oddities.
*/
public void increaseIfNecessary() {
if (isFull() && adaptiveCapacity < ADAPTIVE_CACHE_MAX) {
adaptiveCapacity += ADAPTIVE_CACHE_TICK;
adjustCacheSizes();
}
}
/**
* Gets the direct reference of the references cache
*
* @return
*/
public LRUCache<Protection, Object> getReferences() {
return references;
}
/**
* Get the method counter for this class
*
* @return
*/
public MethodCounter getMethodCounter() {
return counter;
}
/**
* Gets the default capacity of the cache
*
* @return
*/
public int capacity() {
return capacity;
}
/**
* Gets the adaptive capacity of the cache
*
* @return
*/
public int adaptiveCapacity() {
return adaptiveCapacity;
}
/**
* Gets the total capacity (default + adaptive) of the cache
*
* @return
*/
public int totalCapacity() {
return capacity + adaptiveCapacity;
}
/**
* Clears the entire protection cache
*/
public void clear() {
// remove hard refs
references.clear();
// remove weak refs
byCacheKey.clear();
byId.clear();
byKnownBlock.clear();
byKnownNulls.clear();
}
/**
* Check if the cache is full
*
* @return
*/
public boolean isFull() {
return references.size() >= totalCapacity();
}
/**
* Gets the amount of protections that are cached
*
* @return
*/
public int size() {
return references.size();
}
/**
* Cache a protection
*
* @param protection
*/
public void addProtection(Protection protection) {
if (protection == null) {
return;
}
counter.increment("addProtection");
// Add the hard reference
references.put(protection, null);
// Add weak references which are used to lookup protections
byCacheKey.put(protection.getCacheKey(), protection);
byId.put(protection.getId(), protection);
// get the protection's finder if it was found via that
if (protection.getProtectionFinder() != null) {
Block protectedBlock = protection.getBlock();
for (BlockState state : protection.getProtectionFinder().getBlocks()) {
if (!protectedBlock.equals(state.getBlock())) {
String cacheKey = cacheKey(state.getLocation());
byKnownBlock.put(cacheKey, protection);
}
}
}
}
/**
* Remove the protection from the cache
*
* @param protection
*/
public void removeProtection(Protection protection) {
counter.increment("removeProtection");
references.remove(protection);
byId.remove(protection.getId());
if (protection.getProtectionFinder() != null) {
for (BlockState state : protection.getProtectionFinder().getBlocks()) {
remove(cacheKey(state.getLocation()));
}
}
}
/**
* Remove the given cache key from any caches
*
* @param cacheKey
*/
public void remove(String cacheKey) {
byCacheKey.remove(cacheKey);
byKnownBlock.remove(cacheKey);
byKnownNulls.remove(cacheKey);
}
/**
* Make a cache key known as null in the cache
*
* @param cacheKey
*/
public void addKnownNull(String cacheKey) {
counter.increment("addKnownNull");
byKnownNulls.put(cacheKey, FAKE_VALUE);
}
/**
* Check if a cache key is known to not exist in the database
*
* @param cacheKey
* @return
*/
public boolean isKnownNull(String cacheKey) {
counter.increment("isKnownNull");
return byKnownNulls.containsKey(cacheKey);
}
/**
* Get a protection in the cache via its cache key
*
* @param cacheKey
* @return
*/
public Protection getProtection(String cacheKey) {
counter.increment("getProtection");
Protection protection;
// Check the direct cache first
if ((protection = byCacheKey.get(cacheKey)) != null) {
return protection;
}
// now use the 'others' cache
return byKnownBlock.get(cacheKey);
}
/**
* Get a protection in the cache located on the given block
*
* @param block
* @return
*/
public Protection getProtection(Block block) {
return getProtection(cacheKey(block.getWorld().getName(), block.getX(), block.getY(), block.getZ()));
}
/**
* Get a protection in the cache located on the given block
*
* @param block
* @return
*/
public Protection getProtection(BlockState block) {
return getProtection(cacheKey(block.getWorld().getName(), block.getX(), block.getY(), block.getZ()));
}
/**
* Check if the known block protection cache contains the given key
*
* @param block
* @return
*/
public boolean isKnownBlock(Block block) {
counter.increment("isKnownBlock");
return byKnownBlock.containsKey(cacheKey(block.getWorld().getName(), block.getX(), block.getY(), block.getZ()));
}
/**
* Get a protection in the cache via its id
*
* @param id
* @return
*/
public Protection getProtectionById(int id) {
counter.increment("getProtectionById");
return byId.get(id);
}
/**
* Gets the cache key for the given location
*
* @param location
* @return
*/
public String cacheKey(Location location) {
return cacheKey(location.getWorld().getName(), location.getBlockX(), location.getBlockY(), location.getBlockZ());
}
/**
* Generate a cache key using the given data
*
* @param world
* @param x
* @param y
* @param z
* @return
*/
private String cacheKey(String world, int x, int y, int z) {
return world + ":" + x + ":" + y + ":" + z;
}
/**
* Fixes the internal caches and adjusts them to the new cache total capacity
*/
private void adjustCacheSizes() {
references.maxCapacity = totalCapacity();
byCacheKey.maxCapacity = totalCapacity();
byId.maxCapacity = totalCapacity();
byKnownBlock.maxCapacity = totalCapacity();
byKnownNulls.maxCapacity = totalCapacity();
}
}