초기커밋

This commit is contained in:
2025-05-01 07:20:41 +09:00
commit 98bb2e3c5c
2747 changed files with 646947 additions and 0 deletions

View File

@@ -0,0 +1,71 @@
using Google.Protobuf;
using Google.Protobuf.WellKnownTypes;
using ServerCore;
using ServerBase;
using ServerCommon;
using ServerCommon.BusinessLogDomain;
using MetaAssets;
namespace GameServer;
public class GameModeHelper
{
public static (Result, IGameModeJoinHandler) getGameModeJoinHandler(InstanceRoom instanceRoom, EPlaceType placeType)
{
var result = new Result();
switch (placeType)
{
case EPlaceType.BattleRoom:
return (result, new BattleInstanceJoinHandler(instanceRoom));
case EPlaceType.ArcadeRunning:
return (result, new ArcadeRunningInstanceJoinHandler(instanceRoom));
case EPlaceType.GameRoom:
return (result, new GameRoomJoinHandler(instanceRoom));
default:
var err_msg = $"invalid placeType in this mode!!!! placeType : {placeType}";
result.setFail(ServerErrorCode.GameModeJoinHandlerNotExist, err_msg);
return (result, new BattleInstanceJoinHandler(instanceRoom));
}
}
public static (Result, IGameModeInitHandler) getGameModeInitHandler(InstanceRoom instanceRoom, EPlaceType placeType)
{
var result = new Result();
switch (placeType)
{
case EPlaceType.BattleRoom:
return (result, new BattleInstanceInitHandler(instanceRoom));
case EPlaceType.ArcadeRunning:
return (result, new ArcadeRunningInstanceInitHandler(instanceRoom));
case EPlaceType.GameRoom:
return (result, new GameRoomInitHandler(instanceRoom));
default:
var err_msg = $"getGameModeInitHandler not!!!! placeType : {placeType}";
result.setFail(ServerErrorCode.GameModeInitHandlerNotExist, err_msg);
return (result, new BattleInstanceInitHandler(instanceRoom));
}
}
public static (Result, IGameModeJoinSuccessHandler?) getGameModeJoinSuccessHandler(Player player, InstanceRoom instanceRoom, EPlaceType placeType)
{
var result = new Result();
switch (placeType)
{
case EPlaceType.BattleRoom:
return (result, new BattleInstanceJoinSuccessHandler(player, instanceRoom));
case EPlaceType.ArcadeRunning:
return (result, new ArcadeRunningInstanceJoinSuccessHandler(player, instanceRoom));
case EPlaceType.GameRoom:
return (result, new GameRoomJoinSuccessHandler(player, instanceRoom));
default:
var err_msg = $"getGameModeInitHandler not!!!! placeType : {placeType}";
result.setFail(ServerErrorCode.GameModeJoinSuccessHandlerNotExist, err_msg);
return (result, null);
}
}
}

View File

