Counter refactoring
This commit is contained in:
parent
a0fd852537
commit
e8370579c0
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
Loading…
Reference in New Issue