修复不同时间多客户端连接游戏模组时可能产生的线程安全问题

This commit is contained in:
milimoe 2025-01-16 20:22:34 +08:00
parent 85c0651056
commit ce64314516
Signed by: milimoe
GPG Key ID: 05D280912DA6C69E
2 changed files with 73 additions and 234 deletions

View File

@ -6,6 +6,7 @@ using Milimoe.FunGame.Core.Interface.Base;
using Milimoe.FunGame.Core.Library.Common.Addon; using Milimoe.FunGame.Core.Library.Common.Addon;
using Milimoe.FunGame.Core.Library.Constant; using Milimoe.FunGame.Core.Library.Constant;
using Oshima.Core; using Oshima.Core;
using Oshima.Core.Configs;
using Oshima.FunGame.OshimaModules; using Oshima.FunGame.OshimaModules;
using Oshima.FunGame.OshimaServers.Service; using Oshima.FunGame.OshimaServers.Service;
@ -45,26 +46,31 @@ namespace Oshima.FunGame.OshimaServers
catch (Exception e) catch (Exception e)
{ {
anonymous.Controller.Error(e); anonymous.Controller.Error(e);
Instances.Remove(anonymous);
} }
} }
} }
protected IServerModel? _clientModel; protected HashSet<IServerModel> _clientModels = [];
/// <summary> /// <summary>
/// 启动匿名服务器 /// 启动匿名服务器
/// </summary> /// </summary>
/// <param name="model"></param> /// <param name="model"></param>
/// <returns></returns> /// <returns></returns>
public override bool StartAnonymousServer(IServerModel model) public override bool StartAnonymousServer(IServerModel model, Dictionary<string, object> data)
{ {
// 添加当前单例 // 可以做验证处理
Instances.Add(this); string access_token = NetworkUtility.JsonDeserializeFromDictionary<string>(data, "access_token") ?? "";
Controller.WriteLine($"{model.GetClientName()} 连接至匿名服务器", LogLevel.Info); if (GeneralSettings.TokenList.Contains(access_token))
// 接收连接匿名服务器的客户端 {
_clientModel = model; // 添加当前单例
return true; Instances.Add(this);
Controller.WriteLine($"{model.GetClientName()} 连接至匿名服务器", LogLevel.Info);
// 接收连接匿名服务器的客户端
_clientModels.Add(model);
return true;
}
return false;
} }
/// <summary> /// <summary>
@ -75,6 +81,8 @@ namespace Oshima.FunGame.OshimaServers
{ {
// 移除当前单例 // 移除当前单例
Instances.Remove(this); Instances.Remove(this);
// 移除客户端
_clientModels.Remove(model);
Controller.WriteLine($"{model.GetClientName()} 从匿名服务器断开", LogLevel.Info); Controller.WriteLine($"{model.GetClientName()} 从匿名服务器断开", LogLevel.Info);
} }
@ -93,22 +101,21 @@ namespace Oshima.FunGame.OshimaServers
/// <param name="msg"></param> /// <param name="msg"></param>
public async Task PushMessage(long qq, string msg) public async Task PushMessage(long qq, string msg)
{ {
if (_clientModel != null) Dictionary<string, object> data = [];
{ data.Add(nameof(qq), qq);
Dictionary<string, object> data = []; data.Add(nameof(msg), msg);
data.Add(nameof(qq), qq); Controller.WriteLine("向客户端推送事件", LogLevel.Debug);
data.Add(nameof(msg), msg); List<IServerModel> failedModels = await SendAnonymousGameServerMessage(_clientModels, data);
Controller.WriteLine("向客户端推送事件", LogLevel.Debug); failedModels.ForEach(model => _clientModels.Remove(model));
await SendAnonymousGameServerMessage([_clientModel], data);
}
} }
/// <summary> /// <summary>
/// 接收并处理匿名服务器消息 /// 接收并处理匿名服务器消息
/// </summary> /// </summary>
/// <param name="model"></param>
/// <param name="data"></param> /// <param name="data"></param>
/// <returns></returns> /// <returns></returns>
public override async Task<Dictionary<string, object>> AnonymousGameServerHandler(Dictionary<string, object> data) public override async Task<Dictionary<string, object>> AnonymousGameServerHandler(IServerModel model, Dictionary<string, object> data)
{ {
Dictionary<string, object> result = []; Dictionary<string, object> result = [];
Controller.WriteLine("接收匿名服务器消息", LogLevel.Debug); Controller.WriteLine("接收匿名服务器消息", LogLevel.Debug);
@ -229,7 +236,7 @@ namespace Oshima.FunGame.OshimaServers
return result.Trim(); return result.Trim();
} }
public override Task<Dictionary<string, object>> GamingMessageHandler(string username, GamingType type, Dictionary<string, object> data) public override Task<Dictionary<string, object>> GamingMessageHandler(IServerModel model, GamingType type, Dictionary<string, object> data)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }

