fix!: 🐛 fix deadlock problem of spectator
tags/0.1.0
| @@ -51,22 +51,22 @@ class Constants(NoInstance): | |||
| # 攻击相关 | |||
| Constants.basicApOfTricker = 1500000 | |||
| Constants.basicCD = 3000 # 初始子弹冷却 | |||
| Constants.basicCastTime = 500 # 基本前摇时间 | |||
| Constants.basicBackswing = 800 # 基本后摇时间 | |||
| Constants.basicRecoveryFromHit = 3700 # 基本命中攻击恢复时长 | |||
| basicApOfTricker = 1500000 | |||
| basicCD = 3000 # 初始子弹冷却 | |||
| basicCastTime = 500 # 基本前摇时间 | |||
| basicBackswing = 800 # 基本后摇时间 | |||
| basicRecoveryFromHit = 3700 # 基本命中攻击恢复时长 | |||
| basicStunnedTimeOfStudent = 4300 | |||
| Constants.basicBulletMoveSpeed = 7400 # 基本子弹移动速度 | |||
| Constants.basicRemoteAttackRange = 6000 # 基本远程攻击范围 | |||
| Constants.basicAttackShortRange = 2200 # 基本近程攻击范围 | |||
| Constants.basicBulletBombRange = 2000 # 基本子弹爆炸范围 | |||
| basicBulletMoveSpeed = 7400 # 基本子弹移动速度 | |||
| basicRemoteAttackRange = 6000 # 基本远程攻击范围 | |||
| basicAttackShortRange = 2200 # 基本近程攻击范围 | |||
| basicBulletBombRange = 2000 # 基本子弹爆炸范围 | |||
| # 道具相关 | |||
| apPropAdd = Constants.basicApOfTricker * 12 / 10 | |||
| apSpearAdd = Constants.basicApOfTricker * 6 / 10 | |||
| apPropAdd = basicApOfTricker * 12 / 10 | |||
| apSpearAdd = basicApOfTricker * 6 / 10 | |||
| # 技能相关 | |||
| maxNumOfSkill = 3 | |||
| @@ -56,3 +56,8 @@ else | |||
| mv -f temp.lock $playback_dir/video.thuaipb | |||
| kill -9 $server_pid | |||
| fi | |||
| result=$(cat /usr/local/playback/result.json) | |||
| score0=$(parse_json $result "Student") | |||
| score1=$(parse_json $result "Tricker") | |||
| curl $URL -X PUT -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" -d '{"result":[{"team_id":0, "score":'${score0}'}, {"team_id":1, "score":'${score1}'}], "mode":'${MODE}'}' | |||
| @@ -7,9 +7,7 @@ | |||
| - 不要使用conio.h,Windows.h | |||
| ## 请注意下载器不更新AI.py,AI.cpp和脚本 | |||
| - 最新版AI.cpp和AI.py | |||
|  | |||
|  | |||
| - [最新版AI.cpp云盘](https://cloud.tsinghua.edu.cn/lib/54b4eb7b-956e-474c-b932-7b1ac29a9267/file/AI.cpp) 和[AI.py云盘](https://cloud.tsinghua.edu.cn/lib/54b4eb7b-956e-474c-b932-7b1ac29a9267/file/AI.py) | |||
| ## C++接口使用说明 | |||
| @@ -2,7 +2,7 @@ | |||
| "profiles": { | |||
| "Client": { | |||
| "commandName": "Project", | |||
| "commandLineArgs": "--port 8888 --characterID 3 --type 1 --occupation 5" | |||
| "commandLineArgs": "--port 8892 --characterID 8880 --type 1 --occupation 1 --ip thuai6.eesast.com --cl" | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -11,14 +11,15 @@ using Playback; | |||
| using Newtonsoft.Json; | |||
| using Newtonsoft.Json.Linq; | |||
| using Preparation.Interface; | |||
| using System.Collections.Concurrent; | |||
| namespace Server | |||
| { | |||
| public partial class GameServer : AvailableService.AvailableServiceBase | |||
| { | |||
| private Dictionary<long, (SemaphoreSlim, SemaphoreSlim)> semaDict = new(); | |||
| private object semaDictLock = new(); | |||
| private ConcurrentDictionary<long, (SemaphoreSlim, SemaphoreSlim)> semaDict = new(); | |||
| // private object semaDictLock = new(); | |||
| protected readonly ArgumentOptions options; | |||
| private HttpSender? httpSender; | |||
| private object gameLock = new(); | |||
| @@ -29,13 +30,13 @@ namespace Server | |||
| private SemaphoreSlim endGameSem = new(0); | |||
| protected readonly Game game; | |||
| private uint spectatorMinPlayerID = 2023; | |||
| private List<uint> spectatorList = new List<uint>(); | |||
| public int playerNum; | |||
| public int TeamCount => options.TeamCount; | |||
| protected long[] communicationToGameID; // 通信用的ID映射到游戏内的ID,通信中0-3为Student,4为Tricker | |||
| private readonly object messageToAllClientsLock = new(); | |||
| public static readonly long SendMessageToClientIntervalInMilliseconds = 50; | |||
| private MessageWriter? mwr = null; | |||
| private object spetatorJoinLock = new(); | |||
| public void StartGame() | |||
| { | |||
| @@ -169,14 +170,19 @@ namespace Server | |||
| break; | |||
| } | |||
| } | |||
| foreach (var kvp in semaDict) | |||
| lock (spetatorJoinLock) | |||
| { | |||
| kvp.Value.Item1.Release(); | |||
| } | |||
| foreach (var kvp in semaDict) | |||
| { | |||
| kvp.Value.Item1.Release(); | |||
| } | |||
| foreach (var kvp in semaDict) | |||
| { | |||
| kvp.Value.Item2.Wait(); | |||
| // 若此时观战者加入,则死锁,所以需要 spetatorJoinLock | |||
| foreach (var kvp in semaDict) | |||
| { | |||
| kvp.Value.Item2.Wait(); | |||
| } | |||
| } | |||
| } | |||
| private bool playerDeceased(int playerID) | |||
| @@ -2,7 +2,7 @@ | |||
| "profiles": { | |||
| "Server": { | |||
| "commandName": "Project", | |||
| "commandLineArgs": "--ip 0.0.0.0 -p 8888 --characterID 2030" | |||
| "commandLineArgs": "--port 8888 --studentCount 4 --trickerCount 1 --gameTimeInSecond 600 --fileName video" | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -16,6 +16,7 @@ namespace Server | |||
| { | |||
| public partial class GameServer : AvailableService.AvailableServiceBase | |||
| { | |||
| private int playerCountNow = 0; | |||
| protected object spectatorLock = new object(); | |||
| protected bool isSpectatorJoin = false; | |||
| protected bool IsSpectatorJoin | |||
| @@ -59,17 +60,18 @@ namespace Server | |||
| if (request.PlayerId >= spectatorMinPlayerID && options.NotAllowSpectator == false) | |||
| { | |||
| // 观战模式 | |||
| uint tp = (uint)request.PlayerId; | |||
| if (!spectatorList.Contains(tp)) | |||
| lock (spetatorJoinLock) // 具体原因见另一个上锁的地方 | |||
| { | |||
| spectatorList.Add(tp); | |||
| Console.WriteLine("A new spectator comes to watch this game."); | |||
| var temp = (new SemaphoreSlim(0, 1), new SemaphoreSlim(0, 1)); | |||
| lock (semaDictLock) | |||
| if (semaDict.TryAdd(request.PlayerId, (new SemaphoreSlim(0, 1), new SemaphoreSlim(0, 1)))) | |||
| { | |||
| semaDict.Add(request.PlayerId, temp); | |||
| Console.WriteLine("A new spectator comes to watch this game."); | |||
| IsSpectatorJoin = true; | |||
| } | |||
| else | |||
| { | |||
| Console.WriteLine($"Duplicated Spectator ID {request.PlayerId}"); | |||
| return; | |||
| } | |||
| IsSpectatorJoin = true; | |||
| } | |||
| do | |||
| { | |||
| @@ -82,13 +84,30 @@ namespace Server | |||
| //Console.WriteLine("Send!"); | |||
| } | |||
| } | |||
| catch (InvalidOperationException) | |||
| { | |||
| if (semaDict.TryRemove(request.PlayerId, out var semas)) | |||
| { | |||
| try | |||
| { | |||
| semas.Item1.Release(); | |||
| semas.Item2.Release(); | |||
| } | |||
| catch { } | |||
| Console.WriteLine($"The spectator {request.PlayerId} exited"); | |||
| } | |||
| } | |||
| catch (Exception) | |||
| { | |||
| //Console.WriteLine(ex); | |||
| // Console.WriteLine(ex); | |||
| } | |||
| finally | |||
| { | |||
| semaDict[request.PlayerId].Item2.Release(); | |||
| try | |||
| { | |||
| semaDict[request.PlayerId].Item2.Release(); | |||
| } | |||
| catch { } | |||
| } | |||
| } while (game.GameMap.Timer.IsGaming); | |||
| return; | |||
| @@ -117,10 +136,12 @@ namespace Server | |||
| var temp = (new SemaphoreSlim(0, 1), new SemaphoreSlim(0, 1)); | |||
| bool start = false; | |||
| Console.WriteLine($"Id: {request.PlayerId} joins."); | |||
| lock (semaDictLock) | |||
| // lock (semaDictLock) | |||
| { | |||
| semaDict.Add(request.PlayerId, temp); | |||
| start = (semaDict.Count - spectatorList.Count) == playerNum; | |||
| if (semaDict.TryAdd(request.PlayerId, temp)) | |||
| { | |||
| start = Interlocked.Increment(ref playerCountNow) == playerNum; | |||
| } | |||
| } | |||
| if (start) StartGame(); | |||
| } | |||