@@ -0,0 +1,239 @@
using Nettention.Proud;
using ServerCore;
using ServerBase;
using ServerCommon;
using MetaAssets;
namespace GameServer;
public class HostMigrationFactory
{
public static IHostMigrator createCommonHostMigrator(int battleModeId, GameServerLogic gameServerLogic)
{
// battleModeId <- 게임 모드 ID 테이블 참조용
// TODO: battleModeId를 사용하여 HostMigrationPolicy 생성하는 것으로 변경 예정
int game_mode_option_data_id = 1;
var host_migration_policy = createHostMigrationPolicyByMeta(game_mode_option_data_id);
var net_server = gameServerLogic.getProudNetListener().getNetServer();
NullReferenceCheckHelper.throwIfNull(net_server, () => $"net_server is null !!!!");
var host_migration_system = new HostMigrationSystem(net_server, host_migration_policy);
var host_migrator = new CommonHostMigrator(host_migration_system, gameServerLogic);
return host_migrator;
}
private static HostMigrationPolicy createHostMigrationPolicyByMeta(int gameModeOptionDataId)
{
var meta = getMeta(gameModeOptionDataId);
return new HostMigrationPolicy
{
HostKickFpsThreshold = meta.HostKickFpsThreshold,
HostKickFpsDuration = TimeSpan.FromSeconds(meta.HostKickFpsDuration),
HostKickPingThreshold = meta.HostKickPingThreshold,
HostKickPingDuration = TimeSpan.FromSeconds(meta.HostKickPingDuration),
};
GameModeOptionMetaData getMeta(int gameModeOptionDataId)
{
var game_mode_option_meta_table = MetaData.Instance.Meta.GameModeOptionMetaTable;
game_mode_option_meta_table.GameModeOptionDataListbyGameModeOptionId.TryGetValue(gameModeOptionDataId,
out var game_mode_option_meta);
NullReferenceCheckHelper.throwIfNull(game_mode_option_meta, () => $"game_mode_option_meta is null !!!!");
return game_mode_option_meta;
}
}
}
public class CommonHostMigrator : IHostMigrator
{
private readonly HostMigrationSystem m_host_migration_system;
private string m_host_user_guid = string.Empty;
private readonly GameServerLogic m_game_server_logic;
private DateTime m_last_loop_time = DateTime.MinValue;
private readonly TimeSpan m_loop_interval = TimeSpan.FromSeconds(1);
public HostID SuperPeerHostId => m_host_migration_system.SuperPeerHostId;
public HostMigrationSystem HostMigrationSystem => m_host_migration_system;
public CommonHostMigrator(HostMigrationSystem hostMigrationSystem, GameServerLogic gameServerLogic)
{
m_host_migration_system = hostMigrationSystem;
m_game_server_logic = gameServerLogic;
}
public (Result, bool) migrateCheck(bool ignoreInterval = false)
{
if (!ignoreInterval && isOnMigrationLoopInterval())
{
return (new Result(), false);
}
var (is_migrated, new_super_peer_id) = m_host_migration_system.migrateCheck();
if (is_migrated)
{
var (result, user_guid) = getUserGuidByHostId(new_super_peer_id);
if (result.isFail())
{
return (result, is_migrated);
}
var host_change_success = m_host_migration_system.changeSuperPeerHostId(new_super_peer_id);
if (!host_change_success)
{
return (new Result
{
ErrorCode = ServerErrorCode.NotFoundUser, ResultString = "Failed to change super peer."
}, is_migrated);
}
m_host_user_guid = user_guid;
return (result, is_migrated);
}
return (new Result(), false);
}
public void setGroupHostId(HostID groupId)
{
m_host_migration_system.setGroupHostId(groupId);
}
//=================================================================
// 삭제 예정
//=================================================================
public Result defineHost(HostID p2pGroupId = HostID.HostID_None, SuperPeerSelectionPolicy policy = null!, HostID[] excludes = null!)
{
// m_host_migration_system.setGroupHostId(p2pGroupId);
//
// var (is_migrated, new_super_peer_id) = m_host_migration_system.migrateCheck();
//
// if (is_migrated)
// {
// var (result, user_guid) = getUserGuidByHostId(new_super_peer_id);
// if (result.isFail())
// {
// return result;
// }
//
// var host_change_success = m_host_migration_system.changeSuperPeerHostId(new_super_peer_id);
// if (!host_change_success)
// {
// return new Result
// {
// ErrorCode = ServerErrorCode.NotFoundUser, ResultString = "Failed to change super peer."
// };
// }
// m_host_user_guid = user_guid;
// return result;
// }
return new Result();
}
public Result modifyHost(string userGuid)
{
var (result, host_id) = getHostIdByUserGuid(userGuid);
if (result.isFail())
{
return result;
}
if (m_host_migration_system.changeSuperPeerHostId(host_id))
{
m_host_user_guid = userGuid;
return result;
}
return new Result { ErrorCode = ServerErrorCode.NotFoundUser, ResultString = "Failed to change super peer." };
}
public Result removeHost()
{
m_host_migration_system.changeSuperPeerHostId(HostID.HostID_None);
m_host_user_guid = string.Empty;
return new Result();
}
public string getHostUserGuid()
{
return m_host_user_guid;
}
private (Result, string) getUserGuidByHostId(HostID hostId)
{
string err_msg;
var result = new Result();
if (hostId == HostID.HostID_None)
{
err_msg = $"there is not suitable super peer !!!! host_id : {hostId}";
result.setFail(ServerErrorCode.NotFoundUser, err_msg);
return (result, string.Empty);
}
var session = m_game_server_logic.getProudNetListener().onLookupEntityWithSession((int)hostId);
if (session is null)
{
err_msg = $"session is null!!!! host_id : {hostId}";
result.setFail(ServerErrorCode.NotFoundUser, err_msg);
return (result, string.Empty);
}
var player = session as Player;
if (player is null)
{
err_msg = $"player is null!!!! host_id : {hostId}";
result.setFail(ServerErrorCode.NotFoundUser, err_msg);
return (result, string.Empty);
}
return (result, player.getUserGuid());
}
private (Result, HostID) getHostIdByUserGuid(string userGuid)
{
string err_msg;
var result = new Result();
// todo: 임시 다른 방법 모색 - 비용이 비쌀 수 있음
var session_pair = m_game_server_logic.getProudNetListener()
.getEntityWithSessions()
.FirstOrDefault(x => x.Value.getUserGuid() == userGuid);
var session = session_pair.Value;
if (session is null)
{
err_msg = $"session is null!!!! user_guid : {userGuid}";
result.setFail(ServerErrorCode.NotFoundUser, err_msg);
return (result, HostID.HostID_None);
}
if (session is not Player player)
{
err_msg = $"player is null!!!! user_guid : {userGuid}";
result.setFail(ServerErrorCode.NotFoundUser, err_msg);
return (result, HostID.HostID_None);
}
return (result, player.getHostId());
}
//=================================================================
// 호스트 마이그레이션 루프 인터벌 중인지 여부
//=================================================================
private bool isOnMigrationLoopInterval()
{
if (m_last_loop_time == DateTime.MinValue)
{
m_last_loop_time = DateTime.UtcNow;
return false;
}
if (m_last_loop_time.Add(m_loop_interval) < DateTime.UtcNow)
{
return true;
}
m_last_loop_time = DateTime.UtcNow;
return false;
}
}

