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 onInit() => await Task.FromResult(new Result()); public override void onClear() {} public async Task 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().ToList(); var tasks = new List(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 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 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 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(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(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 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 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 copyTotalToOrganization(UgcNpcRankType type) { var server_logic = GameServerApp.getServerLogic(); var dynamoDb_client = server_logic.getDynamoDbClient(); var delete_rank_keys = new List(); 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(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(query_config); if (result.isFail()) return result; NullReferenceCheckHelper.throwIfNull(total_doc, () => $"total_doc is null !!! - {getOwner()}"); var total_attrib = total_doc.getAttrib(); 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(); NullReferenceCheckHelper.throwIfNull(total_rank_attrib, () => $"total_rank_attrib is null !!! - {getOwner()}"); var ranks = new Dictionary(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(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(total_doc); return result; } private async Task 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(rank_doc); return result; } private async Task 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 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(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(); 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 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 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(); NullReferenceCheckHelper.throwIfNull(like_action, () => $"like_action is null !!!"); await like_action.loadRank(state); break; case UgcNpcRankType.Quest: var quest_action = entity.getEntityAction(); NullReferenceCheckHelper.throwIfNull(quest_action, () => $"quest_action is null !!!"); await quest_action.loadRank(state); break; case UgcNpcRankType.Communication: var communication_action = entity.getEntityAction(); NullReferenceCheckHelper.throwIfNull(communication_action, () => $"communication_action is null !!!"); await communication_action.loadRank(state); break; } } private static Dictionary sortRanks(Dictionary ranks, int length) { var list = new Dictionary(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(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"); } }