using System.Collections.Concurrent; using System.Diagnostics; using System.Numerics; using ServerCore; using ServerBase; using USER_GUID = System.String; namespace ServerCommon; public class ReservationManagerBase { public const int RESERVATION_TASK_DELAY_SEC = 10; protected readonly ReservationUserManager m_reservation_user_manager = new(); protected readonly ReservationMessageManager m_reservation_message_manager = new(); private ServerLogicBase? m_server_logic { get; set; } private ConcurrentDictionary m_cancelled_tasks { get; set; } = new(); public virtual void onInit(ServerLogicBase serverLogic) { m_server_logic = serverLogic; m_reservation_user_manager.onInit(serverLogic); } public int getReservedUserCount() => m_reservation_user_manager.Count; public ReservationMessageManager getMessageManager() => m_reservation_message_manager; public async Task checkLimitReservedUsers() => await m_reservation_user_manager.checkLimitReservedUsers(); public async Task checkReservation(USER_GUID userGuid) { await Task.CompletedTask; var result = new Result(); var check = m_reservation_user_manager.checkReserved(userGuid); if (false == check) { var err_msg = $"Failed to check reservation enter to server !!! : UserGuid - {userGuid}"; Log.getLogger().error(err_msg); result.setFail(ServerErrorCode.InvalidReservationUser, err_msg); return result; } m_reservation_user_manager.deleteReserved(userGuid); return result; } public async Task registerReservationEnterToServer(ServerMessage.Types.GS2GS_REQ_RESERVATION_ENTER_TO_SERVER message, string destServerName) { var result = new Result(); var stopwatch = new Stopwatch(); long elapsed_msec; var event_tid = Guid.NewGuid().ToString(); stopwatch.Start(); var next_task = new Task(() => getMessageManager().ackTask(message.RequestUserGuid)); ServerMessage.Types.GS2GS_ACK_RESERVATION_ENTER_TO_SERVER? reserved = null; // 1. task manager 에 등록 var is_add = m_reservation_message_manager.addReservationMessage(message.RequestUserGuid, message, next_task); if (false == is_add) { var err_msg = $"fail to add reservation message: userGuid[{message.RequestUserGuid}] message[{message}]"; result.setFail(ServerErrorCode.FailedToReservationEnter, err_msg); Log.getLogger().error(err_msg); return null; } // 2. server manager 전송 var server_message = new ServerMessage(); server_message.ReqReservationEnterToServer = message; NullReferenceCheckHelper.throwIfNull(m_server_logic, () => $"m_server_logic is null !!!"); var rabbit_mq = m_server_logic.getRabbitMqConnector() as RabbitMqConnector; NullReferenceCheckHelper.throwIfNull(rabbit_mq, () => $"rabbit_mq is null !!!"); rabbit_mq.SendMessage(destServerName, server_message); try { reserved = await next_task.WaitAsync(TimeSpan.FromSeconds(RESERVATION_TASK_DELAY_SEC)); // 3. 예약 실패 체크 if (null == reserved || reserved.Result.isFail()) { Log.getLogger().warn($"Failed to reservation enter to game server!!! - {reserved?.Result.toBasicString() ?? string.Empty}"); elapsed_msec = stopwatch.ElapsedMilliseconds; stopwatch.Stop(); Log.getLogger().debug($"registerReservationEnterToServer Stopwatch Stop : ETID:{event_tid}, ElapsedMSec:{elapsed_msec}, UserGuid:{message.RequestUserGuid}, srcServer:{message.RequestServerName}, destServer:{destServerName}"); return null; } } catch (Exception e) { // Task Cancelled deleteReservationEnterToServer(message.RequestUserGuid); Log.getLogger().warn($"Failed to reservation enter to game server!!! - {e}"); elapsed_msec = stopwatch.ElapsedMilliseconds; stopwatch.Stop(); Log.getLogger().debug($"registerReservationEnterToServer Stopwatch Stop : ETID:{event_tid}, ElapsedMSec:{elapsed_msec}, UserGuid:{message.RequestUserGuid}, srcServer:{message.RequestServerName}, destServer:{destServerName}"); return null; } elapsed_msec = stopwatch.ElapsedMilliseconds; stopwatch.Stop(); Log.getLogger().debug($"registerReservationEnterToServer Stopwatch Stop : ETID:{event_tid}, ElapsedMSec:{elapsed_msec}, UserGuid:{message.RequestUserGuid}, srcServer:{message.RequestServerName}, destServer:{destServerName}"); return reserved; } public async Task cancelReservationEnterToServer(USER_GUID userGuid, string destServerName) { var result = new Result(); NullReferenceCheckHelper.throwIfNull(m_server_logic, () => $"m_server_logic is null !!!"); var server_message = new ServerMessage(); server_message.ReqReservationCancelToServer = new(); server_message.ReqReservationCancelToServer.RequestServerName = m_server_logic.getServerName(); server_message.ReqReservationCancelToServer.RequestUserGuid = userGuid; // 1. 취소 전송 var rabbit_mq = m_server_logic.getRabbitMqConnector() as RabbitMqConnector; NullReferenceCheckHelper.throwIfNull(rabbit_mq, () => $"rabbit_mq is null !!!"); rabbit_mq.SendMessage(destServerName, server_message); // 2. 응답 대기 var task = new Task(() => { Log.getLogger().debug($"cancelReservationEnterToServer: ack cancel reservation - userGuid[{userGuid}]"); m_cancelled_tasks.Remove(userGuid, out _); }); m_cancelled_tasks.TryAdd(userGuid, task); try { await task.WaitAsync(TimeSpan.FromSeconds(RESERVATION_TASK_DELAY_SEC)); } catch (Exception e) { Log.getLogger().debug($"cancelReservationEnterToServer: exception cancel reservation - userGuid[{userGuid}] / {e}"); m_cancelled_tasks.Remove(userGuid, out _); } return result; } public async Task ackCancelProcess(string userGuid) { if (!m_cancelled_tasks.TryGetValue(userGuid, out var task)) return; task.Start(); await Task.CompletedTask; } private void deleteReservationEnterToServer(USER_GUID userGuid) => m_reservation_message_manager.deleteReservationMessage(userGuid); public async Task cancelProcess(ServerMessage.Types.GS2GS_REQ_RESERVATION_CANCEL_TO_SERVER message) { m_reservation_user_manager.deleteReserved(message.RequestUserGuid); await Task.CompletedTask; } public async Task ackProcess(ServerMessage.Types.GS2GS_ACK_RESERVATION_ENTER_TO_SERVER message) => await m_reservation_message_manager.onReceivedReservationEnterToServer(message); public async Task reqProcess(ServerMessage.Types.GS2GS_REQ_RESERVATION_ENTER_TO_SERVER request) { var stopwatch = new Stopwatch(); var event_tid = Guid.NewGuid().ToString(); stopwatch.Start(); var server_message = new ServerMessage(); server_message.AckReservationEnterToServer = new(); server_message.AckReservationEnterToServer.ReservationUserGuid = request.RequestUserGuid; server_message.AckReservationEnterToServer.Result = new(); // 1. 조건 체크 var is_check_condition = await checkReservationCondition(request.MoveType, request.RequestUserGuid); if (false == is_check_condition) { server_message.AckReservationEnterToServer.Result.setFail(ServerErrorCode.FailedToReserveEnterCondition, $"Failed to reservation enter !!! : check condition failed {nameof(reqProcess)}"); sendAckReservationEnterToServer(server_message, request.RequestServerName); return; } // 2. 예약 하기 Log.getLogger().debug($"add reserve user : userGuid[{request.RequestUserGuid}]"); var is_reserved = m_reservation_user_manager.AddReserved(request.RequestUserGuid); if (false == is_reserved) { server_message.AckReservationEnterToServer.Result.setFail(ServerErrorCode.FailedToReservationEnter, $"Failed to reserve enter !!! - {nameof(reqProcess)}"); sendAckReservationEnterToServer(server_message, request.RequestServerName); return; } // 4. 예약 결과 전송 server_message.AckReservationEnterToServer.Result.setSuccess(); NullReferenceCheckHelper.throwIfNull(m_server_logic, () => $"m_server_logic is null !!!"); server_message.AckReservationEnterToServer.ReservationServerName = m_server_logic.getServerName(); sendAckReservationEnterToServer(server_message, request.RequestServerName); var elapsed_msec = stopwatch.ElapsedMilliseconds; stopwatch.Stop(); Log.getLogger().debug($"ReservationEnterToServer reqProcess Stopwatch Stop: ETID:{event_tid}, ElapsedMSec:{elapsed_msec}, UserGuid:{request.RequestUserGuid}, srcServer:{request.RequestServerName}, destServer:{m_server_logic.getServerName()}"); } protected virtual async Task checkReservationCondition(ServerMoveType moveType, USER_GUID reservationUserGuid) => await Task.FromResult(true); protected virtual async Task checkFreeSessionCount() => await Task.FromResult(0); protected bool calculateConnectionRate(int freeCount) { var rate = MetaHelper.GameConfigMeta.AutoMoveRate; var allow_count = freeCount - 1; if (allow_count <= 0) return false; NullReferenceCheckHelper.throwIfNull(m_server_logic, () => $"m_server_logic is null !!!"); var proud_net_listener = m_server_logic.getProudNetListener(); var current_rate = ((float)(proud_net_listener.getMaxConnectionCount() - allow_count) / proud_net_listener.getMaxConnectionCount()) * 100; return current_rate <= rate; } private void sendAckReservationEnterToServer(ServerMessage message, string destServerName) { NullReferenceCheckHelper.throwIfNull(m_server_logic, () => $"m_server_logic is null !!!"); var rabbit_mq = m_server_logic.getRabbitMqConnector() as RabbitMqConnector; NullReferenceCheckHelper.throwIfNull(rabbit_mq, () => $"rabbit_mq is null !!!"); rabbit_mq.SendMessage(destServerName, message); } }