초기커밋
This commit is contained in:
71
GameServer/Contents/GameMode/Helper/GameModeHelper.cs
Normal file
71
GameServer/Contents/GameMode/Helper/GameModeHelper.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
239
GameServer/Contents/GameMode/Helper/HostMigrationFactory.cs
Normal file
239
GameServer/Contents/GameMode/Helper/HostMigrationFactory.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
522
GameServer/Contents/GameMode/Helper/HostMigrationSystem.cs
Normal file
522
GameServer/Contents/GameMode/Helper/HostMigrationSystem.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
162
GameServer/Contents/GameMode/Helper/PingLatencyKicker.cs
Normal file
162
GameServer/Contents/GameMode/Helper/PingLatencyKicker.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user