using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Diagnostics; using Amazon.S3.Model; using Axion.Collections.Concurrent; using NeoSmart.AsyncLock; using Microsoft.Extensions.Logging; using ServerCore; using ServerBase; using ServerCommon; using MetaAssets; using META_ID = System.UInt32; using LAND_AUCTION_KEY = System.String; using REQUESTOR_ID = System.String; namespace GameServer { public class LandAuctionManager : Singleton { private AsyncLock m_reserved_lock = new(); // 랜드 경매가 활성화중인 목록 private ConcurrentDictionary m_activitings = new(); // 랜드 경매가 종료된 목록 private ConcurrentDictionary m_records = new(); public LandAuctionManager() { } public async Task tryActivitingLandAuctions( REQUESTOR_ID requestorId , List toAddActivitings , string callTid ) { var result = new Result(); var err_msg = string.Empty; Stopwatch? stopwatch = null; var event_tid = string.Empty; var server_logic = GameServerApp.getServerLogic(); var server_config = server_logic.getServerConfig(); using ( var releaser = await m_reserved_lock.LockAsync() ) { if(0 < toAddActivitings.Count) { // 1. 현재 활성화중인 랜드 경매 키를 읽어 온다. var activiting_keys = getActivitings().Keys.ToList(); // 2. 활성화 목록에 추가할 랜드 경매 키를 추출 한다. var to_add_reserved_keys = KeyComparer.getKeysOnlyInSecond(activiting_keys, toAddActivitings); // 3. 신규 랜드 경매를 활성화 목록에 추가 한다. foreach (var land_meta_id in to_add_reserved_keys) { result = await addActivitingLandAuctionByReservationKey(land_meta_id); if (result.isFail()) { err_msg = $"Failed to addActivitingLandAuctionByReservationKey() !!!, in onTaskTick() : {result.toBasicString()} - landMetaId:{land_meta_id}, {toBasicString()}"; Log.getLogger().error(err_msg); continue; } } } // 4. 활성화중인 모든 랜드 경매를 업데이트 한다. var activitings = getActivitings().Values.ToList(); foreach (var land_auction in activitings) { if (true == server_config.PerformanceCheckEnable) { event_tid = System.Guid.NewGuid().ToString("N"); stopwatch = Stopwatch.StartNew(); } var land_auction_action = land_auction.getEntityAction(); NullReferenceCheckHelper.throwIfNull(land_auction_action, () => $"land_auction_action is null !!!"); var fn_land_auction_check = async delegate () { var err_msg = $"LandAuctionCheck in LandAuctionCheckTicker.onTaskTick() !!! - TID:{callTid} - {land_auction.toBasicString()}"; Log.getLogger().debug(err_msg); var result = new Result(); var land_meta_id = land_auction_action.getLandMetaId(); result = await land_auction_action.tryCheckLandAuction(land_meta_id, requestorId, true); if (result.isFail()) { err_msg = $"Failed to tryCheckLandAuction() !!! : {result.toBasicString()} - {land_auction.toBasicString()}"; Log.getLogger().error(err_msg); return result; } err_msg = $"Successed LandAuctionAction.tryCheckLandAuctionByTicker() !!! - TID:{callTid} - {land_auction.toBasicString()}"; Log.getLogger().debug(err_msg); return result; }; result = await land_auction.runTransactionRunnerSafelyWithTransGuid( requestorId , TransactionIdType.PrivateContents, "LandAuctionCheck" , fn_land_auction_check); if (result.isFail()) { err_msg = $"Failed to runTransactionRunnerSafely()!!! : {result.toBasicString()} - {land_auction.toBasicString()}"; Log.getLogger().error(err_msg); } if (null != stopwatch) { var elapsed_msec = stopwatch.ElapsedMilliseconds; stopwatch.Stop(); if (1000 <= elapsed_msec) { Log.getLogger().debug( $"{this.getTypeName()} Performance alert !!! : Execution delayed !!!" + $" - ETID:{event_tid}, ElapsedMSec:{elapsed_msec}"); } } } } return result; } public async Task tryConfigureAndAddReservedLandAuctioKeyAll(REQUESTOR_ID requstorId) { var result = new Result(); var err_msg = string.Empty; var reserved_land_meta_ids = new HashSet(); (var auctionable_land_meta_ids, var calculated_ttl_sec) = getAuctionableLandMetaIdsWithTtlSec(); if(0 >= auctionable_land_meta_ids.Count) { return result; } // 1. 랜드 경매 예약 설정 WriteLock 권한을 획득 한다. var is_success = await LandAuctionCacheHelper.tryAcquireWriteLockWithLandAuctionReservation(requstorId, calculated_ttl_sec); if (true == is_success) { foreach (var land_meta_id in auctionable_land_meta_ids) { var is_continue = false; // 1.1. 랜드 경매의 예약 가능 여부를 체크하고 예약 설정 한다. (result, var is_to_add_activiting) = await LandAuctionReservationHelper.configureNextLandAuctionToDb(land_meta_id); if (result.isFail()) { err_msg = $"Failed to configureNextAuctionToDb() !!! : {result.toBasicString()} - landMetaId:{land_meta_id}"; Log.getLogger().error(err_msg); is_continue = true; } if (false == is_to_add_activiting) { is_continue = true; } if (true == is_continue) { continue; } // 랜드 메타 id를 예약 목록에 등록 한다. if (false == reserved_land_meta_ids.Add(land_meta_id)) { err_msg = $"Failed to Add() !!!, in tryConfigureAndAddReservedLandAuctionAll(), Already exist Reserved Key !!! : landMetaId:{land_meta_id}"; Log.getLogger().error(err_msg); continue; } err_msg = $"LandAuction ReservedKeys to add Key !!! : landMetaId:{land_meta_id}"; Log.getLogger().debug(err_msg); } await LandAuctionCacheHelper.tryReleaseWriteLockWithLandAuctionReservation(requstorId); if (0 < reserved_land_meta_ids.Count) { LandAuctionNotifyHelper.broadcast_GS2GS_NTF_LAND_AUCTION_RESERVATION(reserved_land_meta_ids.ToList()); } } return result; } private (List, short) getAuctionableLandMetaIdsWithTtlSec() { var auctionable_land_meta_ids = new HashSet(); var err_msg = string.Empty; foreach (var each in MetaData.Instance._LandTable) { var land_meta = each.Value; var land_meta_id = (META_ID)land_meta.LandId; if (EditorType.USER != land_meta.Editor) { continue; } var result = OwnedLandHelper.checkLandWithoutOwner((META_ID)land_meta_id); if (result.isFail()) { err_msg = $"Failed to checkLandWithoutOwner() !!! : {result.toBasicString()}, landMetaId:{land_meta_id}"; Log.getLogger().debug(err_msg); continue; } if(false == auctionable_land_meta_ids.Add(land_meta_id)) { err_msg = $"Failed to Add() !!!, in getAuctionableLandMetaIdsWithTtlSec() - landMetaId:{land_meta_id}, {toBasicString()}"; Log.getLogger().error(err_msg); continue; } } short ttl_sec = 0; if(0 < auctionable_land_meta_ids.Count) { ttl_sec = (short)(auctionable_land_meta_ids.Count * 2); } return (auctionable_land_meta_ids.ToList(), ttl_sec); } public async Task addActivitingLandAuctionByReservationKey(META_ID landMetaId) { var result = new Result(); var err_msg = string.Empty; if (false == m_activitings.TryGetValue(landMetaId, out var found_land_auction)) { var new_land_auction = new LandAuction(); result = await new_land_auction.onInit(); if (result.isFail()) { err_msg = $"Failed to LandAuction.onInit() !!!, in add_land_auction() - landMetaId:{landMetaId}, {toBasicString()}"; Log.getLogger().error(err_msg); return result; } m_activitings[landMetaId] = new_land_auction; found_land_auction = new_land_auction; var registry_attribute = new_land_auction.getEntityAttribute(); NullReferenceCheckHelper.throwIfNull(registry_attribute, () => $"registry_attribute is null !!! - landMetaId:{landMetaId}, {toBasicString()}"); registry_attribute.LandMetaId = landMetaId; } var land_auction_action = found_land_auction.getEntityAction(); NullReferenceCheckHelper.throwIfNull(land_auction_action, () => $"land_auction_action is null !!! - {found_land_auction.toBasicString()}"); err_msg = $"LandAuction Activitings <= ReservedKeys !!! : landMetaId:{landMetaId}, auctionNumber:{land_auction_action.getAuctionNumber()}"; Log.getLogger().debug(err_msg); return result; } public async Task tryLoadLandAuctionAll() { var result = new Result(); var err_msg = string.Empty; (result, var activity_docs) = await LandAuctionDbHelper.readLandAuctionActivityDocsFromDb(); if (result.isFail()) { err_msg = $"Failed to readLandAuctionActivityDocsFromDb() !!! : {result.toBasicString()} - {toBasicString()}"; Log.getLogger().error(err_msg); return result; } NullReferenceCheckHelper.throwIfNull(activity_docs, () => $"activity_docs is null !!! - {toBasicString()}"); foreach(var activity_doc in activity_docs) { var activity_attrib = activity_doc.getAttrib(); NullReferenceCheckHelper.throwIfNull(activity_attrib, () => $"activity_attrib is null !!! - {toBasicString()}"); var land_meta_id = activity_attrib.LandMetaId; var auction_number = activity_attrib.AuctionNumber; (var registry_result, _) = await tryLoadLandAuctionByMetaId(land_meta_id); if(registry_result.isFail()) { err_msg = $"Failed to tryLoadLandAuctionByMetaId() !!! : {registry_result.toBasicString()} - {toBasicString()}"; Log.getLogger().error(err_msg); continue; } } (result, var record_docs) = await LandAuctionDbHelper.readLandAuctionRecordDocsFromDb(); if (result.isFail()) { err_msg = $"Failed to readLandAuctionRecordDocsFromDb() !!! : {result.toBasicString()} - {toBasicString()}"; Log.getLogger().error(err_msg); return result; } NullReferenceCheckHelper.throwIfNull(record_docs, () => $"record_docs is null !!!"); foreach (var record_doc in record_docs) { var record_attrib = record_doc.getAttrib(); NullReferenceCheckHelper.throwIfNull(record_attrib, () => $"record_attrib is null !!! - {toBasicString()}"); var land_meta_id = record_attrib.LandMetaId; var auction_number = record_attrib.AuctionNumber; var found_land_auction = findRecordLandAuction(land_meta_id); if(null == found_land_auction) { err_msg = $"Not found record LandAuction !!!, in Record LandAuction : landMetaId:{land_meta_id}, auctionNumber:{auction_number} - {toBasicString()}"; Log.getLogger().error(err_msg); continue; } var land_auction_action = found_land_auction.getEntityAction(); NullReferenceCheckHelper.throwIfNull(land_auction_action, () => $"land_auction_action is null !!! - {toBasicString()}"); var record_result = await land_auction_action.tryLoadLandAuctionForRecord(auction_number); if (record_result.isFail()) { err_msg = $"Failed to tryLoadLandAuctionForRecord() !!! : {record_result.toBasicString()} - {toBasicString()}"; Log.getLogger().error(err_msg); continue; } } return result; } public void sendLandAuctionAllToUserGuid(Player player) { var err_msg = string.Empty; if( 0 >= m_activitings.Count && 0 >= m_records.Count ) { err_msg = $"No loaded LandAuction !!! - {toBasicString()}, {player.toBasicString()}"; Log.getLogger().warn(err_msg); return; } var land_auction_summaries = getLandAuctionAll(); if (false == LandAuctionNotifyHelper.send_GS2C_NTF_LAND_AUCTION_ALL_LOAD(player, land_auction_summaries)) { err_msg = $"Failed to send_GS2C_NTF_LAND_AUCTION_ALL_LOAD() !!! - {toBasicString()} - {player.toBasicString()}"; Log.getLogger().warn(err_msg); return; } Log.getLogger().info($"Sent LandAuction All : Count:{land_auction_summaries.Count} - {toBasicString()}"); } public async Task<(Result, LandAuction?)> tryLoadLandAuctionByMetaId(META_ID landMetaId) { var result = new Result(); var err_msg = string.Empty; var land_auction = new LandAuction(); result = land_auction.onInit().GetAwaiter().GetResult(); if (result.isFail()) { err_msg = $"Failed to LandAuction.onInit() !!!, in add_land_auction() - landMetaId:{landMetaId}, {toBasicString()}"; Log.getLogger().error(err_msg); return (result, null); } var land_auction_action = land_auction.getEntityAction(); NullReferenceCheckHelper.throwIfNull(land_auction_action, () => $"land_auction_action is null !!! - landMetaId:{landMetaId}, {toBasicString()}"); result = await land_auction_action.tryLoadLandAuctionMetaWithLock(landMetaId); if (result.isFail()) { err_msg = $"Failed to tryLoadLandAuctionMeta() !!! : {result.toBasicString()} - {toBasicString()}"; Log.getLogger().error(err_msg); return (result, null); } if (true == land_auction_action.isLandAuctionState(LandAuctionState.Ended)) { m_records.TryAdd(landMetaId, land_auction); } else { var add_land_auction = delegate (META_ID landMetaId) { return land_auction; }; var found_land_auction = m_activitings.AddOrUpdate(landMetaId, add_land_auction, (key, value) => value); NullReferenceCheckHelper.throwIfNull(found_land_auction, () => $"found_land_auction is null !!! - landMetaId:{landMetaId}, {toBasicString()}"); } return (result, land_auction); } public async Task<(Result, LandAuctionCheckResult?)> tryCheckActivitingLandAuctionByMetaId(META_ID landMetaId, REQUESTOR_ID requestorId) { var result = new Result(); var err_msg = string.Empty; var is_load_required_meta = true; if (false == MetaData.Instance._LandTable.TryGetValue((Int32)landMetaId, out var land_meta_data)) { err_msg = $"Failed to TryGetValue() !!! : landMetaId:{landMetaId} - requestorId:{requestorId}"; result.setFail(ServerErrorCode.LandMetaDataNotFound, err_msg); Log.getLogger().error(result.toBasicString()); return (result, null); } if (false == m_activitings.TryGetValue(landMetaId, out var found_land_auction)) { err_msg = $"Failed to TryGetValue() !!! : landMetaId:{landMetaId} - requestorId:{requestorId}"; result.setFail(ServerErrorCode.LandAuctionNotFound, err_msg); Log.getLogger().error(result.toBasicString()); return (result, null); } NullReferenceCheckHelper.throwIfNull(found_land_auction, () => $"found_land_auction is null !!! - landMetaId:{landMetaId}, requestorId:{requestorId}"); var land_auction_action = found_land_auction.getEntityAction(); NullReferenceCheckHelper.throwIfNull(land_auction_action, () => $"land_auction_action is null !!! - landMetaId:{landMetaId}, requestorId:{requestorId}, {toBasicString()}"); (result, var auction_check_result) = await land_auction_action.tryCheckActivitingLandAuctionWithTransactionRunner(landMetaId, requestorId, is_load_required_meta); if (result.isFail()) { err_msg = $"Failed to tryCheckActivitingLandAuctionWithTransactionRunner() !!! : {result.toBasicString()} - landMetaId:{landMetaId}, requestorId{requestorId}, {toBasicString()}"; Log.getLogger().error(err_msg); return (result, null); } return (result, auction_check_result); } public void activitingToRecord(META_ID landMetaId, LandAuction landAuction) { var err_msg = string.Empty; if(false == m_activitings.TryRemove(landMetaId, out _)) { err_msg = $"Failed to TryRemove() !!!, in Activitings : {landAuction.toBasicString()} - {toBasicString()}"; Log.getLogger().debug(err_msg); } m_records[landMetaId] = landAuction; var land_auction_action = landAuction.getEntityAction(); NullReferenceCheckHelper.throwIfNull(land_auction_action, () => $"land_auction_action is null !!! - {landAuction.toBasicString()}"); err_msg = $"LandAuction Records <= Activitings !!! : landMetaId:{landMetaId}, auctionNumber:{land_auction_action.getAuctionNumber()}"; Log.getLogger().debug(err_msg); } public LandAuction? findRecordLandAuction(META_ID landMetaId) { m_records.TryGetValue(landMetaId, out var found_land_auction); return found_land_auction; } public LandAuction? findActivitingLandAuction(META_ID landMetaId) { m_activitings.TryGetValue(landMetaId, out var found_land_auction); return found_land_auction; } public bool hasActivitingLandAuction(META_ID landMetaId) { return m_activitings.ContainsKey(landMetaId); } public async Task> getLandAuctionHistoryAll() { var land_auction_compacts = new List(); foreach (var each in m_records) { var land_auction = each.Value; var land_auction_action = land_auction.getEntityAction(); NullReferenceCheckHelper.throwIfNull(land_auction_action, () => $"land_auction_action is null !!! - {toBasicString()}"); if (true == land_auction_action.isHistory()) { var land_auction_compact = await land_auction_action.toLandAuctionCompact(); if (null == land_auction_compact) { continue; } land_auction_compacts.Add(land_auction_compact); } } return land_auction_compacts; } public List getLandAuctionScheduleAll() { var land_auction_summaries = new List(); foreach (var each in m_activitings) { var land_auction = each.Value; var land_auction_action = land_auction.getEntityAction(); NullReferenceCheckHelper.throwIfNull(land_auction_action, () => $"land_auction_action is null !!! - {toBasicString()}"); if(true == land_auction_action.isScheduling()) { land_auction_summaries.Add(land_auction_action.toLandAuctionSummary()); } } return land_auction_summaries; } public List getLandAuctionAll() { var land_auction_summaries = new List(); foreach (var each in m_activitings) { var land_auction = each.Value; var land_auction_action = land_auction.getEntityAction(); NullReferenceCheckHelper.throwIfNull(land_auction_action, () => $"land_auction_action is null !!! - {toBasicString()}"); if ( true == land_auction_action.isLandAuctionState(LandAuctionState.Scheduled ) || true == land_auction_action.isLandAuctionState(LandAuctionState.Started ) || true == land_auction_action.isLandAuctionState(LandAuctionState.Ended ) ) { land_auction_summaries.Add(land_auction_action.toLandAuctionSummary()); } } foreach (var each in m_records) { var land_auction = each.Value; var land_auction_action = land_auction.getEntityAction(); NullReferenceCheckHelper.throwIfNull(land_auction_action, () => $"land_auction_action is null !!! - {toBasicString()}"); if(land_auction_action.isLandAuctionResult(LandAuctionResult.Successed)) { land_auction_summaries.Add(land_auction_action.toLandAuctionSummary()); } } return land_auction_summaries; } public ConcurrentDictionary getRecords() => m_records; public ConcurrentDictionary getActivitings() => m_activitings; public string toBasicString() { return $"{GameServerApp.getServerLogic().toBasicString()}"; } } }