View File

@@ -0,0 +1,522 @@
using System;
using System.Threading.Tasks;
using Nettention.Proud;
using ServerCore;
namespace GameServer;
//===================================================================
// 호스트 마이그레이션 정책 - 기획서 기반
//===================================================================
public class HostMigrationPolicy
{
// 현재 호스트의 Ping이 너무 높을 경우, 호스트를 교체(마이그레이션)하기 위한 임계값(ms).
public int HostKickPingThreshold { get; set; } = 500;
// PingKickThreshold를 초과한 상태가 몇 초(sec) 동안 지속되면 호스트 마이그레이션이 일어나는 지를 설정. 도중에 정상 복귀하면 타이머 리셋.
public TimeSpan HostKickPingDuration { get; set; } = TimeSpan.FromSeconds(30);
// 호스트의 FPS가 이 값 이하면 마이그레이션 타이머 발동.
public int HostKickFpsThreshold { get; set; } = 30;
// HostFpsThreshold를 초과한 상태가 몇 초(sec) 동안 지속되면 호스트 마이그레이션이 일어나는 지를 설정. 도중에 정상 복귀하면 타이머 리셋.
public TimeSpan HostKickFpsDuration { get; set; } = TimeSpan.FromSeconds(30);
// 한 번 호스트 마이그레이션이 발생한 후, 다시 마이그레이션이 일어나기까지의 최소 대기 시간(쿨다운, min)으로 아예 응답이 없는 경우 쿨다운을 무시하고 즉시 호스트 마이그레이션 발동.
public TimeSpan MigrationCooldown { get; set; } = TimeSpan.FromSeconds(60);
// 입장 후 호스트 변경 유예 시간 ms
public long ExcludeNewJoineeDurationTimeMs { get; set; } = 0;
}
//===================================================================
// 호스트 마이그레이션 정책을 적용해서 호스트를 마이그레이션을 진행하는 클래스
//===================================================================
public class HostMigrationSystem
{
public enum MigrationStateType
{
None,
Ping,
Fps
}
public class MigrationState
{
public enum CheckType
{
None,
Greater,
Less
}
private readonly CheckType m_check_type;
private readonly int m_policy_threshold_value = 0;
private readonly TimeSpan m_policy_duration;
public MigrationState(MigrationStateType migrationStateType, CheckType checkType, int thresholdValue,
TimeSpan duration)
{
m_check_type = checkType;
m_policy_threshold_value = thresholdValue;
m_policy_duration = duration;
StateType = migrationStateType;
reset();
}
public MigrationStateType StateType { get; set; }
private DateTime m_trigger_time = DateTime.MinValue;
// public StateType State { get; set; } = StateType.None;
private int m_sum;
private int m_sum_count;
public void reset()
{
m_trigger_time = DateTime.MinValue;
m_sum = 0;
m_sum_count = 0;
}
public bool isHostKicked()
{
if (m_sum_count == 0)
{
return false;
}
var value = (int)Math.Round((double)m_sum / m_sum_count);
return shouldTrigger(value);
}
public bool isHostKickDurationExceeded()
{
return isHostKickCheckTriggered() && DateTime.UtcNow > m_trigger_time.Add(m_policy_duration);
}
public void setTriggerTime(DateTime? triggerTime = null)
{
if (triggerTime == null)
{
return;
}
if (m_trigger_time == DateTime.MinValue)
{
m_trigger_time = DateTime.UtcNow;
}
}
public void update(int value)
{
if (shouldTrigger(value))
{
setTriggerTime();
}
// TriggerTime이 설정된 경우에만 Sum을 계산
if (isHostKickCheckTriggered())
{
m_sum += value;
m_sum_count++;
}
}
private bool isHostKickCheckTriggered()
{
return m_trigger_time != DateTime.MinValue;
}
private bool shouldTrigger(int value)
{
return m_check_type switch
{
CheckType.Greater => value > m_policy_threshold_value,
CheckType.Less => value < m_policy_threshold_value,
_ => false
};
}
}
private readonly HostMigrationPolicy m_host_migration_policy;
private readonly NetServer m_server;
private HostID m_group_host_id = HostID.HostID_None;
private HostID m_super_peer_host_id = HostID.HostID_None;
private readonly List<MigrationState> m_migration_states;
private DateTime m_last_migration_time = DateTime.MinValue;
private bool m_ignore_migration_condition;
public HostMigrationPolicy HostMigrationPolicy => m_host_migration_policy;
public HostID SuperPeerHostId => m_super_peer_host_id;
public HostID GroupHostHostId => m_group_host_id;
public NetClientInfo? CurrentSuperPeerInfo => m_server.GetClientInfo(m_super_peer_host_id);
// StartServerParameter.enablePingTest = true 옵션이 필요함
public HostMigrationSystem(NetServer server, HostMigrationPolicy hostMigrationPolicy)
{
m_server = server;
m_host_migration_policy = hostMigrationPolicy;
m_migration_states =
[
new MigrationState(MigrationStateType.Fps, MigrationState.CheckType.Less,
m_host_migration_policy.HostKickFpsThreshold,
m_host_migration_policy.HostKickFpsDuration),
new MigrationState(MigrationStateType.Ping, MigrationState.CheckType.Greater,
m_host_migration_policy.HostKickPingThreshold,
m_host_migration_policy.HostKickPingDuration)
];
var proudnet_policy = SuperPeerSelectionPolicy.GetOrdinary();
// 클라이언트가 로딩하는 시간동안 프레임 측정이 정화하지 않을 수 있기 때문에 오차가 발생할 수 있음
// 게임 시작 기준에 따라 설정이나 migrate() 호출 시점을 변경해야할 수 있음
proudnet_policy.m_excludeNewJoineeDurationTimeMs = hostMigrationPolicy.ExcludeNewJoineeDurationTimeMs;
}
//=================================================================
// P2P 그룹 아이디 설정 - 최초 한번만 설정됨
//=================================================================
public void setGroupHostId(HostID groupHostId)
{
if (m_group_host_id == HostID.HostID_None && groupHostId != HostID.HostID_None)
{
m_group_host_id = groupHostId;
}
}
//=================================================================
// 현재 수퍼 피어 아이디 설정
// - getMostSuitableSuperPeer() 호출 후
// - 클라이언트에 호스트 변경 요청
// - 클라이언트 응답을 받은 후, changeSuperPeerId() 호출
//=================================================================
public bool changeSuperPeerHostId(HostID superPeerId)
{
if (!isHostIdInP2PGroup(superPeerId))
{
return false;
}
m_super_peer_host_id = superPeerId;
return true;
}
//=================================================================
// 호스트 변경 요청
// migrate() 호출 시점에 따라 마이그레이션이 발생하지 않을 수 있음
// migrate()가 처리되지 않아도 에러가 아니기 때문에 bool을 반환함
//=================================================================
public (bool, HostID) migrateCheck()
{
if (m_ignore_migration_condition)
{
return migrateCheckIgnoreCondition();
}
try
{
// P2P 그룹이 설정돼 있는 지 확인
if (!isValidGroup())
{
return (false, m_super_peer_host_id);
}
// 최초에 수퍼 피어가 설정되지 않은 경우
var super_peer_id = m_super_peer_host_id;
if (m_super_peer_host_id == HostID.HostID_None)
{
super_peer_id = m_server.GetMostSuitableSuperPeerInGroup(m_group_host_id);
changeSuperPeerHostId(super_peer_id);
return (true, super_peer_id);
}
if (!isValidSuperPeer())
{
return (false, m_super_peer_host_id);
}
// 설정 시간만큼 설정 시간 유지
if (isOnMigrationCooldown())
{
return (false, m_super_peer_host_id);
}
hostMigrationStateUpdate();
if (!isHostKickDurationExceeded())
{
return (false, m_super_peer_host_id);
}
// 마이그레이션이 발생할 수 있는 조건인지 체크
if (isHostKicked())
{
super_peer_id = findSuperPeer();
}
resetMigrationState();
return (true, super_peer_id);
}
catch(Exception e)
{
Log.getLogger().error($"HostMigrationSystem.migrateCheck => {e}");
}
return (false, m_super_peer_host_id);
}
private void resetMigrationState()
{
foreach (var state in m_migration_states)
{
state.reset();
}
m_last_migration_time = DateTime.MinValue;
}
//=================================================================
// 호스트 변경 요청 - 무조건
//=================================================================
public (bool, HostID) getMostSuitableSuperPeerIgnoreCondition()
{
// P2P 그룹이 설정돼 있는 지 확인
if (!isValidGroup())
{
return (false, m_super_peer_host_id);
}
// checkAndValidateSuperPeer();
// if (isValidSuperPeer())
// {
// // 설정 시간만큼 설정 시간 유지
// if (isOnMigrationCooldown())
// {
// return (false, m_super_peer_id);
// }
//
// // 마이그레이션이 발생할 수 있는 조건인지 체크
// if (!isHostMigrateCondition())
// {
// return (false, m_super_peer_id);
// }
// }
//
// resetMigrationState();
var super_peer_id = m_server.GetMostSuitableSuperPeerInGroup(m_group_host_id);
if (super_peer_id == m_super_peer_host_id)
{
return (false, m_super_peer_host_id);
}
m_last_migration_time = DateTime.UtcNow;
return (true, super_peer_id);
}
//=================================================================
// 호스트 변경 요청
// getMostSuitableSuperPeerAsync() 호출 시점에 따라 마이그레이션이 발생하지 않을 수 있음
// getMostSuitableSuperPeerAsync()가 처리되지 않아도 에러가 아니기 때문에 bool을 반환함
//=================================================================
public async Task<(bool, HostID)> migrateCheckAsync()
{
return await Task.Run(migrateCheck);
}
private (bool, HostID) migrateCheckIgnoreCondition()
{
try
{
// P2P 그룹이 설정돼 있는 지 확인
if (!isValidGroup())
{
return (false, m_super_peer_host_id);
}
var super_peer_id = findSuperPeer();
resetMigrationState();
return (true, super_peer_id);
}
catch(Exception e)
{
Log.getLogger().error($"HostMigrationSystem.migrateCheckIgnoreCondition => {e}");
}
finally
{
m_ignore_migration_condition = false;
}
return (true, m_super_peer_host_id);
}
private void hostMigrationStateUpdate()
{
var info = m_server.GetClientInfo(m_super_peer_host_id);
m_migration_states.ForEach(state =>
{
switch (state.StateType)
{
case MigrationStateType.Fps:
state.update((int)info.recentFrameRate);
break;
case MigrationStateType.Ping:
state.update(info.recentPingMs);
break;
}
});
}
private bool isHostKicked()
{
return m_migration_states.Any(state => state.isHostKicked());
}
private bool isHostKickDurationExceeded()
{
return m_migration_states.Any(state => state.isHostKickDurationExceeded());
}
//=================================================================
// 호스트 변경 유예 조건 체크
//=================================================================
private bool isOnMigrationCooldown()
{
// 설정 시간 만큼 유지
return m_last_migration_time.Add(m_host_migration_policy.MigrationCooldown) > DateTime.UtcNow;
}
//=================================================================
// 호스트 그룹이 삭제됐는 지 확인
//=================================================================
private bool isValidGroup()
{
// 호스트 그룹 아이디가 설정되지 않은 경우
if (m_group_host_id == HostID.HostID_None)
{
return false;
}
// 호스트 그룹 아이디가 설정되어 있지만, P2P 그룹 정보가 없는 경우
if (m_group_host_id != HostID.HostID_None && m_server.GetP2PGroupInfo(m_group_host_id) == null)
{
m_group_host_id = HostID.HostID_None;
return false;
}
// 그룹에 속한 호스트가 없는 경우
if (m_group_host_id != HostID.HostID_None && m_server.GetP2PGroupInfo(m_group_host_id).members.GetCount() == 0)
{
m_group_host_id = HostID.HostID_None;
return false;
}
return true;
}
//=================================================================
// 수퍼 피어가 설정되어 있는지 체크
//=================================================================
private bool isValidSuperPeer()
{
// 수퍼 피어가 퇴장한 경우 등을 체크
if (m_super_peer_host_id != HostID.HostID_None && m_server.GetClientInfo(m_super_peer_host_id) == null)
{
m_super_peer_host_id = HostID.HostID_None;
}
return m_super_peer_host_id != HostID.HostID_None;
}
//=================================================================
// HostID가 p2pGroup에 속해 있는지 체크
//=================================================================
private bool isHostIdInP2PGroup(HostID hostId)
{
if (!isValidGroup())
{
return false;
}
return Enumerable.Range(0, m_server.GetP2PGroupInfo(m_group_host_id).members.GetCount())
.Any(i => m_server.GetP2PGroupInfo(m_group_host_id).members.At(i) == hostId);
}
private HostID findSuperPeer()
{
var peer_infos = new SortedList<HostID, NetClientInfo>();
var group_info = m_server.GetP2PGroupInfo(m_group_host_id);
Enumerable.Range(0, group_info.members.GetCount()).ToList().ForEach(i =>
{
var host_id = group_info.members.At(i);
if (host_id == m_super_peer_host_id)
{
return;
}
var info = m_server.GetClientInfo(host_id);
if (info == null)
{
return;
}
peer_infos.Add(info.hostID, info);
});
var recommend_super_peer_id = m_server.GetMostSuitableSuperPeerInGroup(m_group_host_id);
// peer_infos.TryGetValue(recommend_super_peer_id, out var recommend_super_peer_info);
var recommend_super_peer_info = m_server.GetClientInfo(recommend_super_peer_id);
NullReferenceCheckHelper.throwIfNull(recommend_super_peer_info, () => "recommend_super_peer_info is null");
if (!isValidPolicy(recommend_super_peer_info))
{
// 추천된 수퍼 피어가 마이그레이션 조건을 만족하지 않는 경우
// 커스텀 추천 로직 구현
// recentPingMs > HostKickPingThreshold && recentFrameRate < HostKickFpsThreshold
var custom_super_peer_info = peer_infos
.OrderBy(x => x.Value.recentPingMs)
.FirstOrDefault(x => isValidPolicy(x.Value));
if (custom_super_peer_info.Value != null)
{
Log.getLogger().info($"Host Migration Custom - recommend_super_peer_id: {recommend_super_peer_id}");
recommend_super_peer_id = custom_super_peer_info.Key;
}
else
{
// 정책에 맞는 수퍼 피어가 없는 경우 ping이 가장 낮은 수퍼 피어를 추천
var second_super_peer_info = peer_infos.OrderBy(x => x.Value.recentPingMs).FirstOrDefault();
if (second_super_peer_info.Value != null)
{
Log.getLogger().info($"Host Migration Second - recommend_super_peer_id: {recommend_super_peer_id}");
recommend_super_peer_id = second_super_peer_info.Key;
}
}
}
m_last_migration_time = DateTime.UtcNow;
Log.getLogger().info($"Host Migration ProudNet - recommend_super_peer_id: {recommend_super_peer_id}");
return recommend_super_peer_id;
bool isValidPolicy(NetClientInfo info)
{
return info.recentFrameRate > m_host_migration_policy.HostKickFpsThreshold &&
info.recentPingMs < m_host_migration_policy.HostKickPingThreshold;
}
}
//=================================================================
// 호스트 마이그레이션 조건을 무시할 지 설정 - 이슈 방지를 위해서 1회성으로 제안함
//=================================================================
public void setIgnoreMigrationCondition(bool ignore)
{
m_ignore_migration_condition = ignore;
}
}

