Counter refactoring

This commit is contained in:
Shaun Bennett 2016-04-26 13:55:06 +10:00
parent a0fd852537
commit e8370579c0
5 changed files with 196 additions and 42 deletions

View File

@ -8,6 +8,7 @@ import mineplex.core.hologram.Hologram;
import mineplex.core.hologram.HologramManager; import mineplex.core.hologram.HologramManager;
import mineplex.core.stats.StatsManager; import mineplex.core.stats.StatsManager;
import mineplex.serverdata.redis.counter.Counter; import mineplex.serverdata.redis.counter.Counter;
import mineplex.serverdata.redis.counter.GoalCounter;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -25,7 +26,7 @@ public class Fountain
private final Location _location; private final Location _location;
private Location _lavaLocation; private Location _lavaLocation;
private final Hologram _hologram; private final Hologram _hologram;
private final Counter _counter; private final GoalCounter _counter;
private final FountainShop _shop; private final FountainShop _shop;
@ -41,7 +42,7 @@ public class Fountain
_location = location; _location = location;
_lavaLocation = lavaLocation; _lavaLocation = lavaLocation;
_hologram = new Hologram(hologramManager, location.clone().add(0, 2, 0), name).start(); _hologram = new Hologram(hologramManager, location.clone().add(0, 2, 0), name).start();
_counter = new Counter(dataKey, goal); _counter = new GoalCounter(dataKey, 5000, goal);
_shop = new FountainShop(this, fountainManager, clientManager, donationManager); _shop = new FountainShop(this, fountainManager, clientManager, donationManager);
@ -56,19 +57,13 @@ public class Fountain
protected void updateCount() protected void updateCount()
{ {
// Make sure we only update if it has been 5 seconds since the last update updateHologram();
// Players incrementing the counter will automatically cause it to update
if (System.currentTimeMillis() - _counter.getCacheLastUpdated() > 5000)
{
_counter.updateCount();
updateHologram();
}
} }
public void increment(Player player, long amount) public void increment(Player player, long amount)
{ {
_statsManager.incrementStat(player, "Global.Fountain." + getDataKey(), amount); _statsManager.incrementStat(player, "Global.Fountain." + getDataKey(), amount);
_counter.increment(amount); _counter.addAndGet(amount);
updateHologram(); updateHologram();
} }

View File

@ -3,73 +3,100 @@ package mineplex.serverdata.redis.counter;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
/** /**
* A counter represents an atomic number that is trying to hit a goal. Once the counter * A counter represents an incrementing atomic number that is stored and updated through redis. This allows for
* reaches it's goal you can either reset it or let it stay completed for an indefinite time. * multiple servers to share and update the same counter
* *
* @author Shaun Bennett * @author Shaun Bennett
*/ */
public class Counter public class Counter
{ {
// Cached count for the fountain // Cached count of the counter
private AtomicLong _count = new AtomicLong(0); private final AtomicLong _count = new AtomicLong(0);
// The System.currentTimeMillis() when cached count was last updated // The System.currentTimeMillis() when cached count was last updated
private long _cacheLastUpdated; private volatile long _lastUpdated;
// The goal that this fountain is trying to reach // The maximum time to wait before syncing the count with repository
private final long _goal; private final long _syncTime;
// The unique key to reference this counter // The unique key to reference this counter
private final String _dataKey; private final String _dataKey;
// Redis repository to store the count // Redis repository to store the count
private CounterRedisRepository _redisRepository; private final CounterRedisRepository _redisRepository;
public Counter(String dataKey, long goal) public Counter(String dataKey, long syncTime)
{ {
_goal = goal;
_dataKey = dataKey; _dataKey = dataKey;
_syncTime = syncTime;
_redisRepository = new CounterRedisRepository(dataKey); _redisRepository = new CounterRedisRepository(dataKey);
updateCount();
}
public void updateCount()
{
updateCount(_redisRepository.getCount()); updateCount(_redisRepository.getCount());
} }
protected void updateCount(long newCount) public Counter(String dataKey)
{ {
_count.set(newCount); this(dataKey, 5000); // 5 seconds
_cacheLastUpdated = System.currentTimeMillis();
} }
public void increment(long amount) /**
* Add a value to the counter and return the new counter value. This method is thread-safe and interacts
* directly with the atomic value stored in redis. The value returned from redis is then returned
*
* addAndGet will also update the cached counter value so we don't need to make extra trips to redis
*
* @param amount the amount to add to the counter
* @return the updated value of the counter from redis repository
*/
public long addAndGet(long amount)
{ {
long newCount = _redisRepository.incrementCount(amount); long newCount = _redisRepository.incrementCount(amount);
updateCount(newCount); updateCount(newCount);
return newCount;
} }
/**
* Get the latest count of the counter. This will use the last cached value for the counter, or if
* the last cached count hasn't been updated in the {@link #_syncTime} period it will pull the latest
* count from the redis repository and use that value.
*
* @return The counter count
*/
public long getCount() public long getCount()
{ {
if (System.currentTimeMillis() - _lastUpdated > _syncTime)
{
updateCount(_redisRepository.getCount());
}
return _count.get(); return _count.get();
} }
public long getGoal() /**
* Reset the counter back to 0. This will take a maximum time of {@link #_syncTime} to propagate
* across all instances of this counter. Immediately updates the redis repository.
*
* @return The value of the counter before it was reset
*/
public long reset()
{ {
return _goal; updateCount(0);
} return _redisRepository.reset();
public double getFillPercent()
{
return Math.min(1, (((double) getCount()) / _goal));
} }
/**
* Get the data key for this counter. The data key is used as the redis repository key
* @return The data key for this counter
*/
public String getDataKey() public String getDataKey()
{ {
return _dataKey; return _dataKey;
} }
public long getCacheLastUpdated() /**
* Update the cached count with a new value
* @param newCount updated count
*/
protected void updateCount(long newCount)
{ {
return _cacheLastUpdated; _count.set(newCount);
_lastUpdated = System.currentTimeMillis();
} }
} }

