diff --git a/AxibugEmuOnline.Server/Common/SQLRUN.cs b/AxibugEmuOnline.Server/Common/SQLRUN.cs index b21e78a1..e54d72a7 100644 --- a/AxibugEmuOnline.Server/Common/SQLRUN.cs +++ b/AxibugEmuOnline.Server/Common/SQLRUN.cs @@ -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() diff --git a/AxibugEmuOnline.Server/Data/ServerInputSnapShot.cs b/AxibugEmuOnline.Server/Data/ServerInputSnapShot.cs new file mode 100644 index 00000000..4c312818 --- /dev/null +++ b/AxibugEmuOnline.Server/Data/ServerInputSnapShot.cs @@ -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; + } +} diff --git a/AxibugEmuOnline.Server/Manager/ChatManager.cs b/AxibugEmuOnline.Server/Manager/ChatManager.cs index 3c14876e..d671ecf1 100644 --- a/AxibugEmuOnline.Server/Manager/ChatManager.cs +++ b/AxibugEmuOnline.Server/Manager/ChatManager.cs @@ -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 { diff --git a/AxibugEmuOnline.Server/Manager/Client/ClientInfo.cs b/AxibugEmuOnline.Server/Manager/Client/ClientInfo.cs new file mode 100644 index 00000000..97f23b81 --- /dev/null +++ b/AxibugEmuOnline.Server/Manager/Client/ClientInfo.cs @@ -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; + } + } +} diff --git a/AxibugEmuOnline.Server/Manager/ClientManager.cs b/AxibugEmuOnline.Server/Manager/ClientManager.cs index 56f76c1d..ea3351a1 100644 --- a/AxibugEmuOnline.Server/Manager/ClientManager.cs +++ b/AxibugEmuOnline.Server/Manager/ClientManager.cs @@ -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>(); diff --git a/AxibugEmuOnline.Server/Manager/GameShareManager.cs b/AxibugEmuOnline.Server/Manager/GameShareManager.cs index 29d455ad..16df6fe3 100644 --- a/AxibugEmuOnline.Server/Manager/GameShareManager.cs +++ b/AxibugEmuOnline.Server/Manager/GameShareManager.cs @@ -1,4 +1,5 @@ using AxibugEmuOnline.Server.Common; +using AxibugEmuOnline.Server.Manager.Client; using AxibugEmuOnline.Server.NetWork; using AxibugProtobuf; using MySql.Data.MySqlClient; diff --git a/AxibugEmuOnline.Server/Manager/LogManager.cs b/AxibugEmuOnline.Server/Manager/LogManager.cs index 89a22e54..84e41848 100644 --- a/AxibugEmuOnline.Server/Manager/LogManager.cs +++ b/AxibugEmuOnline.Server/Manager/LogManager.cs @@ -1,7 +1,4 @@ -using AxibugProtobuf; -using static Mysqlx.Expect.Open.Types; - -namespace AxibugEmuOnline.Server.Manager +namespace AxibugEmuOnline.Server.Manager { public class LogManager { diff --git a/AxibugEmuOnline.Server/Manager/LoginManager.cs b/AxibugEmuOnline.Server/Manager/LoginManager.cs index 63f5a2ed..ac7cf0f7 100644 --- a/AxibugEmuOnline.Server/Manager/LoginManager.cs +++ b/AxibugEmuOnline.Server/Manager/LoginManager.cs @@ -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; diff --git a/AxibugEmuOnline.Server/Manager/Room/GameRoom.cs b/AxibugEmuOnline.Server/Manager/Room/GameRoom.cs new file mode 100644 index 00000000..46ff1965 --- /dev/null +++ b/AxibugEmuOnline.Server/Manager/Room/GameRoom.cs @@ -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 + } +} diff --git a/AxibugEmuOnline.Server/Manager/Room/GameRoomSlot.cs b/AxibugEmuOnline.Server/Manager/Room/GameRoomSlot.cs new file mode 100644 index 00000000..11b69f7f --- /dev/null +++ b/AxibugEmuOnline.Server/Manager/Room/GameRoomSlot.cs @@ -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; + } + } +} diff --git a/AxibugEmuOnline.Server/Manager/RoomManager.cs b/AxibugEmuOnline.Server/Manager/RoomManager.cs index 23c5f5ed..f2ab0889 100644 --- a/AxibugEmuOnline.Server/Manager/RoomManager.cs +++ b/AxibugEmuOnline.Server/Manager/RoomManager.cs @@ -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; - } } \ No newline at end of file diff --git a/AxibugEmuOnline.Server/Manager/SavDataManager.cs b/AxibugEmuOnline.Server/Manager/SavDataManager.cs index 60524150..ad329d5e 100644 --- a/AxibugEmuOnline.Server/Manager/SavDataManager.cs +++ b/AxibugEmuOnline.Server/Manager/SavDataManager.cs @@ -1,4 +1,5 @@ using AxibugEmuOnline.Server.Common; +using AxibugEmuOnline.Server.Manager.Client; using AxibugEmuOnline.Server.NetWork; using AxibugProtobuf; using MySql.Data.MySqlClient; diff --git a/AxibugEmuOnline.Server/Manager/UserManager.cs b/AxibugEmuOnline.Server/Manager/UserManager.cs index 61b72a36..6a9750be 100644 --- a/AxibugEmuOnline.Server/Manager/UserManager.cs +++ b/AxibugEmuOnline.Server/Manager/UserManager.cs @@ -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; diff --git a/AxibugEmuOnline.Server/Program.cs b/AxibugEmuOnline.Server/Program.cs index 70dc50bf..9c09148d 100644 --- a/AxibugEmuOnline.Server/Program.cs +++ b/AxibugEmuOnline.Server/Program.cs @@ -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}");