View File

@@ -0,0 +1,162 @@
namespace GameServer.Contents.GameMode.Helper;
using Nettention.Proud;
public class PingLatencyPolicy
{
public int PingKickThreshold { get; set; } = 600;
public TimeSpan PingKickDuration { get; set; } = TimeSpan.FromSeconds(30);
}
//=====================================================================
// LatencyChecker - 플레이가 불가능한 핑 지연을 체크한다. super peer와의 핑을 체크
//=====================================================================
public class PingLatencyKicker
{
public class ClientLatency
{
public HostID HostId { get; set; }
public int PingSum { get; set; }
public int PingCheckCount { get; set; }
public DateTime PingCheckBeginTime { get; set; } = DateTime.UtcNow;
public int PingAverage => PingCheckCount > 0 ? PingSum / PingCheckCount: 0;
}
private readonly PingLatencyPolicy m_ping_latency_policy;
private readonly SortedList<HostID, ClientLatency> m_host_latencies = new();
private HostID m_group_id;
private HostID m_super_peer_id;
private readonly NetServer m_server;
public PingLatencyKicker(NetServer server, PingLatencyPolicy pingLatencyPolicy)
{
m_ping_latency_policy = pingLatencyPolicy;
m_server = server;
}
public void setGroupHostId(HostID groupId)
{
m_group_id = groupId;
}
public void setSuperPeerId(HostID superPeerId)
{
m_super_peer_id = superPeerId;
}
//=================================================================
// 호스트 그룹에 속한 클라이언트의 핑 지연을 체크하여, 게임이 불가한 지연이 발생한 경우 해당 호스트를 반환
//==============================================================
public IEnumerable<HostID> shouldKick()
{
if (!isValidGroup())
{
return [];
}
checkAndValidateSuperPeer();
if (!isValidSuperPeer())
{
return [];
}
var kick_host_ids = getGroupMemberHostIds().Where(x => checkLatency(x) == true);
return kick_host_ids;
}
public async Task<IEnumerable<HostID>> shouldKickAsync()
{
return await Task.Run(shouldKick);
}
//=================================================================
// 핑 지연 체크
//=================================================================
private bool checkLatency(HostID hostId)
{
var ping_to_super_peer = getP2PRecentPingMs(hostId);
m_host_latencies.TryGetValue(hostId, out var client_latency);
if (ping_to_super_peer > m_ping_latency_policy.PingKickThreshold)
{
if (client_latency == null)
{
client_latency = new ClientLatency
{
HostId = hostId, PingSum = 0, PingCheckCount = 0, PingCheckBeginTime = DateTime.UtcNow
};
m_host_latencies.Add(hostId, client_latency);
}
}
if (client_latency != null)
{
if (client_latency.PingCheckBeginTime + m_ping_latency_policy.PingKickDuration > DateTime.UtcNow)
{
if (client_latency.PingAverage > m_ping_latency_policy.PingKickThreshold)
{
return true;
}
m_host_latencies.Remove(hostId);
}
}
return false;
}
private int getP2PRecentPingMs(HostID hostId)
{
return m_server.GetP2PRecentPingMs(hostId, m_super_peer_id);
}
private IEnumerable<HostID> getGroupMemberHostIds()
{
var host_ids = new List<HostID>();
var group_info = m_server.GetP2PGroupInfo(m_group_id);
for (int i = 0; i < group_info.members.GetCount(); i++)
{
host_ids.Add(group_info.members.At(i));
}
return host_ids;
}
//=================================================================
// 호스트 그룹이 삭제됐는 지 확인
//=================================================================
private bool isValidGroup()
{
if (m_group_id == HostID.HostID_None)
return false;
var group_info = m_server.GetP2PGroupInfo(m_group_id);
if (group_info == null || group_info.members.GetCount() == 0)
{
m_group_id = HostID.HostID_None;
return false;
}
return true;
}
//=================================================================
// 수퍼 피어가 퇴장한 경우 등을 체크
//=================================================================
private void checkAndValidateSuperPeer()
{
// 수퍼 피어가 퇴장한 경우 등을 체크
if (m_super_peer_id != HostID.HostID_None && m_server.GetClientInfo(m_super_peer_id) == null)
{
m_super_peer_id = HostID.HostID_None;
}
}
//=================================================================
// 수퍼 피어가 설정되어 있는지 체크
//=================================================================
private bool isValidSuperPeer()
{
return m_super_peer_id != HostID.HostID_None;
}
}