规范服务端代码,使用.Net9的Lock对象

This commit is contained in:
sin365 2025-03-13 17:18:32 +08:00
parent fef8fe42a8
commit 9272f98631
14 changed files with 889 additions and 865 deletions

View File

@ -10,7 +10,7 @@ namespace AxibugEmuOnline.Server.Common
const int DefaultCount = 1;
const int MaxLimit = 10;
static readonly object _sync = new object();
static readonly Lock _sync = new Lock();
static MySqlConnectionStringBuilder connBuilder;
public static void InitConnMgr()

View File

@ -0,0 +1,30 @@
using System.Runtime.InteropServices;
namespace AxibugEmuOnline.Server.Data
{
[StructLayout(LayoutKind.Explicit, Size = 8)]
public struct ServerInputSnapShot
{
[FieldOffset(0)]
public UInt64 all;
[FieldOffset(0)]
public byte p1_byte;
[FieldOffset(1)]
public byte p2_byte;
[FieldOffset(2)]
public byte p3_byte;
[FieldOffset(3)]
public byte p4_byte;
[FieldOffset(0)]
public ushort p1_ushort;
[FieldOffset(2)]
public ushort p2_ushort;
[FieldOffset(4)]
public ushort p3_ushort;
[FieldOffset(6)]
public ushort p4_ushort;
}
}

View File

