package com.asteria.game.character.player.serialize;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.asteria.game.World;
import com.asteria.service.Service;
import com.asteria.service.ServiceQueue;
import com.asteria.utility.LoggerUtils;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.gson.JsonObject;
/**
* The wrapper for the cache that will store character files on logout for later
* use. We cache character files on logout for {@code 15} minutes so that if the
* player logs out within that time period, we don't have to reload the
* character file. Many benefits can be seen with this on larger servers
* especially, where players are constantly logging in and out within short
* periods of time.
* <p>
* <p>
* Caches are thread safe, so functions within this class can be executed within
* multiple threads.
*
* @author lare96 <http://github.com/lare96>
*/
public final class PlayerSerializationCache extends Service {
/**
* The logger that will print important information.
*/
private final Logger logger = LoggerUtils.getLogger(PlayerSerializationCache.class);
/**
* The collection of character files that have been cached by the
* {@link PlayerSerializer}. These character files will be removed from the
* cache {@code 15} minutes after they've been added to free up memory.
*/
private final Cache<Long, JsonObject> cache = CacheBuilder.newBuilder().initialCapacity(100).expireAfterWrite(15, TimeUnit.MINUTES)
.concurrencyLevel(2).build();
/**
* The flag that determines if entries should be automatically invalidated.
*/
private final boolean automatic;
/**
* Creates a new {@link PlayerSerializationCache} with {@code automatic}
* determining the invalidation policy.
*
* @param automatic
* determines if entries should be automatically invalidated with
* the logic service, or if the cache should manually invalidate
* entries upon reads and writes.
*/
public PlayerSerializationCache(boolean automatic) {
super(15, TimeUnit.MINUTES);
this.automatic = automatic;
}
@Override
public void execute(ServiceQueue context) {
try {
cache.cleanUp();
} catch (Throwable t) {
// Clean up fail, discard all entries.
logger.log(Level.SEVERE, "The player serialization cache failed to clean up!", t);
cache.invalidateAll();
}
}
/**
* Initializes this {@link PlayerSerializationCache} task to be ran every
* {@code 15} minutes by the logic service, only if automatic invalidation
* is enabled.
*/
public void init() {
if (automatic)
World.getService().submit(this);
}
/**
* Inserts an entry for {@code value} with {@code data} into this player
* serialization cache.
*
* @param value
* the username hash of the player.
* @param data
* the data for the character file.
*/
public void add(long value, JsonObject data) {
cache.put(value, data);
}
/**
* Retrieves the character file data for {@code value} wrapped in an
* optional.
*
* @param value
* the username hash of the player.
* @return the data wrapped in an optional if present, or an empty optional
* if not present.
*/
public Optional<JsonObject> get(long value) {
return Optional.ofNullable(cache.getIfPresent(value));
}
/**
* Determines if entries should be automatically invalidated.
*
* @return {@code true} if there is automatic invalidation, {@code false}
* otherwise.
*/
public boolean isAutomatic() {
return automatic;
}
}