View File

@ -42,10 +42,10 @@ public class CounterRedisRepository extends RedisRepository
} }
/** /**
* Increment the current fountain count by {@code increment} and then return the latest * Increment the current count by {@code increment} and then return the latest
* count of the fountain. This is handled in an atomic process through redis * count from redis. This is handled in an atomic process
* @param increment Amount to increment the fountain by * @param increment Amount to increment counter by
* @return The new count for the fountain * @return The updated count from redis
*/ */
public long incrementCount(long increment) public long incrementCount(long increment)
{ {
@ -59,6 +59,26 @@ public class CounterRedisRepository extends RedisRepository
return count; return count;
} }
/**
* Reset the counter back to 0
* @return the value of the counter before it was reset
*/
public long reset()
{
long count = -1;
try (Jedis jedis = getResource(true))
{
count = Long.parseLong(jedis.getSet(getKey(), "0"));
}
return count;
}
/**
* Get the key for this counter
* @return The key is used to store the value in redis
*/
private String getKey() private String getKey()
{ {
return getKey(_dataKey); return getKey(_dataKey);

View File

@ -0,0 +1,101 @@
package mineplex.serverdata.redis.counter;
import java.util.ArrayList;
import java.util.List;
/**
* A redis counter that is aiming to reach a goal
*
* @author Shaun Bennett
*/
public class GoalCounter extends Counter
{
// Has the goal been completed?
private boolean _completed;
// The goal we are trying to reach
private long _goal;
private List<GoalCounterListener> _listeners;
public GoalCounter(String dataKey, long syncTime, long goal)
{
super(dataKey, syncTime);
_completed = false;
_goal = goal;
_listeners = new ArrayList<>();
update();
}
/**
* Get the progress towards the goal as a percent ranging from 0 to 1.
* @return the percent progress towards goal
*/
public double getFillPercent()
{
return Math.min(1, (((double) getCount()) / _goal));
}
/**
* Has the goal been completed?
* @return
*/
public boolean isCompleted()
{
return _completed;
}
/**
* Get the goal for this GoalCounter
* @return the goal of this counter
*/
public long getGoal()
{
return _goal;
}
/**
* Add a listener to this GoalCounter
* @param listener the listener to be added
*/
public void addListener(GoalCounterListener listener)
{
_listeners.add(listener);
}
/**
* Clear all the listeners
*/
public void clearListeners()
{
_listeners.clear();
}
/**
* Update {@link #_completed} and notify listeners if it has completed
*/
private void update()
{
boolean updatedValue = getCount() > _goal;
if (updatedValue != _completed)
{
_completed = updatedValue;
_listeners.forEach(listener -> {
if (_completed) listener.onGoalComplete(this);
else listener.onGoalReset(this);
});
}
}
@Override
protected void updateCount(long newCount)
{
super.updateCount(newCount);
update();
}
}

View File

@ -0,0 +1,11 @@
package mineplex.serverdata.redis.counter;
/**
* @author Shaun Bennett
*/
public interface GoalCounterListener
{
public void onGoalComplete(GoalCounter counter);
public void onGoalReset(GoalCounter counter);
}