433 lines
17 KiB
C#
433 lines
17 KiB
C#
using Google.Protobuf;
|
|
using Google.Protobuf.WellKnownTypes;
|
|
|
|
|
|
using ServerCore;
|
|
using ServerBase;
|
|
using ServerCommon;
|
|
using ServerCommon.BusinessLogDomain;
|
|
using MetaAssets;
|
|
|
|
|
|
namespace GameServer;
|
|
|
|
public class UgcNpcRankManageAction : EntityActionBase
|
|
{
|
|
private UgcNpcRankManageCacheRequest m_rank_manage_cache_request { get; set; }
|
|
|
|
public UgcNpcRankManageAction(UgcNpcRankEntity owner) : base(owner)
|
|
{
|
|
// DailyTimeEventManager 등록
|
|
var date = MetaHelper.GameConfigMeta.NpcRankingCalculateTime;
|
|
DailyTimeEventManager.Instance.tryAddTask("UgcNpcRankManage", date, onOrganizationUgcNpcRanking);
|
|
|
|
var businesslog_refresh_date = MetaHelper.GameConfigMeta.BusinessLogRefreshTime;
|
|
DailyTimeEventManager.Instance.tryAddTask("BusinessLogRefresh", businesslog_refresh_date, onBusunessLogRefresh);
|
|
|
|
m_rank_manage_cache_request =
|
|
new UgcNpcRankManageCacheRequest(GameServerApp.getServerLogic().getRedisConnector());
|
|
}
|
|
|
|
public override async Task<Result> onInit() => await Task.FromResult(new Result());
|
|
public override void onClear() {}
|
|
|
|
public async Task<Result> initRank()
|
|
{
|
|
// 1. 기본 정보 체크
|
|
var result = await checkBaseUgcNpcRank();
|
|
if (result.isFail()) return result;
|
|
|
|
// 2. Ranking 데이터 로딩
|
|
await reloadAllUgcNpcRanks();
|
|
|
|
return result;
|
|
}
|
|
|
|
public async Task reloadAllUgcNpcRanks()
|
|
{
|
|
try
|
|
{
|
|
var ranking_types = System.Enum.GetValues(typeof(UgcNpcRankType)).Cast<UgcNpcRankType>().ToList();
|
|
var tasks = new List<Task>(ranking_types.Count * 2);
|
|
|
|
foreach (var type in ranking_types)
|
|
{
|
|
tasks.Add(getUgcNpcRankingData(type, UgcNpcRankState.Total));
|
|
tasks.Add(getUgcNpcRankingData(type, UgcNpcRankState.Trend));
|
|
}
|
|
|
|
await Task.WhenAll(tasks);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Log.getLogger().error($"fail to reload ugc npc rank : {e}");
|
|
}
|
|
}
|
|
|
|
public async Task<Result> notifyClientUgcNpcRankRefresh(bool includeAnotherServer)
|
|
{
|
|
var result = new Result();
|
|
|
|
// 1. Client 전송
|
|
var client_message = new ClientToGame();
|
|
client_message.Message = new ClientToGameMessage();
|
|
client_message.Message.NtfUgcNpcRankRefresh = new();
|
|
|
|
result = await UgcNpcRankHelper.BroadcastToAllClients(client_message);
|
|
|
|
// 2. Server Noti
|
|
if (!includeAnotherServer) return result;
|
|
|
|
var server_message = new ServerMessage();
|
|
server_message.NtfUgcNpcRankRefresh = new();
|
|
|
|
result = await UgcNpcRankHelper.BroadcastToAllServers(server_message, true);
|
|
|
|
return await Task.FromResult(result);
|
|
}
|
|
|
|
private async Task<Result> checkBaseUgcNpcRank()
|
|
{
|
|
var result = new Result();
|
|
|
|
// 1. total / like
|
|
result = await checkBaseUgcNpcRank(UgcNpcRankState.Total, UgcNpcRankType.Like);
|
|
if (result.isFail()) return result;
|
|
|
|
// 2. total / quest
|
|
result = await checkBaseUgcNpcRank(UgcNpcRankState.Total, UgcNpcRankType.Quest);
|
|
if (result.isFail()) return result;
|
|
|
|
// 3. trend / like
|
|
result = await checkBaseUgcNpcRank(UgcNpcRankState.Trend, UgcNpcRankType.Like);
|
|
if (result.isFail()) return result;
|
|
|
|
// 4. trend / quest
|
|
result = await checkBaseUgcNpcRank(UgcNpcRankState.Trend, UgcNpcRankType.Quest);
|
|
|
|
return result;
|
|
}
|
|
|
|
private async Task<Result> checkBaseUgcNpcRank(UgcNpcRankState state, UgcNpcRankType type)
|
|
{
|
|
var server_logic = GameServerApp.getServerLogic();
|
|
var dynamoDb_client = server_logic.getDynamoDbClient();
|
|
|
|
var organization_time = UgcNpcRankHelper.makeCurrentOrganizationDate();
|
|
var rank_date = organization_time.ToString("yyyy-MM-dd");
|
|
|
|
var primary_key = $"{UgcNpcRankDoc.getPrefixOfPK()}{state.ToString()}";
|
|
var second_key = state == UgcNpcRankState.Trend ? $"{type.ToString()}#{rank_date}" : type.ToString();
|
|
|
|
// 1. data 조회
|
|
var query_config = dynamoDb_client.makeQueryConfigForReadByPKSK(primary_key, second_key);
|
|
var (result, rank_doc) = await dynamoDb_client.simpleQueryDocTypeWithQueryOperationConfig<UgcNpcRankDoc>(query_config);
|
|
if (result.isSuccess() && null != rank_doc) return result;
|
|
|
|
// 2. 빈 데이터 생성
|
|
rank_doc = state == UgcNpcRankState.Trend
|
|
? new UgcNpcRankDoc(state, type, rank_date, UgcNpcRankHelper.getUgcNpcRankDocTtlSeconds())
|
|
: new UgcNpcRankDoc(state, type, null);
|
|
|
|
result = await dynamoDb_client.simpleInsertDocumentWithDocType<UgcNpcRankDoc>(rank_doc);
|
|
|
|
return result;
|
|
}
|
|
|
|
private async Task onOrganizationUgcNpcRanking()
|
|
{
|
|
var result = new Result();
|
|
|
|
// 1. 시작 설정
|
|
var is_set_start = await m_rank_manage_cache_request.setStartUgcNpcRankManage();
|
|
if (!is_set_start) return;
|
|
|
|
// 2. 시작 조건 체크
|
|
var is_start = await checkStartCondition();
|
|
if (false == is_start) return;
|
|
|
|
// 3. total rank 데이터 처리
|
|
result = await copyToOrganization();
|
|
if (result.isFail()) return;
|
|
|
|
// 4. rank 데이터 등록
|
|
result = await registrationOrganizationRank();
|
|
if (result.isFail())
|
|
{
|
|
var err_msg = $"fail to organize ugc npc rank!! [{result.toBasicString()}]";
|
|
Log.getLogger().error(err_msg);
|
|
}
|
|
|
|
// 5. reload data
|
|
await reloadAllUgcNpcRanks();
|
|
|
|
// 6. Client 노티
|
|
_ = await notifyClientUgcNpcRankRefresh(true);
|
|
}
|
|
|
|
private async Task<bool> checkStartCondition()
|
|
{
|
|
var key = "";
|
|
var rank_date = UgcNpcRankHelper.getCurrentRankDate();
|
|
var sub_key = rank_date.ToString("yyyy-MM-dd");
|
|
|
|
// 1. total key 존재 여부 체크
|
|
key = $"total:{UgcNpcRankType.Like.ToString()}:{sub_key}";
|
|
var is_exist = await m_rank_manage_cache_request.isExistKey(key);
|
|
if (is_exist == false) return true;
|
|
|
|
key = $"total:{UgcNpcRankType.Quest.ToString()}:{sub_key}";
|
|
is_exist = await m_rank_manage_cache_request.isExistKey(key);
|
|
if (is_exist == false) return true;
|
|
|
|
// 2. trend key 존재 여부 체크
|
|
key = $"trend:{UgcNpcRankType.Like.ToString()}:{sub_key}";
|
|
is_exist = await m_rank_manage_cache_request.isExistKey(key);
|
|
if (is_exist == false) return true;
|
|
|
|
key = $"trend:{UgcNpcRankType.Quest.ToString()}:{sub_key}";
|
|
is_exist = await m_rank_manage_cache_request.isExistKey(key);
|
|
if (is_exist == false) return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
private async Task<Result> copyToOrganization()
|
|
{
|
|
// 1. total like 정보 복사
|
|
var result = await copyTotalToOrganization(UgcNpcRankType.Like);
|
|
if (result.isFail()) return result;
|
|
|
|
// 2. total Quest 정보 복사
|
|
result = await copyTotalToOrganization(UgcNpcRankType.Quest);
|
|
if (result.isFail()) return result;
|
|
|
|
// 3. trend like doc 생성
|
|
result = await createTodayTrendRankDoc(UgcNpcRankType.Like);
|
|
if (result.isFail()) return result;
|
|
|
|
// 4. trend quest doc 생성
|
|
result = await createTodayTrendRankDoc(UgcNpcRankType.Quest);
|
|
if (result.isFail()) return result;
|
|
|
|
return result;
|
|
}
|
|
|
|
private async Task<Result> copyTotalToOrganization(UgcNpcRankType type)
|
|
{
|
|
var server_logic = GameServerApp.getServerLogic();
|
|
var dynamoDb_client = server_logic.getDynamoDbClient();
|
|
|
|
var delete_rank_keys = new List<string>();
|
|
var rank_date = UgcNpcRankHelper.getCurrentRankDate().ToString("yyyy-MM-dd");
|
|
|
|
// 1. 기존 정보가 있다면 ?? 패스
|
|
var primary_key = $"{UgcNpcRankDoc.getPrefixOfPK()}{UgcNpcRankState.Total.ToString()}";
|
|
var second_key = $"{type.ToString()}#{rank_date}";
|
|
var query_config = dynamoDb_client.makeQueryConfigForReadByPKSK(primary_key, second_key);
|
|
var (result, origin_total_rank_doc) = await dynamoDb_client.simpleQueryDocTypeWithQueryOperationConfig<UgcNpcRankDoc>(query_config);
|
|
if (null != origin_total_rank_doc) return result;
|
|
|
|
// 2. Total 정보 조회
|
|
query_config = dynamoDb_client.makeQueryConfigForReadByPKSK(primary_key, type.ToString());
|
|
(result, var total_doc) = await dynamoDb_client.simpleQueryDocTypeWithQueryOperationConfig<UgcNpcRankDoc>(query_config);
|
|
if (result.isFail()) return result;
|
|
|
|
NullReferenceCheckHelper.throwIfNull(total_doc, () => $"total_doc is null !!! - {getOwner()}");
|
|
|
|
var total_attrib = total_doc.getAttrib<UgcNpcRankAttrib>();
|
|
NullReferenceCheckHelper.throwIfNull(total_attrib, () => $"total_attrib is null !!! - {getOwner()}");
|
|
|
|
// 3. 랭킹 정보 생성
|
|
var total_rank_doc = new UgcNpcRankDoc(UgcNpcRankState.Total, type, rank_date, UgcNpcRankHelper.getUgcNpcRankDocTtlSeconds());
|
|
var total_rank_attrib = total_rank_doc.getAttrib<UgcNpcRankAttrib>();
|
|
NullReferenceCheckHelper.throwIfNull(total_rank_attrib, () => $"total_rank_attrib is null !!! - {getOwner()}");
|
|
|
|
var ranks = new Dictionary<string, long>(total_attrib.ranks.Count);
|
|
foreach (var rank in total_attrib.ranks)
|
|
{
|
|
if (rank.Value <= 0)
|
|
{
|
|
delete_rank_keys.Add(rank.Key);
|
|
continue;
|
|
}
|
|
ranks.Add(rank.Key, rank.Value);
|
|
}
|
|
|
|
// 4. 데이터 정렬
|
|
total_rank_attrib.ranks = sortRanks(ranks, UgcNpcRankEntity.DefaultPageSize);
|
|
|
|
result = await dynamoDb_client.simpleInsertDocumentWithDocType<UgcNpcRankDoc>(total_rank_doc);
|
|
if (result.isFail()) return result;
|
|
|
|
// 5. score:0 랭킹 정보 정리
|
|
foreach (var delete_key in delete_rank_keys)
|
|
{
|
|
total_attrib.ranks.Remove(delete_key);
|
|
}
|
|
result = await dynamoDb_client.simpleUpdateDocumentWithDocType<UgcNpcRankDoc>(total_doc);
|
|
|
|
return result;
|
|
}
|
|
|
|
private async Task<Result> createTodayTrendRankDoc(UgcNpcRankType type)
|
|
{
|
|
var result = new Result();
|
|
|
|
var server_logic = GameServerApp.getServerLogic();
|
|
var dynamoDb_client = server_logic.getDynamoDbClient();
|
|
|
|
var rank_date = UgcNpcRankHelper.makeCurrentOrganizationDate().ToString("yyyy-MM-dd");
|
|
var rank_doc = new UgcNpcRankDoc(UgcNpcRankState.Trend, type, rank_date, UgcNpcRankHelper.getUgcNpcRankDocTtlSeconds());
|
|
|
|
result = await dynamoDb_client.simpleInsertDocumentWithDocType<UgcNpcRankDoc>(rank_doc);
|
|
return result;
|
|
}
|
|
|
|
private async Task<Result> registrationOrganizationRank()
|
|
{
|
|
var result = new Result();
|
|
|
|
// 1. total / like
|
|
result = await registrationOrganizationRank(UgcNpcRankState.Total, UgcNpcRankType.Like);
|
|
if (result.isFail()) return result;
|
|
|
|
// 2. total / quest
|
|
result = await registrationOrganizationRank(UgcNpcRankState.Total, UgcNpcRankType.Quest);
|
|
if (result.isFail()) return result;
|
|
|
|
// 3. trend / like
|
|
result = await registrationOrganizationRank(UgcNpcRankState.Trend, UgcNpcRankType.Like);
|
|
if (result.isFail()) return result;
|
|
|
|
// 4. trend / quest
|
|
result = await registrationOrganizationRank(UgcNpcRankState.Trend, UgcNpcRankType.Quest);
|
|
|
|
return result;
|
|
}
|
|
|
|
private async Task<Result> registrationOrganizationRank(UgcNpcRankState state, UgcNpcRankType type)
|
|
{
|
|
var server_logic = GameServerApp.getServerLogic();
|
|
var dynamoDb_client = server_logic.getDynamoDbClient();
|
|
|
|
var rank_date = UgcNpcRankHelper.getCurrentRankDate().ToString("yyyy-MM-dd");
|
|
|
|
var result = new Result();
|
|
|
|
// 1. dynamoDb Data 가져오기
|
|
var primary_key = $"{UgcNpcRankDoc.getPrefixOfPK()}{state.ToString()}";
|
|
var second_key = $"{type.ToString()}#{rank_date}";
|
|
var query_config = dynamoDb_client.makeQueryConfigForReadByPKSK(primary_key, second_key);
|
|
(result, var origin_rank_doc) = await dynamoDb_client.simpleQueryDocTypeWithQueryOperationConfig<UgcNpcRankDoc>(query_config);
|
|
if (result.isFail()) return result;
|
|
|
|
NullReferenceCheckHelper.throwIfNull(origin_rank_doc, () => $"origin_rank_doc is null !!! - {getOwner()}");
|
|
|
|
var rank_attrib = origin_rank_doc.getAttrib<UgcNpcRankAttrib>();
|
|
NullReferenceCheckHelper.throwIfNull(rank_attrib, () => $"rank_attrib is null !!! - {getOwner()}");
|
|
|
|
switch (state)
|
|
{
|
|
case UgcNpcRankState.Total:
|
|
result = await registrationOrganizationRankForTotalToRedis(rank_attrib, type);
|
|
break;
|
|
|
|
case UgcNpcRankState.Trend:
|
|
result = await registrationOrganizationRankForTrendToRedis(rank_attrib, type);
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private async Task<Result> registrationOrganizationRankForTotalToRedis(UgcNpcRankAttrib rankAttrib, UgcNpcRankType type)
|
|
{
|
|
var server_logic = GameServerApp.getServerLogic();
|
|
var redis_client = server_logic.getRedisConnector();
|
|
|
|
var rank_request = new UgcNpcTotalRankCacheRequest(type, UgcNpcRankHelper.getCurrentRankDate(), redis_client);
|
|
|
|
// 1. rank 설정
|
|
var result = await rank_request.initRankScore(rankAttrib.ranks);
|
|
if (result.isFail()) return result;
|
|
|
|
// 2. ttl 설정
|
|
result = await rank_request.setTtl();
|
|
return result;
|
|
}
|
|
|
|
private async Task<Result> registrationOrganizationRankForTrendToRedis(UgcNpcRankAttrib rankAttrib, UgcNpcRankType type)
|
|
{
|
|
var server_logic = GameServerApp.getServerLogic();
|
|
var redis_client = server_logic.getRedisConnector();
|
|
|
|
var rank_request = new UgcNpcTrendRankCacheRequest(type, UgcNpcRankHelper.getCurrentRankDate(), redis_client);
|
|
|
|
// 1. rank 설정
|
|
var result = await rank_request.initRankScore(rankAttrib.ranks);
|
|
if (result.isFail()) return result;
|
|
|
|
// 2. ttl 설정
|
|
result = await rank_request.setTtl();
|
|
return result;
|
|
}
|
|
|
|
private async Task getUgcNpcRankingData(UgcNpcRankType type, UgcNpcRankState state)
|
|
{
|
|
var entity = getOwner() as UgcNpcRankEntity;
|
|
NullReferenceCheckHelper.throwIfNull(entity, () => $"entity is null !!!");
|
|
|
|
switch (type)
|
|
{
|
|
case UgcNpcRankType.Like:
|
|
var like_action = entity.getEntityAction<UgcNpcLikeRankAction>();
|
|
NullReferenceCheckHelper.throwIfNull(like_action, () => $"like_action is null !!!");
|
|
|
|
await like_action.loadRank(state);
|
|
break;
|
|
|
|
case UgcNpcRankType.Quest:
|
|
var quest_action = entity.getEntityAction<UgcNpcQuestRankAction>();
|
|
NullReferenceCheckHelper.throwIfNull(quest_action, () => $"quest_action is null !!!");
|
|
|
|
await quest_action.loadRank(state);
|
|
break;
|
|
|
|
case UgcNpcRankType.Communication:
|
|
var communication_action = entity.getEntityAction<UgcNpcCommunicationRankAction>();
|
|
NullReferenceCheckHelper.throwIfNull(communication_action, () => $"communication_action is null !!!");
|
|
|
|
await communication_action.loadRank(state);
|
|
break;
|
|
}
|
|
}
|
|
|
|
private static Dictionary<string, long> sortRanks(Dictionary<string, long> ranks, int length)
|
|
{
|
|
var list = new Dictionary<string, long>(length);
|
|
var sorted_ranks = ranks.OrderByDescending(x => x.Value).ToDictionary(x => x.Key, x => x.Value);
|
|
|
|
var count = 0;
|
|
foreach (var rank in sorted_ranks)
|
|
{
|
|
if (count >= length) break;
|
|
list.Add(rank.Key, rank.Value);
|
|
count++;
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
private async Task onBusunessLogRefresh()
|
|
{
|
|
await Task.CompletedTask;
|
|
|
|
var log_invokers = new List<ILogInvoker>(1);
|
|
var empty_business_refresh_with_log_actor = new EmptyBusinessWithLogActor();
|
|
DailyRefreshBusinessLog log = new();
|
|
log_invokers.Add(log);
|
|
BusinessLogger.collectLogs(new LogActionEx(LogActionType.TestBusinessLog), empty_business_refresh_with_log_actor, log_invokers);
|
|
Log.getLogger().info("EmptyBusinessLog write");
|
|
}
|
|
} |