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.stats.StatsManager;
import mineplex.serverdata.redis.counter.Counter;
import mineplex.serverdata.redis.counter.GoalCounter;
import org.bukkit.Location;
import org.bukkit.entity.Player;
@ -25,7 +26,7 @@ public class Fountain
private final Location _location;
private Location _lavaLocation;
private final Hologram _hologram;
private final Counter _counter;
private final GoalCounter _counter;
private final FountainShop _shop;
@ -41,7 +42,7 @@ public class Fountain
_location = location;
_lavaLocation = lavaLocation;
_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);
@ -56,19 +57,13 @@ public class Fountain
protected void updateCount()
{
// Make sure we only update if it has been 5 seconds since the last update
// Players incrementing the counter will automatically cause it to update
if (System.currentTimeMillis() - _counter.getCacheLastUpdated() > 5000)
{
_counter.updateCount();
updateHologram();
}
updateHologram();
}
public void increment(Player player, long amount)
{
_statsManager.incrementStat(player, "Global.Fountain." + getDataKey(), amount);
_counter.increment(amount);
_counter.addAndGet(amount);
updateHologram();
}

View File

@ -3,73 +3,100 @@ package mineplex.serverdata.redis.counter;
import java.util.concurrent.atomic.AtomicLong;
/**
* A counter represents an atomic number that is trying to hit a goal. Once the counter
* reaches it's goal you can either reset it or let it stay completed for an indefinite time.
* A counter represents an incrementing atomic number that is stored and updated through redis. This allows for
* multiple servers to share and update the same counter
*
* @author Shaun Bennett
*/
public class Counter
{
// Cached count for the fountain
private AtomicLong _count = new AtomicLong(0);
// Cached count of the counter
private final AtomicLong _count = new AtomicLong(0);
// The System.currentTimeMillis() when cached count was last updated
private long _cacheLastUpdated;
// The goal that this fountain is trying to reach
private final long _goal;
private volatile long _lastUpdated;
// The maximum time to wait before syncing the count with repository
private final long _syncTime;
// The unique key to reference this counter
private final String _dataKey;
// 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;
_syncTime = syncTime;
_redisRepository = new CounterRedisRepository(dataKey);
updateCount();
}
public void updateCount()
{
updateCount(_redisRepository.getCount());
}
protected void updateCount(long newCount)
public Counter(String dataKey)
{
_count.set(newCount);
_cacheLastUpdated = System.currentTimeMillis();
this(dataKey, 5000); // 5 seconds
}
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);
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()
{
if (System.currentTimeMillis() - _lastUpdated > _syncTime)
{
updateCount(_redisRepository.getCount());
}
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;
}
public double getFillPercent()
{
return Math.min(1, (((double) getCount()) / _goal));
updateCount(0);
return _redisRepository.reset();
}
/**
* 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()
{
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
* count of the fountain. This is handled in an atomic process through redis
* @param increment Amount to increment the fountain by
* @return The new count for the fountain
* Increment the current count by {@code increment} and then return the latest
* count from redis. This is handled in an atomic process
* @param increment Amount to increment counter by
* @return The updated count from redis
*/
public long incrementCount(long increment)
{
@ -59,6 +59,26 @@ public class CounterRedisRepository extends RedisRepository
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()
{
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);
}