@ -2,6 +2,7 @@
using AxibugEmuOnline.Server.Common;
using AxibugEmuOnline.Server.NetWork;
using System.Net.Sockets;
using AxibugEmuOnline.Server.Manager.Client;
namespace AxibugEmuOnline.Server.Manager
{

View File

@ -0,0 +1,44 @@
using AxibugProtobuf;
using System.Net.Sockets;
namespace AxibugEmuOnline.Server.Manager.Client
{
public class ClientInfo
{
public long UID { get; set; }
public string NickName { get; set; } = string.Empty;
public string Account { get; set; } = string.Empty;
internal DeviceType deviceType { get; set; } = DeviceType.Default;
public Socket _socket { get; set; }
public bool IsOffline { get; set; } = false;
public DateTime RegisterDT { get; set; }
public DateTime LogOutDT { get; set; }
public DateTime LogInDT { get; set; }
public DateTime LastLogInDT { get; set; }
public UserRoomState RoomState { get; set; } = new UserRoomState();
public TimeSpan LastStartPingTime { get; set; }
public int LastPingSeed { get; set; }
public double AveNetDelay { get; set; }
public double MinNetDelay { get; set; }
public double MaxNetDelay { get; set; }
public List<double> NetDelays { get; set; } = new List<double>();
public const int NetAveDelayCount = 3;
}
public class UserRoomState
{
public int RoomID { get; private set; }
public UserRoomState()
{
ClearRoomData();
}
public void SetRoomData(int roomID)
{
RoomID = roomID;
}
public void ClearRoomData()
{
RoomID = -1;
}
}
}

View File

@ -1,54 +1,13 @@
using AxibugEmuOnline.Server.Common;
using AxibugEmuOnline.Server.Event;
using AxibugEmuOnline.Server.Manager.Client;
using AxibugEmuOnline.Server.NetWork;
using AxibugProtobuf;
using MySql.Data.MySqlClient;
using MySqlX.XDevAPI;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Timers;
namespace AxibugEmuOnline.Server.Manager
{
public class ClientInfo
{
public long UID { get; set; }
public string NickName { get; set; } = string.Empty;
public string Account { get; set; } = string.Empty;
internal DeviceType deviceType { get; set; } = DeviceType.Default;
public Socket _socket { get; set; }
public bool IsOffline { get; set; } = false;
public DateTime RegisterDT { get; set; }
public DateTime LogOutDT { get; set; }
public DateTime LogInDT { get; set; }
public DateTime LastLogInDT { get; set; }
public UserRoomState RoomState { get; set; } = new UserRoomState();
public TimeSpan LastStartPingTime { get; set; }
public int LastPingSeed { get; set; }
public double AveNetDelay { get; set; }
public double MinNetDelay { get; set; }
public double MaxNetDelay { get; set; }
public List<double> NetDelays { get; set; } = new List<double>();
public const int NetAveDelayCount = 3;
}
public class UserRoomState
{
public int RoomID { get; private set; }
public UserRoomState()
{
ClearRoomData();
}
public void SetRoomData(int roomID)
{
RoomID = roomID;
}
public void ClearRoomData()
{
RoomID = -1;
}
}
public class ClientManager
{
private List<ClientInfo> ClientList = new List<ClientInfo>();

View File

@ -1,4 +1,5 @@
using AxibugEmuOnline.Server.Common;
using AxibugEmuOnline.Server.Manager.Client;
using AxibugEmuOnline.Server.NetWork;
using AxibugProtobuf;
using MySql.Data.MySqlClient;

View File

@ -1,7 +1,4 @@
using AxibugProtobuf;
using static Mysqlx.Expect.Open.Types;
namespace AxibugEmuOnline.Server.Manager
namespace AxibugEmuOnline.Server.Manager
{
public class LogManager
{

View File

@ -1,5 +1,6 @@
using AxibugEmuOnline.Server.Common;
using AxibugEmuOnline.Server.Event;
using AxibugEmuOnline.Server.Manager.Client;
using AxibugEmuOnline.Server.NetWork;
using AxibugProtobuf;
using MySql.Data.MySqlClient;

View File

@ -0,0 +1,756 @@
using AxibugEmuOnline.Server.Common;
using AxibugEmuOnline.Server.Data;
using AxibugEmuOnline.Server.Manager.Client;
using AxibugProtobuf;
namespace AxibugEmuOnline.Server.Manager.Room
{
public class GameRoom : IDisposable
{
public int RoomID { get; private set; }
public int GameRomID { get; private set; }
public RomPlatformType GameRomPlatformType { get; private set; }
public string RomHash { get; private set; }
public long HostUID { get; private set; }
public long ScreenProviderUID { get; private set; }
public GameRoomSlot[] PlayerSlot { get; private set; }
public long Player1_UID => PlayerSlot[0].UID;
public long Player2_UID => PlayerSlot[1].UID;
public long Player3_UID => PlayerSlot[2].UID;
public long Player4_UID => PlayerSlot[3].UID;
public Google.Protobuf.ByteString? NextStateRaw { get; private set; }
public Google.Protobuf.ByteString? ScreenRaw { get; private set; }
//public bool[] PlayerReadyState { get; private set; }
public List<long> SynUIDs;
//public RoomPlayerState PlayerState => getPlayerState();
private RoomGameState mGameState;
public uint mCurrServerFrameId = 0;
public ServerInputSnapShot mCurrInputData;
public Queue<(uint, ServerInputSnapShot)> mInputQueue;
public List<double> send2time;
const int SynLimitOnSec = 61;
Lock synInputLock = new Lock();
//TODO
//public Dictionary<int, Queue<byte[]>> mDictPlayerIdx2SendQueue;
public RoomGameState GameState
{
get { return mGameState; }
set
{
if (mGameState != value)
{
mGameState = value;
switch (value)
{
case RoomGameState.WaitRawUpdate:
NextStateRaw = null;
break;
case RoomGameState.WaitReady:
ClearAllSlotReadyState();//清理玩家所有准备状态
break;
}
}
}
}
/// <summary>
/// 服务器提前帧数
/// </summary>
public uint SrvForwardFrames { get; set; }
public void Init(int roomID, int gameRomID, string roomHash, long hostUId, bool bloadState = false, RomPlatformType ptype = default)
{
Dispose();
RoomID = roomID;
GameRomID = gameRomID;
GameRomPlatformType = ptype;
RomHash = roomHash;
HostUID = hostUId;
ScreenProviderUID = hostUId;
if (PlayerSlot == null)
{
PlayerSlot = new GameRoomSlot[4];
for (uint i = 0; i < PlayerSlot.Length; i++)
PlayerSlot[i] = new GameRoomSlot();
}
for (uint i = 0; i < PlayerSlot.Length; i++)
PlayerSlot[i].Init(i);
//PlayerReadyState = new bool[4];
SynUIDs = ObjectPoolAuto.AcquireList<long>();//new List<long>();//广播角色列表
GameState = RoomGameState.NoneGameState;
mCurrInputData = new ServerInputSnapShot();
mInputQueue = ObjectPoolAuto.AcquireQueue<(uint, ServerInputSnapShot)>();
// new Queue<(uint, ServerInputSnapShot)>();
//mDictPlayerIdx2SendQueue = new Dictionary<int, Queue<byte[]>>();
send2time = ObjectPoolAuto.AcquireList<double>();
}
/// <summary>
/// 房间释放时,需要调用
/// </summary>
public void Dispose()
{
if (SynUIDs != null)
{
ObjectPoolAuto.Release(SynUIDs);
SynUIDs = null;
}
if (mInputQueue != null)
{
ObjectPoolAuto.Release(mInputQueue);
mInputQueue = null;
}
if (send2time != null)
{
ObjectPoolAuto.Release(send2time);
send2time = null;
}
}
public bool GetSlotDataByUID(long uid, out Dictionary<uint, uint> slotIdx2JoyIdx)
{
slotIdx2JoyIdx = new Dictionary<uint, uint>();
var dataarr = PlayerSlot.Where(w => w.UID == uid).ToArray();
foreach (var slot in dataarr)
slotIdx2JoyIdx[slot.SlotIdx] = slot.LocalJoyIdx;
return slotIdx2JoyIdx.Count > 0;
}
/// <summary>
/// 按照SlotIdx设置Input
/// </summary>
/// <param name="slotIdx"></param>
void SetInputDataBySlotIdx(uint slotIdx, ServerInputSnapShot data)
{
ushort val = 0;
switch (GameRomPlatformType)
{
case RomPlatformType.Cps1:
case RomPlatformType.Cps2:
case RomPlatformType.Neogeo:
case RomPlatformType.Igs:
case RomPlatformType.ArcadeOld:
{
switch (slotIdx)
{
case 0: val = data.p1_ushort; break;
case 1: val = data.p2_ushort; break;
case 2: val = data.p3_ushort; break;
case 4: val = data.p4_ushort; break;
}
//ushort 类型作为单个玩家操作
switch (slotIdx)
{
case 0: mCurrInputData.p1_ushort = val; break;
case 1: mCurrInputData.p2_ushort = val; break;
case 2: mCurrInputData.p3_ushort = val; break;
case 4: mCurrInputData.p3_ushort = val; break;
}
}
break;
default:
{
switch (slotIdx)
{
case 0: val = data.p1_byte; break;
case 1: val = data.p2_byte; break;
case 2: val = data.p3_byte; break;
case 4: val = data.p4_byte; break;
}
//byte 类型作为单个玩家操作
switch (slotIdx)
{
case 0: mCurrInputData.p1_byte = (byte)val; break;
case 1: mCurrInputData.p2_byte = (byte)val; break;
case 2: mCurrInputData.p3_byte = (byte)val; break;
case 4: mCurrInputData.p3_byte = (byte)val; break;
}
}
break;
}
}
/// <summary>
/// 按照UID清理SlotData
/// </summary>
/// <param name="uid"></param>
void ClearSlotDataByUid(long uid)
{
var dataarr = PlayerSlot.Where(w => w.UID == uid).ToArray();
foreach (var slot in dataarr)
{
slot.Init(slot.SlotIdx);
ClearInputDataBySlotIdx(slot.SlotIdx);
}
}
/// <summary>
/// 按照SlotIdx清理SlotData
/// </summary>
/// <param name="slotIdx"></param>
void ClearSlotDataBySlotIdx(uint slotIdx)
{
PlayerSlot[slotIdx].Init(slotIdx);
ClearInputDataBySlotIdx(slotIdx);
}
/// <summary>
/// 按照SlotIdx清理Input
/// </summary>
/// <param name="slotIdx"></param>
void ClearInputDataBySlotIdx(uint slotIdx)
{
switch (GameRomPlatformType)
{
case RomPlatformType.Cps1:
case RomPlatformType.Cps2:
case RomPlatformType.Neogeo:
case RomPlatformType.Igs:
case RomPlatformType.ArcadeOld:
{
//ushort 类型作为单个玩家操作
switch (slotIdx)
{
case 0: mCurrInputData.p1_ushort = 0; break;
case 1: mCurrInputData.p2_ushort = 0; break;
case 2: mCurrInputData.p3_ushort = 0; break;
case 4: mCurrInputData.p4_ushort = 0; break;
}
}
break;
default:
{
//byte 类型作为单个玩家操作
switch (slotIdx)
{
case 0: mCurrInputData.p1_byte = 0; break;
case 1: mCurrInputData.p2_byte = 0; break;
case 2: mCurrInputData.p3_byte = 0; break;
case 4: mCurrInputData.p3_byte = 0; break;
}
}
break;
}
}
/// <summary>
/// 更新同步名单
/// </summary>
void UpdateSynUIDs()
{
for (int i = SynUIDs.Count - 1; i >= 0; i--)
{
long uid = SynUIDs[i];
bool bHad = false;
if (Player1_UID == uid) bHad = true;
else if (Player2_UID == uid) bHad = true;
else if (Player3_UID == uid) bHad = true;
else if (Player4_UID == uid) bHad = true;
if (!bHad)
SynUIDs.RemoveAt(i);
}
if (Player1_UID > 0 && !SynUIDs.Contains(Player1_UID)) SynUIDs.Add(Player1_UID);
if (Player2_UID > 0 && !SynUIDs.Contains(Player2_UID)) SynUIDs.Add(Player2_UID);
if (Player3_UID > 0 && !SynUIDs.Contains(Player3_UID)) SynUIDs.Add(Player3_UID);
if (Player4_UID > 0 && !SynUIDs.Contains(Player4_UID)) SynUIDs.Add(Player4_UID);
}
#region
bool IsAllReady()
{
bool Ready = true;
if (
(Player1_UID > 0 && !PlayerSlot[0].Ready)
||
(Player2_UID > 0 && !PlayerSlot[1].Ready)
||
(Player3_UID > 0 && !PlayerSlot[2].Ready)
||
(Player4_UID > 0 && !PlayerSlot[3].Ready)
)
{
Ready = false;
}
return Ready;
}
/// <summary>
/// 清除所有槽位准备状态
/// </summary>
void ClearAllSlotReadyState()
{
for (var i = 0; i < PlayerSlot.Length; i++)
{
PlayerSlot[i].Ready = false;
}
}
/// <summary>
/// 按照UID设置Ready信息
/// </summary>
/// <param name="uid"></param>
void SetReadyByUid(long uid)
{
for (var i = 0; i < PlayerSlot.Length; i++)
{
if (PlayerSlot[i].UID == uid)
PlayerSlot[i].Ready = true;
}
}
#endregion
public void SetPlayerSlotData(ClientInfo _c, ref readonly Dictionary<uint, (uint, GamePadType)> newSlotIdx2JoyIdx)
{
GetSlotDataByUID(_c.UID, out Dictionary<uint, uint> oldSlotIdx2JoyIdx);
HashSet<uint> diffSlotIdxs = ObjectPoolAuto.AcquireSet<uint>();// new HashSet<uint>();
foreach (var old in oldSlotIdx2JoyIdx)
{
uint old_slotIdx = old.Key;
//如果旧位置已经不存在于新位置则需要算作diff
if (!newSlotIdx2JoyIdx.ContainsKey(old_slotIdx))
{
diffSlotIdxs.Add(old_slotIdx); continue;
}
uint old_slotjoyIdx = old.Value;
//如果旧位置不变但客户端本地JoyIdx变化则算作diff
if (old_slotjoyIdx != newSlotIdx2JoyIdx[old_slotIdx].Item1)
{
diffSlotIdxs.Add(old_slotIdx); continue;
}
}
//如果是在旧数据中不存在的位置则算作diff
foreach (var newdata in newSlotIdx2JoyIdx)
{
uint new_slotIdx = newdata.Key;
if (!oldSlotIdx2JoyIdx.ContainsKey(new_slotIdx))
{
diffSlotIdxs.Add(new_slotIdx); continue;
}
}
//必要的diff slot 清理键值数据
foreach (var diffSlotIdx in diffSlotIdxs)
{
ClearSlotDataBySlotIdx(diffSlotIdx);
}
//设置新的槽位
foreach (var slotdata in newSlotIdx2JoyIdx)
{
PlayerSlot[slotdata.Key].LocalJoyIdx = slotdata.Value.Item1;
PlayerSlot[slotdata.Key].LocalGamePadType = slotdata.Value.Item2;
PlayerSlot[slotdata.Key].UID = _c.UID;
AppSrv.g_Log.DebugCmd($"SetPlayerSlot RoomID->{RoomID} _c.UID->{_c.UID} PlayerSlotIdx->{slotdata.Key} LocalJoyIdx->{slotdata.Value}");
}
//更新需要同步的UID
UpdateSynUIDs();
_c.RoomState.SetRoomData(this.RoomID);
ObjectPoolAuto.Release(diffSlotIdxs);
}
public void RemovePlayer(ClientInfo _c)
{
ClearSlotDataByUid(_c.UID);
UpdateSynUIDs();
_c.RoomState.ClearRoomData();
}
public bool GetPlayerUIDByIdx(uint Idx, out long UID)
{
switch (Idx)
{
case 0: UID = Player1_UID; break;
case 1: UID = Player2_UID; break;
case 2: UID = Player3_UID; break;
case 3: UID = Player4_UID; break;
default: UID = -1; break;
}
return UID > 0;
}
public bool GetFreeSlot(out uint SlotIdx)
{
for (uint i = 0; i < PlayerSlot.Length; i++)
{
if (PlayerSlot[i].UID < 0)
{
SlotIdx = i;
return true;
}
}
SlotIdx = 0;
return false;
}
public bool GetPlayerClientByIdx(uint Idx, out ClientInfo _c)
{
_c = null;
if (!GetPlayerUIDByIdx(Idx, out long UID))
return false;
if (!AppSrv.g_ClientMgr.GetClientByUID(UID, out _c))
return false;
return true;
}
public void GetAllPlayerClientList(ref List<ClientInfo> list)
{
List<long> Uids = SynUIDs;
foreach (long uid in Uids)
{
if (!AppSrv.g_ClientMgr.GetClientByUID(uid, out ClientInfo _c, true))
continue;
list.Add(_c);
}
}
void SetInputBySlotIdxJoyIdx(uint SlotIdx, uint LocalJoyIdx, ServerInputSnapShot clieninput)
{
switch (LocalJoyIdx)
{
case 0: SetInputDataBySlotIdx(SlotIdx, clieninput); break;
case 1: SetInputDataBySlotIdx(SlotIdx, clieninput); break;
case 2: SetInputDataBySlotIdx(SlotIdx, clieninput); break;
case 3: SetInputDataBySlotIdx(SlotIdx, clieninput); break;
}
}
public int GetPlayerCount()
{
return SynUIDs.Count;
}
public void UpdateRoomForwardNum()
{
List<ClientInfo> playerlist = ObjectPoolAuto.AcquireList<ClientInfo>();
GetAllPlayerClientList(ref playerlist);
double maxNetDelay = 0;
for (int i = 0; i < playerlist.Count; i++)
{
ClientInfo player = playerlist[i];
maxNetDelay = Math.Max(maxNetDelay, player.AveNetDelay);
}
float MustTaskFrame = 1;
SrvForwardFrames = (uint)((maxNetDelay / 0.016f) + MustTaskFrame);
if (SrvForwardFrames < 2)
SrvForwardFrames = 2;
//AppSrv.g_Log.Debug($"服务器提前跑帧数Max(2,({maxNetDelay} / {0.016f}) + {MustTaskFrame}) = {SrvForwardFrames}");
ObjectPoolAuto.Release(playerlist);
}
#region
void StartNewTick()
{
mInputQueue.Clear();
//mDictPlayerIdx2SendQueue.Clear();
mCurrServerFrameId = 0;
//??????????=>>mCurrInputData.all = 1;
mCurrInputData.all = 0;
UpdateRoomForwardNum();
uint StartForwardFrames = (SrvForwardFrames * 2) + 5;
StartForwardFrames = Math.Max(10, StartForwardFrames);
//服务器提前跑帧数
for (int i = 0; i < StartForwardFrames; i++)
TakeFrame();
AppSrv.g_Log.Info($"房间初始提前量=>{StartForwardFrames},当前延迟提前量=>{SrvForwardFrames}");
}
public void TakeFrame()
{
lock (synInputLock)
{
mInputQueue.Enqueue((mCurrServerFrameId, mCurrInputData));
mCurrServerFrameId++;
if (mCurrServerFrameId % 60 == 0)
{
UpdateRoomForwardNum();
}
}
}
#endregion
ulong LastTestSend = 0;
internal ulong LastTestRecv;
/// <summary>
/// 广播数据
/// </summary>
public void SynInputData()
{
List<(uint frameId, ServerInputSnapShot inputdata)> temp = new List<(uint frameId, ServerInputSnapShot inputdata)>();
bool flagInitList = false;
lock (synInputLock)
{
#region
//double timeNow = AppSrv.g_Tick.timeNow;
//while (mInputQueue.Count > 0)
//{
// if (send2time.Count >= SynLimitOnSec)
// {
// //AppSrv.g_Log.Info($"{timeNow} - {send2time[0]} =>{timeNow - send2time[0]}");
// if (timeNow - send2time[0] < 1f) //最早的历史发送还在一秒之内
// break;
// else
// send2time.RemoveAt(0);
// }
// if (!flagInitList)
// {
// flagInitList = true;
// //temp = new List<(uint frameId, ServerInputSnapShot inputdata)>();
// temp = ObjectPoolAuto.AcquireList<(uint frameId, ServerInputSnapShot inputdata)>();
// }
// temp.Add(mInputQueue.Dequeue());
// send2time.Add(timeNow);
//}
//第二种限制速率办法
//int SendCount = 0; ;
//while (mInputQueue.Count > 0)
//{
// SendCount++;
// temp.Add(mInputQueue.Dequeue());
// if (SendCount >= SynLimitOnSec)
// {
// AppSrv.g_Log.Debug($"outSide SendCount=>{SendCount},morequeue.count->{mInputQueue.Count}");
// break;
// }
//}
#endregion
int SendCount = 0; ;
while (mInputQueue.Count > 0)
{
SendCount++;
temp.Add(mInputQueue.Dequeue());
if (SendCount >= SynLimitOnSec)
{
AppSrv.g_Log.Debug($"outSide SendCount=>{SendCount},morequeue.count->{mInputQueue.Count}");
break;
}
}
}
//if (!flagInitList)
// return;
for (int i = 0; i < temp.Count; i++)
{
(uint frameId, ServerInputSnapShot inputdata) data = temp[i];
Protobuf_Room_Syn_RoomFrameAllInputData resp = new Protobuf_Room_Syn_RoomFrameAllInputData()
{
FrameID = data.frameId,
InputData = data.inputdata.all,
ServerFrameID = mCurrServerFrameId,
ServerForwardCount = this.SrvForwardFrames
};
AppSrv.g_ClientMgr.ClientSend(SynUIDs, (int)CommandID.CmdRoomSynPlayerInput, (int)ErrorCode.ErrorOk, ProtoBufHelper.Serizlize(resp));
//if (LastTestSend != data.inputdata.all)
//{
// LastTestSend = data.inputdata.all;
// AppSrv.g_Log.Debug($" {DateTime.Now.ToString("hh:mm:ss.fff")} SynInput=> RoomID->{RoomID} ServerFrameID->{mCurrServerFrameId} SynUIDs=>{string.Join(",", SynUIDs)} ");
//}
}
ObjectPoolAuto.Release(temp);
}
bool CheckRoomStateChange(int oldPlayerCount, int newPlayerCount)
{
bool bChanged = false;
bool bNewToOnlyHost = (oldPlayerCount != 1 && newPlayerCount == 1);
bool bMorePlayer = (oldPlayerCount < 2 && newPlayerCount >= 2) || (newPlayerCount > oldPlayerCount);
switch (this.GameState)
{
case RoomGameState.NoneGameState:
if (bNewToOnlyHost)
{
this.GameState = RoomGameState.OnlyHost;
bChanged = true;
}
break;
case RoomGameState.OnlyHost:
if (bMorePlayer)//加入更多玩家
{
this.GameState = RoomGameState.WaitRawUpdate;
bChanged = true;
break;
}
break;
case RoomGameState.WaitRawUpdate:
if (bMorePlayer)//加入更多玩家
{
this.GameState = RoomGameState.WaitRawUpdate;
bChanged = true;
break;
}
if (NextStateRaw != null)//已经上传即时存档
{
this.GameState = RoomGameState.WaitReady;
bChanged = true;
break;
}
break;
case RoomGameState.WaitReady:
if (bMorePlayer)//加入更多玩家
{
this.GameState = RoomGameState.WaitRawUpdate;
bChanged = true;
break;
}
//没有未准备的
bool bAllReady = IsAllReady();
if (bAllReady)
{
this.GameState = RoomGameState.InOnlineGame;
//新开Tick
StartNewTick();
bChanged = true;
break;
}
break;
case RoomGameState.Pause:
if (bMorePlayer)//加入更多玩家
{
this.GameState = RoomGameState.WaitRawUpdate;
bChanged = true;
break;
}
break;
case RoomGameState.InOnlineGame:
if (bMorePlayer)//加入更多玩家
{
this.GameState = RoomGameState.WaitRawUpdate;
bChanged = true;
break;
}
break;
}
//房主离线,自动选择延迟最低另一名玩家做房主
if (!GetSlotDataByUID(this.HostUID, out Dictionary<uint, uint> slotIdx2JoyIdx))
{
List<ClientInfo> userlist = ObjectPoolAuto.AcquireList<ClientInfo>();
GetAllPlayerClientList(ref userlist);
if (userlist.Count > 0)
{
ClientInfo? client = userlist.OrderBy(w => w.AveNetDelay).FirstOrDefault();
this.HostUID = client.UID;
AppSrv.g_Log.DebugCmd($"更换房主为{this.HostUID}");
bChanged = true;
}
ObjectPoolAuto.Release(userlist);
}
if (this.GameState > RoomGameState.OnlyHost && newPlayerCount == 1)
{
this.GameState = RoomGameState.OnlyHost;
AppSrv.g_Log.DebugCmd("回到OnlyHost状态");
bChanged = true;
}
return bChanged;
}
#region
#region
/// <summary>
/// 进入房间
/// </summary>
/// <param name="RoomID"></param>
/// <param name="PlayerNum"></param>
/// <param name="_c"></param>
/// <param name="errcode"></param>
/// <returns></returns>
public bool Join(uint slotIdx, uint joyIdx, ClientInfo _c, out ErrorCode errcode, out bool bHadRoomStateChange)
{
bHadRoomStateChange = false;
int oldPlayerCount = GetPlayerCount();
if (GetPlayerUIDByIdx(slotIdx, out long hadUID))
{
errcode = ErrorCode.ErrorRoomSlotAlreadlyHadPlayer;
return false;
}
AppSrv.g_Log.Debug($"Join _c.UID->{_c.UID} RoomID->{RoomID}");
Dictionary<uint, (uint, GamePadType)> slotInfo = new Dictionary<uint, (uint, GamePadType)>();
slotInfo[slotIdx] = (joyIdx, GamePadType.GlobalGamePad);
SetPlayerSlotData(_c, ref slotInfo);
int newPlayerCount = GetPlayerCount();
errcode = ErrorCode.ErrorOk;
bHadRoomStateChange = CheckRoomStateChange(oldPlayerCount, newPlayerCount);
return true;
}
/// <summary>
/// 离开房间
/// </summary>
/// <param name="RoomID"></param>
/// <param name="_c"></param>
/// <param name="errcode"></param>
/// <returns></returns>
public bool Leave(ClientInfo _c, out ErrorCode errcode, out bool bHadRoomStateChange)
{
int oldPlayerCount = GetPlayerCount();
RemovePlayer(_c);
int newPlayerCount = GetPlayerCount();
errcode = ErrorCode.ErrorOk;
bHadRoomStateChange = CheckRoomStateChange(oldPlayerCount, newPlayerCount);
return true;
}
#endregion
public void SetPlayerInput(long UID, uint FrameID, ServerInputSnapShot clieninput)
{
for (uint i = 0; i < PlayerSlot.Count(); i++)
{
GameRoomSlot slotData = PlayerSlot[i];
if (slotData.UID != UID)
continue;
SetInputBySlotIdxJoyIdx(slotData.SlotIdx, slotData.LocalJoyIdx, clieninput);
}
}
public bool SetRePlayerReady(long UID, out ErrorCode errcode, out bool bHadRoomStateChange)
{
int oldPlayerCount = GetPlayerCount();
SetReadyByUid(UID);
int newPlayerCount = GetPlayerCount();
errcode = ErrorCode.ErrorOk;
bHadRoomStateChange = CheckRoomStateChange(oldPlayerCount, newPlayerCount);
return true;
}
public void SetLoadRaw(Google.Protobuf.ByteString NextStateRaw, out bool bHadRoomStateChange)
{
int oldPlayerCount = GetPlayerCount();
AppSrv.g_Log.Debug($"SetLoadRaw proto Lenght->{NextStateRaw.Length}");
this.NextStateRaw = NextStateRaw;
int newPlayerCount = GetPlayerCount();
bHadRoomStateChange = CheckRoomStateChange(oldPlayerCount, newPlayerCount);
}
public void InputScreenData(Google.Protobuf.ByteString screenRaw)
{
this.ScreenRaw = NextStateRaw;
}
#region
public bool GetNeedForwardTick(uint clientFrame, out long forwaFrame)
{
forwaFrame = 0;
//目标帧,客户端+服务器提前量
long targetFrame = clientFrame + SrvForwardFrames;
if (targetFrame > mCurrServerFrameId)//更靠前
forwaFrame = targetFrame - mCurrServerFrameId;
return forwaFrame > 0;
}
#endregion
#endregion
}
}

View File

@ -0,0 +1,20 @@
using AxibugProtobuf;
namespace AxibugEmuOnline.Server.Manager.Room
{
public class GameRoomSlot
{
public uint SlotIdx { get; set; }
public long UID { get; set; }
public uint LocalJoyIdx { get; set; }
public GamePadType LocalGamePadType { get; set; }
public bool Ready = false;
public void Init(uint SlotIdx)
{
this.SlotIdx = SlotIdx;
UID = -1;
LocalJoyIdx = 0;
Ready = false;
}
}
}

View File

@ -1,21 +1,18 @@
using AxibugEmuOnline.Server.Common;
using AxibugEmuOnline.Server.Data;
using AxibugEmuOnline.Server.Manager;
using AxibugEmuOnline.Server.Manager.Client;
using AxibugEmuOnline.Server.Manager.Room;
using AxibugEmuOnline.Server.NetWork;
using AxibugProtobuf;
using MySql.Data.MySqlClient;
using Org.BouncyCastle.Crypto.Parameters;
using System.Collections.Generic;
using System.Data;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Security.Policy;
namespace AxibugEmuOnline.Server
{
public class RoomManager
{
Dictionary<int, Data_RoomData> mDictRoom = new Dictionary<int, Data_RoomData>();
Dictionary<int, GameRoom> mDictRoom = new Dictionary<int, GameRoom>();
List<int> mKeyRoomList = new List<int>();
AutoResetEvent roomTickARE;
Thread threadRoomTick;
@ -53,7 +50,7 @@ namespace AxibugEmuOnline.Server
return RoomIDSeed++;
}
void AddRoom(Data_RoomData data)
void AddRoom(GameRoom data)
{
lock (mDictRoom)
{
@ -79,14 +76,14 @@ namespace AxibugEmuOnline.Server
}
}
public Data_RoomData GetRoomData(int RoomID)
public GameRoom GetRoomData(int RoomID)
{
if (!mDictRoom.TryGetValue(RoomID, out Data_RoomData data))
if (!mDictRoom.TryGetValue(RoomID, out GameRoom data))
return null;
return data;
}
public void GetRoomList(ref List<Data_RoomData> roomList)
public void GetRoomList(ref List<GameRoom> roomList)
{
lock (mDictRoom)
{
@ -136,7 +133,7 @@ namespace AxibugEmuOnline.Server
}
#endregion
private Protobuf_Room_MiniInfo GetProtoDataRoom(Data_RoomData room)
private Protobuf_Room_MiniInfo GetProtoDataRoom(GameRoom room)
{
Protobuf_Room_MiniInfo result = new Protobuf_Room_MiniInfo()
{
@ -153,7 +150,7 @@ namespace AxibugEmuOnline.Server
for (byte i = 0; i < room.PlayerSlot.Count(); i++)
{
Protobuf_Room_GamePlaySlot pbSlot = new Protobuf_Room_GamePlaySlot();
Data_RoomSlot slot = room.PlayerSlot[i];
GameRoomSlot slot = room.PlayerSlot[i];
if (slot.UID > 0)
{
pbSlot.PlayerUID = slot.UID;
@ -178,7 +175,7 @@ namespace AxibugEmuOnline.Server
Protobuf_Room_List msg = ProtoBufHelper.DeSerizlize<Protobuf_Room_List>(reqData);
Protobuf_Room_List_RESP resp = new Protobuf_Room_List_RESP();
List<Data_RoomData> temp = ObjectPoolAuto.AcquireList<Data_RoomData>();
List<GameRoom> temp = ObjectPoolAuto.AcquireList<GameRoom>();
GetRoomList(ref temp);
foreach (var room in temp)
resp.RoomMiniInfoList.Add(GetProtoDataRoom(room));
@ -191,7 +188,7 @@ namespace AxibugEmuOnline.Server
ClientInfo _c = AppSrv.g_ClientMgr.GetClientForSocket(sk);
Protobuf_Room_Get_Screen msg = ProtoBufHelper.DeSerizlize<Protobuf_Room_Get_Screen>(reqData);
Data_RoomData room = GetRoomData(_c.RoomState.RoomID);
GameRoom room = GetRoomData(_c.RoomState.RoomID);
bool bHadRoomStateChange = false;
ErrorCode Errcode = ErrorCode.ErrorOk;
Protobuf_Room_Get_Screen_RESP resp = new Protobuf_Room_Get_Screen_RESP();
@ -212,7 +209,7 @@ namespace AxibugEmuOnline.Server
/// </summary>
/// <param name="RoomID"></param>
/// <param name="type">//[0] 更新或新增 [1] 删除</param>
public void SendRoomUpdateToAll(Data_RoomData room, int type)
public void SendRoomUpdateToAll(GameRoom room, int type)
{
if (room == null)
return;
@ -232,7 +229,7 @@ namespace AxibugEmuOnline.Server
ClientInfo _c = AppSrv.g_ClientMgr.GetClientForSocket(sk);
Protobuf_Room_Create msg = ProtoBufHelper.DeSerizlize<Protobuf_Room_Create>(reqData);
Protobuf_Room_Create_RESP resp = new Protobuf_Room_Create_RESP();
Data_RoomData newRoom = new Data_RoomData();
GameRoom newRoom = new GameRoom();
RomPlatformType ptype = AppSrv.g_GameShareMgr.GetRomPlatformType(msg.GameRomID);
newRoom.Init(GetNewRoomID(), msg.GameRomID, msg.GameRomHash, _c.UID, false, ptype);
@ -261,7 +258,7 @@ namespace AxibugEmuOnline.Server
Protobuf_Room_Join msg = ProtoBufHelper.DeSerizlize<Protobuf_Room_Join>(reqData);
Protobuf_Room_Join_RESP resp = new Protobuf_Room_Join_RESP();
ErrorCode joinErrcode;
Data_RoomData room = GetRoomData(msg.RoomID);
GameRoom room = GetRoomData(msg.RoomID);
bool bHadRoomStateChange = false;
if (room == null)
{
@ -285,7 +282,7 @@ namespace AxibugEmuOnline.Server
//加入
if (room.Join(SlotIdx, (uint)0, _c, out joinErrcode, out bHadRoomStateChange))
{
Data_RoomData roomData = GetRoomData(msg.RoomID);
GameRoom roomData = GetRoomData(msg.RoomID);
resp.RoomMiniInfo = GetProtoDataRoom(roomData);
}
@ -338,7 +335,7 @@ namespace AxibugEmuOnline.Server
return;
Protobuf_Room_Leave_RESP resp = new Protobuf_Room_Leave_RESP();
ErrorCode errcode;
Data_RoomData room = GetRoomData(_c.RoomState.RoomID);
GameRoom room = GetRoomData(_c.RoomState.RoomID);
bool bHadRoomStateChange = false;
if (room == null)
{
@ -375,7 +372,7 @@ namespace AxibugEmuOnline.Server
Protobuf_Room_Change_PlaySlotWithJoy msg = ProtoBufHelper.DeSerizlize<Protobuf_Room_Change_PlaySlotWithJoy>(reqData);
Protobuf_Room_Change_PlaySlotWithJoy_RESP resp = new Protobuf_Room_Change_PlaySlotWithJoy_RESP();
ErrorCode errcode = ErrorCode.ErrorOk;
Data_RoomData room = GetRoomData(_c.RoomState.RoomID);
GameRoom room = GetRoomData(_c.RoomState.RoomID);
if (room == null)
{
errcode = ErrorCode.ErrorRoomNotFound;
@ -413,7 +410,7 @@ namespace AxibugEmuOnline.Server
Protobuf_Room_HostPlayer_UpdateStateRaw msg = ProtoBufHelper.DeSerizlize<Protobuf_Room_HostPlayer_UpdateStateRaw>(reqData);
Protobuf_Room_HostPlayer_UpdateStateRaw_RESP resp = new Protobuf_Room_HostPlayer_UpdateStateRaw_RESP();
ErrorCode errcode = ErrorCode.ErrorOk;
Data_RoomData room = GetRoomData(_c.RoomState.RoomID);
GameRoom room = GetRoomData(_c.RoomState.RoomID);
if (room == null)
errcode = ErrorCode.ErrorRoomNotFound;
else if (room.GameState != RoomGameState.WaitRawUpdate)
@ -435,7 +432,7 @@ namespace AxibugEmuOnline.Server
AppSrv.g_Log.DebugCmd($"OnRoomPlayerReady _c->{_c.UID}");
Protobuf_Room_Player_Ready msg = ProtoBufHelper.DeSerizlize<Protobuf_Room_Player_Ready>(reqData);
ErrorCode errcode = ErrorCode.ErrorOk;
Data_RoomData room = GetRoomData(_c.RoomState.RoomID);
GameRoom room = GetRoomData(_c.RoomState.RoomID);
if (room == null)
return;
lock (room)
@ -453,7 +450,7 @@ namespace AxibugEmuOnline.Server
{
ClientInfo _c = AppSrv.g_ClientMgr.GetClientForSocket(sk);
Protobuf_Room_SinglePlayerInputData msg = ProtoBufHelper.DeSerizlize<Protobuf_Room_SinglePlayerInputData>(reqData);
Data_RoomData room = GetRoomData(_c.RoomState.RoomID);
GameRoom room = GetRoomData(_c.RoomState.RoomID);
if (room == null)
return;
@ -496,7 +493,9 @@ namespace AxibugEmuOnline.Server
if (room.LastTestRecv != room.mCurrInputData.all)
{
room.LastTestRecv = room.mCurrInputData.all;
//AppSrv.g_Log.Debug($" {DateTime.Now.ToString("hh:mm:ss.fff")} SynTestRecv=> UID->{_c.UID} roomId->{room.mCurrServerFrameId} input->{msg.InputData}");
#if DEBUG
AppSrv.g_Log.Debug($" {DateTime.Now.ToString("hh:mm:ss.fff")} SynTestRecv=> UID->{_c.UID} roomId->{room.mCurrServerFrameId} input->{msg.InputData}");
#endif
}
}
@ -505,7 +504,7 @@ namespace AxibugEmuOnline.Server
AppSrv.g_Log.DebugCmd($"OnCmdScreen lenght:{reqData.Length}");
ClientInfo _c = AppSrv.g_ClientMgr.GetClientForSocket(sk);
Protobuf_Screnn_Frame msg = ProtoBufHelper.DeSerizlize<Protobuf_Screnn_Frame>(reqData);
Data_RoomData room = AppSrv.g_Room.GetRoomData(msg.RoomID);
GameRoom room = AppSrv.g_Room.GetRoomData(msg.RoomID);
room.InputScreenData(msg.RawBitmap);
}
@ -515,7 +514,7 @@ namespace AxibugEmuOnline.Server
/// <param name="RoomID"></param>
public void Protobuf_Room_MyRoom_State_Change(int RoomID)
{
Data_RoomData room = GetRoomData(RoomID);
GameRoom room = GetRoomData(RoomID);
if (room == null)
return;
@ -539,7 +538,7 @@ namespace AxibugEmuOnline.Server
/// 广播联机Step
/// </summary>
/// <param name="room"></param>
public void SendRoomStepChange(Data_RoomData room)
public void SendRoomStepChange(GameRoom room)
{
List<ClientInfo> roomClient = ObjectPoolAuto.AcquireList<ClientInfo>();
@ -599,7 +598,7 @@ namespace AxibugEmuOnline.Server
for (int i = 0; i < mKeyRoomList.Count; i++)
{
int roomid = mKeyRoomList[i];
if (!mDictRoom.TryGetValue(roomid, out Data_RoomData room) || room.GameState < RoomGameState.InOnlineGame)
if (!mDictRoom.TryGetValue(roomid, out GameRoom room) || room.GameState < RoomGameState.InOnlineGame)
continue;
//更新帧(服务器主动跑时用)
room.TakeFrame();
@ -610,793 +609,5 @@ namespace AxibugEmuOnline.Server
#endregion
}
public class Data_RoomData : IDisposable
{
public int RoomID { get; private set; }
public int GameRomID { get; private set; }
public RomPlatformType GameRomPlatformType { get; private set; }
public string RomHash { get; private set; }
public long HostUID { get; private set; }
public long ScreenProviderUID { get; private set; }
public Data_RoomSlot[] PlayerSlot { get; private set; }
public long Player1_UID => PlayerSlot[0].UID;
public long Player2_UID => PlayerSlot[1].UID;
public long Player3_UID => PlayerSlot[2].UID;
public long Player4_UID => PlayerSlot[3].UID;
public Google.Protobuf.ByteString? NextStateRaw { get; private set; }
public Google.Protobuf.ByteString? ScreenRaw { get; private set; }
//public bool[] PlayerReadyState { get; private set; }
public List<long> SynUIDs;
//public RoomPlayerState PlayerState => getPlayerState();
private RoomGameState mGameState;
public uint mCurrServerFrameId = 0;
public ServerInputSnapShot mCurrInputData;
public Queue<(uint, ServerInputSnapShot)> mInputQueue;
public List<double> send2time;
const int SynLimitOnSec = 61;
object synInputLock = new object();
//TODO
//public Dictionary<int, Queue<byte[]>> mDictPlayerIdx2SendQueue;
public RoomGameState GameState
{
get { return mGameState; }
set
{
if (mGameState != value)
{
mGameState = value;
switch (value)
{
case RoomGameState.WaitRawUpdate:
NextStateRaw = null;
break;
case RoomGameState.WaitReady:
ClearAllSlotReadyState();//清理玩家所有准备状态
break;
}
}
}
}
/// <summary>
/// 服务器提前帧数
/// </summary>
public uint SrvForwardFrames { get; set; }
public void Init(int roomID, int gameRomID, string roomHash, long hostUId, bool bloadState = false, RomPlatformType ptype = default)
{
Dispose();
RoomID = roomID;
GameRomID = gameRomID;
GameRomPlatformType = ptype;
RomHash = roomHash;
HostUID = hostUId;
ScreenProviderUID = hostUId;
if (PlayerSlot == null)
{
PlayerSlot = new Data_RoomSlot[4];
for (uint i = 0; i < PlayerSlot.Length; i++)
PlayerSlot[i] = new Data_RoomSlot();
}
for (uint i = 0; i < PlayerSlot.Length; i++)
PlayerSlot[i].Init(i);
//PlayerReadyState = new bool[4];
SynUIDs = ObjectPoolAuto.AcquireList<long>();//new List<long>();//广播角色列表
GameState = RoomGameState.NoneGameState;
mCurrInputData = new ServerInputSnapShot();
mInputQueue = ObjectPoolAuto.AcquireQueue<(uint, ServerInputSnapShot)>();
// new Queue<(uint, ServerInputSnapShot)>();
//mDictPlayerIdx2SendQueue = new Dictionary<int, Queue<byte[]>>();
send2time = ObjectPoolAuto.AcquireList<double>();
}
/// <summary>
/// 房间释放时,需要调用
/// </summary>
public void Dispose()
{
if (SynUIDs != null)
{
ObjectPoolAuto.Release(SynUIDs);
SynUIDs = null;
}
if (mInputQueue != null)
{
ObjectPoolAuto.Release(mInputQueue);
mInputQueue = null;
}
if (send2time != null)
{
ObjectPoolAuto.Release(send2time);
send2time = null;
}
}
public bool GetSlotDataByUID(long uid, out Dictionary<uint, uint> slotIdx2JoyIdx)
{
slotIdx2JoyIdx = new Dictionary<uint, uint>();
var dataarr = PlayerSlot.Where(w => w.UID == uid).ToArray();
foreach (var slot in dataarr)
slotIdx2JoyIdx[slot.SlotIdx] = slot.LocalJoyIdx;
return slotIdx2JoyIdx.Count > 0;
}
/// <summary>
/// 按照SlotIdx设置Input
/// </summary>
/// <param name="slotIdx"></param>
void SetInputDataBySlotIdx(uint slotIdx, ServerInputSnapShot data)
{
ushort val = 0;
switch (GameRomPlatformType)
{
case RomPlatformType.Cps1:
case RomPlatformType.Cps2:
case RomPlatformType.Neogeo:
case RomPlatformType.Igs:
case RomPlatformType.ArcadeOld:
{
switch (slotIdx)
{
case 0: val = data.p1_ushort; break;
case 1: val = data.p2_ushort; break;
case 2: val = data.p3_ushort; break;
case 4: val = data.p4_ushort; break;
}
//ushort 类型作为单个玩家操作
switch (slotIdx)
{
case 0: mCurrInputData.p1_ushort = val; break;
case 1: mCurrInputData.p2_ushort = val; break;
case 2: mCurrInputData.p3_ushort = val; break;
case 4: mCurrInputData.p3_ushort = val; break;
}
}
break;
default:
{
switch (slotIdx)
{
case 0: val = data.p1_byte; break;
case 1: val = data.p2_byte; break;
case 2: val = data.p3_byte; break;
case 4: val = data.p4_byte; break;
}
//byte 类型作为单个玩家操作
switch (slotIdx)
{
case 0: mCurrInputData.p1_byte = (byte)val; break;
case 1: mCurrInputData.p2_byte = (byte)val; break;
case 2: mCurrInputData.p3_byte = (byte)val; break;
case 4: mCurrInputData.p3_byte = (byte)val; break;
}
}
break;
}
}
/// <summary>
/// 按照UID清理SlotData
/// </summary>
/// <param name="uid"></param>
void ClearSlotDataByUid(long uid)
{
var dataarr = PlayerSlot.Where(w => w.UID == uid).ToArray();
foreach (var slot in dataarr)
{
slot.Init(slot.SlotIdx);
ClearInputDataBySlotIdx(slot.SlotIdx);
}
}
/// <summary>
/// 按照SlotIdx清理SlotData
/// </summary>
/// <param name="slotIdx"></param>
void ClearSlotDataBySlotIdx(uint slotIdx)
{
PlayerSlot[slotIdx].Init(slotIdx);
ClearInputDataBySlotIdx(slotIdx);
}
/// <summary>
/// 按照SlotIdx清理Input
/// </summary>
/// <param name="slotIdx"></param>
void ClearInputDataBySlotIdx(uint slotIdx)
{
switch (GameRomPlatformType)
{
case RomPlatformType.Cps1:
case RomPlatformType.Cps2:
case RomPlatformType.Neogeo:
case RomPlatformType.Igs:
case RomPlatformType.ArcadeOld:
{
//ushort 类型作为单个玩家操作
switch (slotIdx)
{
case 0: mCurrInputData.p1_ushort = 0; break;
case 1: mCurrInputData.p2_ushort = 0; break;
case 2: mCurrInputData.p3_ushort = 0; break;
case 4: mCurrInputData.p4_ushort = 0; break;
}
}
break;
default:
{
//byte 类型作为单个玩家操作
switch (slotIdx)
{
case 0: mCurrInputData.p1_byte = 0; break;
case 1: mCurrInputData.p2_byte = 0; break;
case 2: mCurrInputData.p3_byte = 0; break;
case 4: mCurrInputData.p3_byte = 0; break;
}
}
break;
}
}
/// <summary>
/// 更新同步名单
/// </summary>
void UpdateSynUIDs()
{
for (int i = SynUIDs.Count - 1; i >= 0; i--)
{
long uid = SynUIDs[i];
bool bHad = false;
if (Player1_UID == uid) bHad = true;
else if (Player2_UID == uid) bHad = true;
else if (Player3_UID == uid) bHad = true;
else if (Player4_UID == uid) bHad = true;
if (!bHad)
SynUIDs.RemoveAt(i);
}
if (Player1_UID > 0 && !SynUIDs.Contains(Player1_UID)) SynUIDs.Add(Player1_UID);
if (Player2_UID > 0 && !SynUIDs.Contains(Player2_UID)) SynUIDs.Add(Player2_UID);
if (Player3_UID > 0 && !SynUIDs.Contains(Player3_UID)) SynUIDs.Add(Player3_UID);
if (Player4_UID > 0 && !SynUIDs.Contains(Player4_UID)) SynUIDs.Add(Player4_UID);
}
#region
bool IsAllReady()
{
bool Ready = true;
if (
(Player1_UID > 0 && !PlayerSlot[0].Ready)
||
(Player2_UID > 0 && !PlayerSlot[1].Ready)
||
(Player3_UID > 0 && !PlayerSlot[2].Ready)
||
(Player4_UID > 0 && !PlayerSlot[3].Ready)
)
{
Ready = false;
}
return Ready;
}
/// <summary>
/// 清除所有槽位准备状态
/// </summary>
void ClearAllSlotReadyState()
{
for (var i = 0; i < PlayerSlot.Length; i++)
{
PlayerSlot[i].Ready = false;
}
}
/// <summary>
/// 按照UID设置Ready信息
/// </summary>
/// <param name="uid"></param>
void SetReadyByUid(long uid)
{
for (var i = 0; i < PlayerSlot.Length; i++)
{
if (PlayerSlot[i].UID == uid)
PlayerSlot[i].Ready = true;
}
}
#endregion
public void SetPlayerSlotData(ClientInfo _c, ref readonly Dictionary<uint, (uint, GamePadType)> newSlotIdx2JoyIdx)
{
GetSlotDataByUID(_c.UID, out Dictionary<uint, uint> oldSlotIdx2JoyIdx);
HashSet<uint> diffSlotIdxs = ObjectPoolAuto.AcquireSet<uint>();// new HashSet<uint>();
foreach (var old in oldSlotIdx2JoyIdx)
{
uint old_slotIdx = old.Key;
//如果旧位置已经不存在于新位置则需要算作diff
if (!newSlotIdx2JoyIdx.ContainsKey(old_slotIdx))
{
diffSlotIdxs.Add(old_slotIdx); continue;
}
uint old_slotjoyIdx = old.Value;
//如果旧位置不变但客户端本地JoyIdx变化则算作diff
if (old_slotjoyIdx != newSlotIdx2JoyIdx[old_slotIdx].Item1)
{
diffSlotIdxs.Add(old_slotIdx); continue;
}
}
//如果是在旧数据中不存在的位置则算作diff
foreach (var newdata in newSlotIdx2JoyIdx)
{
uint new_slotIdx = newdata.Key;
if (!oldSlotIdx2JoyIdx.ContainsKey(new_slotIdx))
{
diffSlotIdxs.Add(new_slotIdx); continue;
}
}
//必要的diff slot 清理键值数据
foreach (var diffSlotIdx in diffSlotIdxs)
{
ClearSlotDataBySlotIdx(diffSlotIdx);
}
//设置新的槽位
foreach (var slotdata in newSlotIdx2JoyIdx)
{
PlayerSlot[slotdata.Key].LocalJoyIdx = slotdata.Value.Item1;
PlayerSlot[slotdata.Key].LocalGamePadType = slotdata.Value.Item2;
PlayerSlot[slotdata.Key].UID = _c.UID;
AppSrv.g_Log.DebugCmd($"SetPlayerSlot RoomID->{RoomID} _c.UID->{_c.UID} PlayerSlotIdx->{slotdata.Key} LocalJoyIdx->{slotdata.Value}");
}
//更新需要同步的UID
UpdateSynUIDs();
_c.RoomState.SetRoomData(this.RoomID);
ObjectPoolAuto.Release(diffSlotIdxs);
}
public void RemovePlayer(ClientInfo _c)
{
ClearSlotDataByUid(_c.UID);
UpdateSynUIDs();
_c.RoomState.ClearRoomData();
}
public bool GetPlayerUIDByIdx(uint Idx, out long UID)
{
switch (Idx)
{
case 0: UID = Player1_UID; break;
case 1: UID = Player2_UID; break;
case 2: UID = Player3_UID; break;
case 3: UID = Player4_UID; break;
default: UID = -1; break;
}
return UID > 0;
}
public bool GetFreeSlot(out uint SlotIdx)
{
for (uint i = 0; i < PlayerSlot.Length; i++)
{
if (PlayerSlot[i].UID < 0)
{
SlotIdx = i;
return true;
}
}
SlotIdx = 0;
return false;
}
public bool GetPlayerClientByIdx(uint Idx, out ClientInfo _c)
{
_c = null;
if (!GetPlayerUIDByIdx(Idx, out long UID))
return false;
if (!AppSrv.g_ClientMgr.GetClientByUID(UID, out _c))
return false;
return true;
}
public void GetAllPlayerClientList(ref List<ClientInfo> list)
{
List<long> Uids = SynUIDs;
foreach (long uid in Uids)
{
if (!AppSrv.g_ClientMgr.GetClientByUID(uid, out ClientInfo _c, true))
continue;
list.Add(_c);
}
}
void SetInputBySlotIdxJoyIdx(uint SlotIdx, uint LocalJoyIdx, ServerInputSnapShot clieninput)
{
switch (LocalJoyIdx)
{
case 0: SetInputDataBySlotIdx(SlotIdx, clieninput); break;
case 1: SetInputDataBySlotIdx(SlotIdx, clieninput); break;
case 2: SetInputDataBySlotIdx(SlotIdx, clieninput); break;
case 3: SetInputDataBySlotIdx(SlotIdx, clieninput); break;
}
}
public int GetPlayerCount()
{
return SynUIDs.Count;
}
public void UpdateRoomForwardNum()
{
List<ClientInfo> playerlist = ObjectPoolAuto.AcquireList<ClientInfo>();
GetAllPlayerClientList(ref playerlist);
double maxNetDelay = 0;
for (int i = 0; i < playerlist.Count; i++)
{
ClientInfo player = playerlist[i];
maxNetDelay = Math.Max(maxNetDelay, player.AveNetDelay);
}
float MustTaskFrame = 1;
SrvForwardFrames = (uint)((maxNetDelay / 0.016f) + MustTaskFrame);
if (SrvForwardFrames < 2)
SrvForwardFrames = 2;
//AppSrv.g_Log.Debug($"服务器提前跑帧数Max(2,({maxNetDelay} / {0.016f}) + {MustTaskFrame}) = {SrvForwardFrames}");
ObjectPoolAuto.Release(playerlist);
}
#region
void StartNewTick()
{
mInputQueue.Clear();
//mDictPlayerIdx2SendQueue.Clear();
mCurrServerFrameId = 0;
//??????????=>>mCurrInputData.all = 1;
mCurrInputData.all = 0;
UpdateRoomForwardNum();
uint StartForwardFrames = (SrvForwardFrames * 2) + 5;
StartForwardFrames = Math.Max(10, StartForwardFrames);
//服务器提前跑帧数
for (int i = 0; i < StartForwardFrames; i++)
TakeFrame();
AppSrv.g_Log.Info($"房间初始提前量=>{StartForwardFrames},当前延迟提前量=>{SrvForwardFrames}");
}
public void TakeFrame()
{
lock (synInputLock)
{
mInputQueue.Enqueue((mCurrServerFrameId, mCurrInputData));
mCurrServerFrameId++;
if (mCurrServerFrameId % 60 == 0)
{
UpdateRoomForwardNum();
}
}
}
#endregion
ulong LastTestSend = 0;
internal ulong LastTestRecv;
/// <summary>
/// 广播数据
/// </summary>
public void SynInputData()
{
List<(uint frameId, ServerInputSnapShot inputdata)> temp = new List<(uint frameId, ServerInputSnapShot inputdata)>();
bool flagInitList = false;
lock (synInputLock)
{
#region
//double timeNow = AppSrv.g_Tick.timeNow;
//while (mInputQueue.Count > 0)
//{
// if (send2time.Count >= SynLimitOnSec)
// {
// //AppSrv.g_Log.Info($"{timeNow} - {send2time[0]} =>{timeNow - send2time[0]}");
// if (timeNow - send2time[0] < 1f) //最早的历史发送还在一秒之内
// break;
// else
// send2time.RemoveAt(0);
// }
// if (!flagInitList)
// {
// flagInitList = true;
// //temp = new List<(uint frameId, ServerInputSnapShot inputdata)>();
// temp = ObjectPoolAuto.AcquireList<(uint frameId, ServerInputSnapShot inputdata)>();
// }
// temp.Add(mInputQueue.Dequeue());
// send2time.Add(timeNow);
//}
//第二种限制速率办法
//int SendCount = 0; ;
//while (mInputQueue.Count > 0)
//{
// SendCount++;
// temp.Add(mInputQueue.Dequeue());
// if (SendCount >= SynLimitOnSec)
// {
// AppSrv.g_Log.Debug($"outSide SendCount=>{SendCount},morequeue.count->{mInputQueue.Count}");
// break;
// }
//}
#endregion
int SendCount = 0; ;
while (mInputQueue.Count > 0)
{
SendCount++;
temp.Add(mInputQueue.Dequeue());
if (SendCount >= SynLimitOnSec)
{
AppSrv.g_Log.Debug($"outSide SendCount=>{SendCount},morequeue.count->{mInputQueue.Count}");
break;
}
}
}
//if (!flagInitList)
// return;
for (int i = 0; i < temp.Count; i++)
{
(uint frameId, ServerInputSnapShot inputdata) data = temp[i];
Protobuf_Room_Syn_RoomFrameAllInputData resp = new Protobuf_Room_Syn_RoomFrameAllInputData()
{
FrameID = data.frameId,
InputData = data.inputdata.all,
ServerFrameID = mCurrServerFrameId,
ServerForwardCount = this.SrvForwardFrames
};
AppSrv.g_ClientMgr.ClientSend(SynUIDs, (int)CommandID.CmdRoomSynPlayerInput, (int)ErrorCode.ErrorOk, ProtoBufHelper.Serizlize(resp));
//if (LastTestSend != data.inputdata.all)
//{
// LastTestSend = data.inputdata.all;
// AppSrv.g_Log.Debug($" {DateTime.Now.ToString("hh:mm:ss.fff")} SynInput=> RoomID->{RoomID} ServerFrameID->{mCurrServerFrameId} SynUIDs=>{string.Join(",", SynUIDs)} ");
//}
}
ObjectPoolAuto.Release(temp);
}
bool CheckRoomStateChange(int oldPlayerCount, int newPlayerCount)
{
bool bChanged = false;
bool bNewToOnlyHost = (oldPlayerCount != 1 && newPlayerCount == 1);
bool bMorePlayer = (oldPlayerCount < 2 && newPlayerCount >= 2) || (newPlayerCount > oldPlayerCount);
switch (this.GameState)
{
case RoomGameState.NoneGameState:
if (bNewToOnlyHost)
{
this.GameState = RoomGameState.OnlyHost;
bChanged = true;
}
break;
case RoomGameState.OnlyHost:
if (bMorePlayer)//加入更多玩家
{
this.GameState = RoomGameState.WaitRawUpdate;
bChanged = true;
break;
}
break;
case RoomGameState.WaitRawUpdate:
if (bMorePlayer)//加入更多玩家
{
this.GameState = RoomGameState.WaitRawUpdate;
bChanged = true;
break;
}
if (NextStateRaw != null)//已经上传即时存档
{
this.GameState = RoomGameState.WaitReady;
bChanged = true;
break;
}
break;
case RoomGameState.WaitReady:
if (bMorePlayer)//加入更多玩家
{
this.GameState = RoomGameState.WaitRawUpdate;
bChanged = true;
break;
}
//没有未准备的
bool bAllReady = IsAllReady();
if (bAllReady)
{
this.GameState = RoomGameState.InOnlineGame;
//新开Tick
StartNewTick();
bChanged = true;
break;
}
break;
case RoomGameState.Pause:
if (bMorePlayer)//加入更多玩家
{
this.GameState = RoomGameState.WaitRawUpdate;
bChanged = true;
break;
}
break;
case RoomGameState.InOnlineGame:
if (bMorePlayer)//加入更多玩家
{
this.GameState = RoomGameState.WaitRawUpdate;
bChanged = true;
break;
}
break;
}
//房主离线,自动选择延迟最低另一名玩家做房主
if (!GetSlotDataByUID(this.HostUID, out Dictionary<uint, uint> slotIdx2JoyIdx))
{
List<ClientInfo> userlist = ObjectPoolAuto.AcquireList<ClientInfo>();
GetAllPlayerClientList(ref userlist);
if (userlist.Count > 0)
{
ClientInfo? client = userlist.OrderBy(w => w.AveNetDelay).FirstOrDefault();
this.HostUID = client.UID;
AppSrv.g_Log.DebugCmd($"更换房主为{this.HostUID}");
bChanged = true;
}
ObjectPoolAuto.Release(userlist);
}
if (this.GameState > RoomGameState.OnlyHost && newPlayerCount == 1)
{
this.GameState = RoomGameState.OnlyHost;
AppSrv.g_Log.DebugCmd("回到OnlyHost状态");
bChanged = true;
}
return bChanged;
}
#region
#region
/// <summary>
/// 进入房间
/// </summary>
/// <param name="RoomID"></param>
/// <param name="PlayerNum"></param>
/// <param name="_c"></param>
/// <param name="errcode"></param>
/// <returns></returns>
public bool Join(uint slotIdx, uint joyIdx, ClientInfo _c, out ErrorCode errcode, out bool bHadRoomStateChange)
{
bHadRoomStateChange = false;
int oldPlayerCount = GetPlayerCount();
if (GetPlayerUIDByIdx(slotIdx, out long hadUID))
{
errcode = ErrorCode.ErrorRoomSlotAlreadlyHadPlayer;
return false;
}
AppSrv.g_Log.Debug($"Join _c.UID->{_c.UID} RoomID->{RoomID}");
Dictionary<uint, (uint, GamePadType)> slotInfo = new Dictionary<uint, (uint, GamePadType)>();
slotInfo[slotIdx] = (joyIdx, GamePadType.GlobalGamePad);
SetPlayerSlotData(_c, ref slotInfo);
int newPlayerCount = GetPlayerCount();
errcode = ErrorCode.ErrorOk;
bHadRoomStateChange = CheckRoomStateChange(oldPlayerCount, newPlayerCount);
return true;
}
/// <summary>
/// 离开房间
/// </summary>
/// <param name="RoomID"></param>
/// <param name="_c"></param>
/// <param name="errcode"></param>
/// <returns></returns>
public bool Leave(ClientInfo _c, out ErrorCode errcode, out bool bHadRoomStateChange)
{
int oldPlayerCount = GetPlayerCount();
RemovePlayer(_c);
int newPlayerCount = GetPlayerCount();
errcode = ErrorCode.ErrorOk;
bHadRoomStateChange = CheckRoomStateChange(oldPlayerCount, newPlayerCount);
return true;
}
#endregion
public void SetPlayerInput(long UID, uint FrameID, ServerInputSnapShot clieninput)
{
for (uint i = 0; i < PlayerSlot.Count(); i++)
{
Data_RoomSlot slotData = PlayerSlot[i];
if (slotData.UID != UID)
continue;
SetInputBySlotIdxJoyIdx(slotData.SlotIdx, slotData.LocalJoyIdx, clieninput);
}
}
public bool SetRePlayerReady(long UID, out ErrorCode errcode, out bool bHadRoomStateChange)
{
int oldPlayerCount = GetPlayerCount();
SetReadyByUid(UID);
int newPlayerCount = GetPlayerCount();
errcode = ErrorCode.ErrorOk;
bHadRoomStateChange = CheckRoomStateChange(oldPlayerCount, newPlayerCount);
return true;
}
public void SetLoadRaw(Google.Protobuf.ByteString NextStateRaw, out bool bHadRoomStateChange)
{
int oldPlayerCount = GetPlayerCount();
AppSrv.g_Log.Debug($"SetLoadRaw proto Lenght->{NextStateRaw.Length}");
this.NextStateRaw = NextStateRaw;
int newPlayerCount = GetPlayerCount();
bHadRoomStateChange = CheckRoomStateChange(oldPlayerCount, newPlayerCount);
}
public void InputScreenData(Google.Protobuf.ByteString screenRaw)
{
this.ScreenRaw = NextStateRaw;
}
#region
public bool GetNeedForwardTick(uint clientFrame, out long forwaFrame)
{
forwaFrame = 0;
//目标帧,客户端+服务器提前量
long targetFrame = clientFrame + SrvForwardFrames;
if (targetFrame > mCurrServerFrameId)//更靠前
forwaFrame = targetFrame - mCurrServerFrameId;
return forwaFrame > 0;
}
#endregion
#endregion
}
public class Data_RoomSlot
{
public uint SlotIdx { get; set; }
public long UID { get; set; }
public uint LocalJoyIdx { get; set; }
public GamePadType LocalGamePadType { get; set; }
public bool Ready = false;
public void Init(uint SlotIdx)
{
this.SlotIdx = SlotIdx;
UID = -1;
LocalJoyIdx = 0;
Ready = false;
}
}
[StructLayout(LayoutKind.Explicit, Size = 8)]
public struct ServerInputSnapShot
{
[FieldOffset(0)]
public UInt64 all;
[FieldOffset(0)]
public byte p1_byte;
[FieldOffset(1)]
public byte p2_byte;
[FieldOffset(2)]
public byte p3_byte;
[FieldOffset(3)]
public byte p4_byte;
[FieldOffset(0)]
public ushort p1_ushort;
[FieldOffset(2)]
public ushort p2_ushort;
[FieldOffset(4)]
public ushort p3_ushort;
[FieldOffset(6)]
public ushort p4_ushort;
}
}

View File

@ -1,4 +1,5 @@
using AxibugEmuOnline.Server.Common;
using AxibugEmuOnline.Server.Manager.Client;
using AxibugEmuOnline.Server.NetWork;
using AxibugProtobuf;
using MySql.Data.MySqlClient;

View File

@ -1,5 +1,6 @@
using AxibugEmuOnline.Server.Common;
using AxibugEmuOnline.Server.Event;
using AxibugEmuOnline.Server.Manager.Client;
using AxibugEmuOnline.Server.NetWork;
using AxibugProtobuf;
using System.Net.Sockets;

View File

@ -1,5 +1,7 @@
using AxibugEmuOnline.Server.Common;
using AxibugEmuOnline.Server.Manager;
using AxibugEmuOnline.Server.Manager.Client;
using AxibugEmuOnline.Server.Manager.Room;
using MySql.Data.MySqlClient;
namespace AxibugEmuOnline.Server
@ -21,7 +23,7 @@ namespace AxibugEmuOnline.Server
{
case "rlist":
{
List<Data_RoomData> roomlist = ObjectPoolAuto.AcquireList<Data_RoomData>();
List<GameRoom> roomlist = ObjectPoolAuto.AcquireList<GameRoom>();
AppSrv.g_Room.GetRoomList(ref roomlist);
AppSrv.g_Log.Info($"RoomCount:{roomlist.Count}");