View File

@ -1,4 +1,5 @@
using System.Text; using System.Collections.Concurrent;
using System.Text;
using Milimoe.FunGame.Core.Api.Transmittal; using Milimoe.FunGame.Core.Api.Transmittal;
using Milimoe.FunGame.Core.Api.Utility; using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Entity; using Milimoe.FunGame.Core.Entity;
@ -9,6 +10,7 @@ using Milimoe.FunGame.Core.Model;
using Oshima.FunGame.OshimaModules; using Oshima.FunGame.OshimaModules;
using Oshima.FunGame.OshimaModules.Items; using Oshima.FunGame.OshimaModules.Items;
using Oshima.FunGame.OshimaModules.Skills; using Oshima.FunGame.OshimaModules.Skills;
using Oshima.FunGame.OshimaServers.Service;
namespace Oshima.FunGame.OshimaServers namespace Oshima.FunGame.OshimaServers
{ {
@ -24,19 +26,20 @@ namespace Oshima.FunGame.OshimaServers
public static Dictionary<Character, CharacterStatistics> CharacterStatistics { get; } = []; public static Dictionary<Character, CharacterStatistics> CharacterStatistics { get; } = [];
public static PluginConfig StatsConfig { get; } = new(OshimaGameModuleConstant.FastAuto, nameof(CharacterStatistics)); public static PluginConfig StatsConfig { get; } = new(OshimaGameModuleConstant.FastAuto, nameof(CharacterStatistics));
public static GameModuleLoader? GameModuleLoader { get; set; } = null; public static GameModuleLoader? GameModuleLoader { get; set; } = null;
public static List<User> ConnectedUsers { get; } = [];
public override async Task<Dictionary<string, object>> GamingMessageHandler(string username, GamingType type, Dictionary<string, object> data) public override async Task<Dictionary<string, object>> GamingMessageHandler(IServerModel model, GamingType type, Dictionary<string, object> data)
{ {
Dictionary<string, object> result = []; Dictionary<string, object> result = [];
// 获取model所在的房间工作类
ModuleServerWorker worker = Workers[model.InRoom.Roomid];
switch (type) switch (type)
{ {
case GamingType.Connect: case GamingType.Connect:
string un = (DataRequest.GetDictionaryJsonObject<string>(data, "un") ?? "").Trim(); string un = (DataRequest.GetDictionaryJsonObject<string>(data, "un") ?? "").Trim();
if (un != "" && !ConnectedUsers.Where(u => u.Username == un).Any()) if (un != "" && !worker.ConnectedUser.Any(u => u.Username == un))
{ {
ConnectedUsers.Add(Users.Where(u => u.Username == un).First()); worker.ConnectedUser.Add(model.User);
Controller.WriteLine(un + " 已连接至房间。"); Controller.WriteLine(un + " 已连接至房间。");
} }
break; break;
@ -87,29 +90,35 @@ namespace Oshima.FunGame.OshimaServers
return result; return result;
} }
protected Room Room = General.HallInstance; private readonly struct ModuleServerWorker(Room room, List<User> users, IServerModel roomMaster, Dictionary<string, IServerModel> serverModels)
protected List<User> Users = []; {
protected IServerModel? RoomMaster; public Room Room { get; } = room;
protected Dictionary<string, IServerModel> All = []; public List<User> Users { get; } = users;
public IServerModel RoomMaster { get; } = roomMaster;
public Dictionary<string, IServerModel> All { get; } = serverModels;
public List<User> ConnectedUser { get; } = [];
public Dictionary<string, Dictionary<string, object>> UserData { get; } = [];
public Dictionary<User, Character> PickCharacters { get; } = [];
}
private ConcurrentDictionary<string, ModuleServerWorker> Workers { get; } = [];
public override bool StartServer(string GameModule, Room Room, List<User> Users, IServerModel RoomMasterServerModel, Dictionary<string, IServerModel> ServerModels, params object[] Args) public override bool StartServer(string GameModule, Room Room, List<User> Users, IServerModel RoomMasterServerModel, Dictionary<string, IServerModel> ServerModels, params object[] Args)
{ {
// 将参数转为本地属性 // 因为模组是单例的,需要为这个房间创建一个工作类接收参数,不能直接用本地变量处理
this.Room = Room; ModuleServerWorker worker = new(Room, Users, RoomMasterServerModel, ServerModels);
this.Users = Users; Workers[Room.Roomid] = worker;
RoomMaster = RoomMasterServerModel; TaskUtility.NewTask(async () => await StartGame(worker)).OnError(Controller.Error);
All = ServerModels;
TaskUtility.NewTask(StartGame).OnError(Controller.Error);
return true; return true;
} }
public async Task StartGame() private async Task StartGame(ModuleServerWorker worker)
{ {
try try
{ {
while (true) while (true)
{ {
if (ConnectedUsers.Count == Users.Count) if (worker.ConnectedUser.Count == worker.All.Count)
{ {
break; break;
} }
@ -121,7 +130,7 @@ namespace Oshima.FunGame.OshimaServers
Dictionary<string, object> data = []; Dictionary<string, object> data = [];
// 抽取角色 // 抽取角色
foreach (User user in Users) foreach (User user in worker.Users)
{ {
List<Character> list = [.. Characters.Where(c => !characterPickeds.Contains(c))]; List<Character> list = [.. Characters.Where(c => !characterPickeds.Contains(c))];
Character cr = list[Random.Shared.Next(list.Count - 1)]; Character cr = list[Random.Shared.Next(list.Count - 1)];
@ -129,7 +138,7 @@ namespace Oshima.FunGame.OshimaServers
characterPickeds.Add(cr); characterPickeds.Add(cr);
characters.Add(user, cr.Copy()); characters.Add(user, cr.Copy());
Controller.WriteLine(msg); Controller.WriteLine(msg);
SendAllGamingMessage(data, msg); SendAllGamingMessage(worker, data, msg);
} }
int clevel = 60; int clevel = 60;
@ -156,192 +165,14 @@ namespace Oshima.FunGame.OshimaServers
}; };
c.Skills.Add(); c.Skills.Add();
if (c.Id == 1) FunGameService.AddCharacterSkills(c, 1, slevel, slevel);
{
Skill META马 = new META马(c)
{
Level = 1
};
c.Skills.Add(META马);
Skill = new (c) SendAllGamingMessage(worker, data, c.GetInfo());
{
Level = mlevel
};
c.Skills.Add();
}
if (c.Id == 2)
{
Skill = new (c)
{
Level = 1
};
c.Skills.Add();
Skill = new (c)
{
Level = slevel
};
c.Skills.Add();
}
if (c.Id == 3)
{
Skill = new (c)
{
Level = 1
};
c.Skills.Add();
Skill = new (c)
{
Level = slevel
};
c.Skills.Add();
}
if (c.Id == 4)
{
Skill = new (c)
{
Level = 1
};
c.Skills.Add();
Skill = new (c)
{
Level = slevel
};
c.Skills.Add();
}
if (c.Id == 5)
{
Skill = new (c)
{
Level = 1
};
c.Skills.Add();
Skill = new (c)
{
Level = slevel
};
c.Skills.Add();
}
if (c.Id == 6)
{
Skill = new (c)
{
Level = 1
};
c.Skills.Add();
Skill = new (c)
{
Level = slevel
};
c.Skills.Add();
}
if (c.Id == 7)
{
Skill = new (c)
{
Level = 1
};
c.Skills.Add();
Skill = new (c)
{
Level = slevel
};
c.Skills.Add();
}
if (c.Id == 8)
{
Skill = new (c)
{
Level = 1
};
c.Skills.Add();
Skill = new (c)
{
Level = slevel
};
c.Skills.Add();
}
if (c.Id == 9)
{
Skill = new (c)
{
Level = 1
};
c.Skills.Add();
Skill = new (c)
{
Level = slevel
};
c.Skills.Add();
}
if (c.Id == 10)
{
Skill = new (c)
{
Level = 1
};
c.Skills.Add();
Skill = new (c)
{
Level = slevel
};
c.Skills.Add();
}
if (c.Id == 11)
{
Skill = new (c)
{
Level = 1
};
c.Skills.Add();
Skill = new (c)
{
Level = slevel
};
c.Skills.Add();
}
if (c.Id == 12)
{
Skill = new (c)
{
Level = 1
};
c.Skills.Add();
Skill = new (c)
{
Level = slevel
};
c.Skills.Add();
}
SendAllGamingMessage(data, c.GetInfo());
} }
ActionQueue actionQueue = new(inGameCharacters, false, (str) => ActionQueue actionQueue = new(inGameCharacters, false, (str) =>
{ {
SendAllGamingMessage(data, str); SendAllGamingMessage(worker, data, str);
}); });
// 总游戏时长 // 总游戏时长
@ -358,7 +189,7 @@ namespace Oshima.FunGame.OshimaServers
{ {
if (i == 998) if (i == 998)
{ {
SendAllGamingMessage(data, $"=== 终局审判 ==="); SendAllGamingMessage(worker, data, $"=== 终局审判 ===");
Dictionary<Character, double> = []; Dictionary<Character, double> = [];
foreach (Character c in inGameCharacters) foreach (Character c in inGameCharacters)
{ {
@ -366,10 +197,10 @@ namespace Oshima.FunGame.OshimaServers
} }
double max = .Values.Max(); double max = .Values.Max();
Character winner = .Keys.Where(c => [c] == max).First(); Character winner = .Keys.Where(c => [c] == max).First();
SendAllGamingMessage(data, "[ " + winner + " ] 成为了天选之人!!"); SendAllGamingMessage(worker, data, "[ " + winner + " ] 成为了天选之人!!");
foreach (Character c in inGameCharacters.Where(c => c != winner && c.HP > 0)) foreach (Character c in inGameCharacters.Where(c => c != winner && c.HP > 0))
{ {
SendAllGamingMessage(data, "[ " + winner + " ] 对 [ " + c + " ] 造成了 99999999999 点真实伤害。"); SendAllGamingMessage(worker, data, "[ " + winner + " ] 对 [ " + c + " ] 造成了 99999999999 点真实伤害。");
actionQueue.DeathCalculation(winner, c); actionQueue.DeathCalculation(winner, c);
} }
actionQueue.EndGameInfo(winner); actionQueue.EndGameInfo(winner);
@ -382,8 +213,8 @@ namespace Oshima.FunGame.OshimaServers
// 处理回合 // 处理回合
if (characterToAct != null) if (characterToAct != null)
{ {
SendAllGamingMessage(data, $"=== Round {i++} ==="); SendAllGamingMessage(worker, data, $"=== Round {i++} ===");
SendAllGamingMessage(data, "现在是 [ " + characterToAct + " ] 的回合!"); SendAllGamingMessage(worker, data, "现在是 [ " + characterToAct + " ] 的回合!");
if (actionQueue.Queue.Count == 0) if (actionQueue.Queue.Count == 0)
{ {
@ -408,11 +239,11 @@ namespace Oshima.FunGame.OshimaServers
} }
} }
SendAllGamingMessage(data, "--- End ---"); SendAllGamingMessage(worker, data, "--- End ---");
SendAllGamingMessage(data, "总游戏时长:" + Calculation.Round2Digits(totalTime)); SendAllGamingMessage(worker, data, "总游戏时长:" + Calculation.Round2Digits(totalTime));
// 赛后统计 // 赛后统计
SendAllGamingMessage(data, "=== 伤害排行榜 ==="); SendAllGamingMessage(worker, data, "=== 伤害排行榜 ===");
int top = inGameCharacters.Count; int top = inGameCharacters.Count;
int count = 1; int count = 1;
foreach (Character character in actionQueue.CharacterStatistics.OrderByDescending(d => d.Value.TotalDamage).Select(d => d.Key)) foreach (Character character in actionQueue.CharacterStatistics.OrderByDescending(d => d.Value.TotalDamage).Select(d => d.Key))
@ -425,7 +256,7 @@ namespace Oshima.FunGame.OshimaServers
builder.AppendLine($"总承受伤害:{stats.TotalTakenDamage} / 总承受物理伤害:{stats.TotalTakenPhysicalDamage} / 总承受魔法伤害:{stats.TotalTakenMagicDamage}"); builder.AppendLine($"总承受伤害:{stats.TotalTakenDamage} / 总承受物理伤害:{stats.TotalTakenPhysicalDamage} / 总承受魔法伤害:{stats.TotalTakenMagicDamage}");
builder.Append($"每秒伤害:{stats.DamagePerSecond} / 每回合伤害:{stats.DamagePerTurn}"); builder.Append($"每秒伤害:{stats.DamagePerSecond} / 每回合伤害:{stats.DamagePerTurn}");
SendAllGamingMessage(data, builder.ToString()); SendAllGamingMessage(worker, data, builder.ToString());
CharacterStatistics? totalStats = CharacterStatistics.Where(kv => kv.Key.GetName() == character.GetName()).Select(kv => kv.Value).FirstOrDefault(); CharacterStatistics? totalStats = CharacterStatistics.Where(kv => kv.Key.GetName() == character.GetName()).Select(kv => kv.Value).FirstOrDefault();
if (totalStats != null) if (totalStats != null)
@ -480,7 +311,7 @@ namespace Oshima.FunGame.OshimaServers
for (i = actionQueue.Eliminated.Count - 1; i >= 0; i--) for (i = actionQueue.Eliminated.Count - 1; i >= 0; i--)
{ {
Character character = actionQueue.Eliminated[i]; Character character = actionQueue.Eliminated[i];
SendAllGamingMessage(data, $"=== 角色 [ {character} ] ===\r\n{character.GetInfo()}"); SendAllGamingMessage(worker, data, $"=== 角色 [ {character} ] ===\r\n{character.GetInfo()}");
} }
lock (StatsConfig) lock (StatsConfig)
@ -493,12 +324,13 @@ namespace Oshima.FunGame.OshimaServers
} }
// 结束 // 结束
await Send(All.Values, SocketMessageType.EndGame, Room, Users); await Send(worker.All.Values, SocketMessageType.EndGame, worker.Room, worker.Users);
foreach (IServerModel model in All.Values) foreach (IServerModel model in worker.All.Values)
{ {
model.NowGamingServer = null; model.NowGamingServer = null;
} }
ConnectedUsers.Clear(); worker.ConnectedUser.Clear();
Workers.Remove(worker.Room.Roomid, out _);
} }
catch (Exception e) catch (Exception e)
{ {
@ -522,12 +354,12 @@ namespace Oshima.FunGame.OshimaServers
} }
} }
public void SendAllGamingMessage(Dictionary<string, object> data, string str, bool showmessage = false) private void SendAllGamingMessage(ModuleServerWorker worker, Dictionary<string, object> data, string str, bool showmessage = false)
{ {
data.Clear(); data.Clear();
data.Add("msg", str); data.Add("msg", str);
data.Add("showmessage", showmessage); data.Add("showmessage", showmessage);
SendGamingMessage(All.Values, GamingType.UpdateInfo, data); _ = SendGamingMessage(worker.All.Values, GamingType.UpdateInfo, data);
} }
public override void AfterLoad(params object[] args) public override void AfterLoad(params object[] args)