排名系统学习
排名榜用于游戏中玩家间的排名和对比,从战力、等级、装备评分、公会、副本通关层数等很多其他模块的数据可以来用进行排行。为了减少代码重复和工作量,所以一个通用排行系统是必须。
排行的抽象
Rank
是对所有排名名词的抽象,每个进行排名的角色都拥有Rank
public class Rank {
// 注意,第一名为零
public static final NO1 = 0;
public static final Rank NOT_IN_RANK = new Rank(false, -1);
// 是否上榜
private final boolean inRank;
private final int rank;
private Rank(boolean inRank, int rank) {
this.inRank = inRank;
this.rank = rank;
}
public Rank(int rank) {
this(true, rank);
}
// 在榜上玩家处理
public void ifInRank(Consumer<Integer> consumer) {
if(this.isInRank()) {
consumer.accept(rank);
}
}
// 判断排名是否在排行榜上
public boolean isInRank() {
return inRank;
}
// 获取排名值
public int getRank() {
return rank;
}
}
单个排行榜的信息,多个Rank
可以组成RankList
,而RankList
包含各种类型的排名,并且会持久化的
@Entity
@Cached(type=CacheType.MANUAL)
@InitailConfig(type=InitialType.ALL)
public class RankList extends AbstractEntity<RankPk> {
// 排名前n名变化时,才通知,包括调出前3名的
private transient static final int NOTIFY_RANK = 3;
@Id
private RankPk key;
@Lob
private String rankJson;
private transient List<RankEntity> rankList;
private transient Map<Long, RankEntity> rankMap;
private transient ReentrantWriteLock lock = new ReentrantReadWriteLock();
// 跨服结算
private long lastSettleTime;
// 跨服同步缓存
private transient AtomicBoolean sync = new AtomiBoolean(false);
// 记录有改变的排名实体,用于跨服增量同步
private transient RankUpdateSet changeSet;
// true为内存排行,不用入库
private transient boolean memory;
public static RankList valueOf(RankPk rankPk, boolean memory) {
RankList vo = new RankList();
vo.kwy = rankPk;
//+1是为了排行榜满时,新增上榜数据,在逻辑运算时不用扩容
vo.rankList = new ArrayList<>(rankPk.getRankType().getMaxRankSize()+1);
vo.rankMap. = new ConcurrentHashMap<>(rankPk.getRankType().getMAxRankSize()+1);
if(rankPk.getRankType().isCross()) {
vo.changeSet = new RankUpdateSet(vo.key);
}
vo.memory = memory;
return vo;
}
public static RankList valueOf(RankPk rankPk) {
return valueOf(rankPk, false);
}
public void directInsertRank(RankEntity rankEntity) {
if(rankMap.put(rankEntity.getUuid(), rankEntity) == null) {
this.rankList.add(rankEntity.getRank(), rankEntiy);
}
}
public RankEntity removeFromRank(int rank) {
RankEntity remove = this.rankList.remove(rank);
this.rankMap.remove(remove.getUuid());
return remove;
}
private void updateRecord(RankEntity rankEntity) {
if(getId().getRankType().isCross() && ServerType.isCenterServer()) {
this.changeSet.updateRank(rankEntity.getUuid());
}
}
@Ovveride
public RankPk getId() {
return key;
}
@Override
protected void serialize() {
rankList.forEach(RankEntity::serialize);
rankJson = rankJson.object2String(rankList);
}
@Override
public void unSerialize() {
}
public boolean isNo1(long uuid) {
RankEntity rankEntity = rankMap.get(uuid);
if(rankEntity == null) {
return false;
}
return rankEntity.isNo1();
}
public Rank getRank(long uuid) {
RankList rankEntity = rankMap.get(uuid);
if(rankEntity == null) {
return Rank.NOT_IN_RANK;
} else {
return new Rank(rankEntity.getRank());
}
}
public RankEntity getRankEntity(long uuid) {
return rankMap.get(uuid);
}
// 清空排行榜
public void clear() {
lock.writeLock.lock();
try {
rankMap.clear();
rankList.clear();
} finally {
lock.writeLock().unlock();
}
RankManager.self().update(this);
}
// 清空排行榜,但不更新
public void clear() {
lock.writeLock.lock();
try {
rankMap.clear();
rankList.clear();
} finally {
lock.writeLock().unlock();
}
}
// 添加数据,不做排序处理
public void addRankEntity(RankEntity rankEntyry) {
if(rankMap.put(rankEntity.getUuid(), rankEntity) == null) {
rankList.add(rankEntity);
}
}
// 清除过多的数据
public void removevOverfull(Predicate<RankEntity> predicate) {
// 可移除玩家数 = 榜单玩家数量-榜单可显示最大值
int count = rankList.size() - key.getRankType().getLimit();
if(count <= 0) {
return;
}
lock.writeLock().lock();
try {
for(int i=0; i < rankList.size() && c<count; i++) {
rankEntity = rankList.get(i);
if(predicae.test(rankEntity)) {
removeWithIndex(i);
c++;
}
}
// 重新设置排名
for(int i=0; i < rankList.size(); i++) {
setNewRank(rankList.get(i),i);
}
RankManager.self().update(this);
} finally {
lock.writeLock().unlock();
}
}
private void setNewRank(RankEntity rankEntity, int newRank) {
int oldRank = rankEntity.getRank();
if(newRank == oldRank) {
return;
}
rankEntity.getRank(newRank);
rankEntity.setUpdateTime(System.currentTimeMills());
if(newRank < NOTIFY_RANK || oldRank < NOTIFY_RANK>) {
try {
this.key.getRankType().getHandler().onChange(rankEntity.getUuid(), oldRank, rankEntity.getRank());
} catch(Exception e) {
logger.error("rank change", e);
}
}
}
// 移出数据,不做排序处理
public void removeWithIndex(int index) {
RankEntity rankEntity = rankList.remove(index);
if(rankEntity != null) {
rankMap.remove(rankEntity.getUuid());
}
RankManger.self().update(this);
}
// 重置排行顺序
public void sortWithRank() {
lock.writeLock().lock();
try {
Collections.sort(rankList);
for(int i=0; i< rankList.size(); i++) {
rankList.get(i).sentRank(i);
}
} finally {
lock.writeLock().unlock();
}
// 更新排行榜数据
public RankEntity update(long uuid, long newValue, Consumer<RankEntity> extraUpdater) {
lock.writeLock().lock();
try {
RankEntity rankEntity = rankMap.get(uuid);
if(rankEntity == null) {
// 未上榜的单位
if(!canIntoRank(newValue)) {
return null;
}
rankEntity = new RankEntity(key.getRankType(), uuid, rnkList.size(), newValue);
// 上榜更新
addRankEntity(rankEntity);
update(rankEntity, RankWave.UP, extraUpdater, true);
// 记录新增的排名
updateRecord(rankEntity);
} else {
// 已经在榜上的单位
long oldValue = rankEntity.getValue();
if(!key.getRankType().needUpdateWhenChange(oldValue,newValue)) {
// 排行值不需要进行变化
return null;
}
rankEntity.setValue(newValue);
update(rankEntity, key.getRankType().comprator(oldValue, newValue), extraUpdater, false);
// 记录新增的排名
updateRecord(rankEntity);
}
} finally {
lock.writeLock().unlock();
}
}
// 判断是否满足进入排行榜条件
public boolean canIntoRank(long newValue) {
return !(isFull() && key.getRankType().comparator(getLastValue(), newVlaue) != RankWave.UP);
}
// 更新排行榜
private void update(RankEntuty updateEntity, RankWave wave, Consumer<RankEntity> extraUpdater, boolean first) {
if(wave == RankWave.NONE) {
return;
}
int mark = wave.getMark();
int myOldRank = updateEntity.getRank();
for(int i=updateEntiy.getRank()+ mark; i<rankList.size() && i>=0; i +=mark) {
RankEntity exist = rankList.get(i);
if(key.getRankType().comparator(exist.getValue(), updateEntity.getValue()) != wave) {
break; // 找到合适的位置了
}
// 交换位置
rankList.set(i - mark, exist);
setNewRank(exist, i-mark);
rankList.set(i, updateEntiy);
updateEntity.setRank(i);
updateEntiy.setRank(i);
}
// 更新额外信息
if(extraUpdater != null) {
extraUpdater.accept(updateEntiy);
}
if(rankList.size() > key.getRankType().getMaxRankSize) {
removeWithIndex(rankList.size() -1);
}
boolean isRankChange = myOldRank != updateEntity.getRank();
if(isRankChange) {
// 名次有变更在记录更新时间
updateEntity.setUpdateTime(System.currentTimeMills());
}
boolean isChange = first || (isRankChange && (myOldRank < NOTIFY_RANK || updateEntity.getRank() < NOTIFY_RANK ));
if(isChange) {
this.key.getRankType().getHandler.onChange(updateEntity.getUuid(), myOldRank, updateEntity.getRank());
}
}
// 排行榜最低
public long getLastValue() {
lock.readLock.lock();
try {
if(rankList.isEmpty()) {
return 0;
}
return rankList.get(rankList.size()-1).getValue();
} finally {
lock.readLock().unlock();
}
}
// 排行榜是否已满
public boolean isFull() {
lock.readLock().lock();
try {
return rankList.size() >= key.getRankType().getMaxRankSize();
} finally {
lock.readLock().unlock();
}
}
// 获取分页数据,例如获取前20名(0,20)
public List<RankEntity> page(int start, int end) {
// ...
}
public List<RankEntity> getAll() {
// ...
}
// 获取榜单第一
public RankEntity getFirstRankEntity() {
if(rankList.isEmpty()) {
return null;
}
return rankList.get(0);
}
// 移出数据,更新排名
public void removeWithUuidAndSort(long uuid) {
}
// 重置排名
public void resetRank(int rank) {
for(int i=rank; i< this.rankList.size(); i++) {
this.rankList.get(i).setRank(i);
}
}
public void replaceRankList(List<RankEntity> rankList) {
lock.writeLock().lock();
try {
this.rankList = rankList;
this.rankMap.clear();
for(RankEntity rankEntiy : this.rankList)) {
this.ranjMap.puifAbsent(rankEntity.getUuid(), rankEntity);
}
} finally {
lock.writeLock().unlock();
}
}
public boolean isMemory() {
return memory;
}
}
排行榜数据波动方式
public enum RankWave {
// 排名上升
UP(-1),
// 排名下降
DOWN(1),
// 不变
NONE(0),
private int mark;
RankWave(int mark) {
this.mark = mark;
}
public int getMark() {
return mark;
}
}
排行榜数据库联合主键
@Embeddable
public class RankPk implements Serializable, Comparable<RankPk> {
@Enumerated(EnumType.STRING)
@Column(columnDefinition = "varchar(32) NOT NULL")
private RankType rankType;
private long subKey;
public RankPk() {
}
public static RankPk valueOf(RankType rankType, long subKey) {
RankRk vo = new RankPk();
vo.subKey = subkey;
vo.rankType = rankType;
return vo;
}
// 各种get/set ......
@Override
public int compareTo(RankPk p) {
if(rankType != o.rankType()) {
return Integer.compare(rankType.oridianl(), o.rankType.ordinal());
}
return Long.compare(subKey,o.subKey);
}
@Override
public boolean equals() {
if(this== o) {
return true;
}
if(o ==null || getClass() != o.getClass()) {
return false;
}
RankRk ranRk = (RankPk) o;
return rankType == rankPk.rankType &&
Objects.equals(subKey, rankPk.subKey);
}
// hashCode() 与 toString() 方法 .......
排行类型
排行榜类型在枚举里定义,然后也要在RankResource.xlsx里配置相同的排行类型。
@Desc("排行榜类型")
public enum RankType implements EnumReadable,ValueConverter, EnumValue {
@Desc("战力排行")
FIGHT_FORCE(1) {
@Override
public int getMaxRankSize() {
// 比排行榜可现实的最大数大0.2,为了在移除流失玩家时做候补
return (int) (this.getLimit() * 1.2);
}
}
@Desc("等级排行")
LEVEL(2) {
@Override
public int getMaxRankSize() {
// 比排行榜可显示的最大数大0.2,为了在移除流失玩家时做候补
return (int) (this.getLimit() * 1.2);
}
}
// ......
@Desc("跨服争霸全服模式个人英雄塔排行")
TRANSFER_HERO_TOWER_SER_PER(34,ServerGroupType.godKillTowerServer),
@Desc("飞升等级榜")
FLY_LEVEL(38),
RankType(int value) {
this(value,null);
}
RankType(int value, ServerGroupType serverGroupType) {
this.value = value;
this.serverGroupType = serverGroupType;
}
// 排行榜可显示的最大数
public int getLimit() {
return RankManger.self().getRankResource(this).getRankAmount();
}
// 服务器排行的最大数
public int getMaxRankSize() {
return this.getLimit();
}
// 比较排行榜值的大小,如果有不同的排序方式,可以覆盖该方法
public RankWave comparator(long oldValue, long newValue) {
return (oldValue < newValue) ? RankWave.UP : ((oldValue == newValue) ? rankWave.NONE:RankWave.DOWN);
}
// 当排行榜内的单位,在数据变化后,检测是否需要更新值,更新后会导致排行变动
public boolean needUpdateWhenChange(long oldValue, long newValue) {
return oldValue != newValue;
}
@Override
public int value() {
return value;
}
// 获取排行榜处理器
public BaseRankHandler getHandler() {
return BaseRankHandler.get(this);
}
public boolean isCross() {
return serverGroupType != null;
}
public ServerGroupType getServerGroupType() {
return serverGroupType;
}
}
排行配置
@Resouce
public class RankResource {
@ResourceId
public RankType rankType;
// 排行榜上榜最大人数
private ubr rankAmount;
// 客户端显示最大数量。因为发奖原因,可能服务端需要保存的排行榜比客户端需要显示的数量大
private int showAmount;
// 达到排行榜第一的通告
private MessageCode rankUpNumberOne;
// 排行榜第一登陆的通告
private MessageCode numberOneLoginOn;
}
排行处理器
角色的游戏数据变化会影响排行榜的变化,所以需要对各种排行类型定义对应的处理器
排行榜处理接口
public abstract class BaseRankHandler {
// 所有排行榜类型对应处理器接口
private static EnumMap<RankType, BaseRankHandler> handlerMap = new EnumMap<>(RankType.class);
public static BaseRankHandler get(RankType type) {
return handlerMap.get(type);
}
public static Collection<BaseRankHandler> getHandlers() {
return handlerMap.values();
}
@PostConstruct
public void init() {
RankTpye type = type();
if(handlerMap.constaisKey(type)) {
throw new RuntimeException(String.format("RankHandler type[%s]重复!",type));
}
handlerMap.put(type, this);
}
public RankList getRanKList() {
return RankManager.self().getRankList(type());
}
public RankList getRankList(long subKey) {
return RankManager.self().getRankList(type(), subKey);
}
public Rank getRank(long uuid) {
return getRankList().getRank(uuid);
}
// 更新排行信息
protected void updateRank(long subKey, long uuid, long newValue, Consumer<RankEntiy> extraUpdater) {
getRankList(subKey).update(uuid,newValue, extraUdater);
}
// 每一个排行榜类型,都应该有一个`BaseRankHandler`
public abstract RankType type() ;
// 获取该排行榜中,与玩家关联的排名实体。以玩家为主体的排行就是玩家本身的排行实体
// 不以玩家为主体的排行,例如公会排名名,就是玩家所属工会排名
public abstract RankEntity getRankEntity(long uuid);
// 排行主键
public abstract RankEntity getRankEntity(long uuid);
// 游戏服发送排行榜信息
public void sendRankList(long playerId, int start, int terminal) {
}
public <T> T getRankListPacket(long playerId, int start, int terminal) {
return null;
}
// 该排名奖励发给哪些玩家
public List<Long> getRewardPlayerIds(long uuid) {
return Collection.emptyList()
}
// 清空排行榜
public void clear() {
RankManager.self().walkRankList(type(), RankList::clear());
}
// 名次变化,只有前几名排行变化才触发
public void onChange(long uuid, int oldRank, int newRank) {
}
// 给排行榜内发奖
public void sendRankReward(long uuid, int rank) {}
}
实现了BaseRankHandler
接口的处理器
玩家等级排行榜处理器
@Component
public class PlayerLevelRankHandler extends BasePlayerRankHandler {
@Overide
public RankType type() {
retrun RankType.LEVEL;
}
@Override
public long getRankValue(Player player) {
return palyer.getLevel();
}
// 战力排行
@ReceiverAnno
public void updateRank(Player player) {
// 玩家等级不会降低,不做降低移出排行榜操作,如果前提不成立,需要支持降级导致的出榜逻辑
if(getRankValue(player) >= RankContants.getInstance().getEnterLevelRankListValue()) {
super.updateRank(player);
}
}
}
总结
添加一个新的排行榜,需要在RankType
添加一个新的枚举,在RankResource.xlsx
添加对应的排行类型。实现BasePlayerRankHandler
为排行榜指定处理器,描述如果获取排行值、如果处理排行的更新。