From 46bdc2ac33077c9ea9762ca9c51463cd7696d544 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Thu, 30 Aug 2018 20:53:00 -0500 Subject: [PATCH] moved event API stuff around finally fixed threading issue (which actually had to do with IW4x log outputs being out of sync (not an issue with my code). What a lot of headache over something that wasn't my fault. --- Application/Application.csproj | 2 +- Application/GameEventHandler.cs | 53 ++++++++++++++++-- Application/IO/GameLogEventDetection.cs | 55 +++++++++---------- Application/IO/GameLogReader.cs | 4 +- Application/IO/GameLogReaderHttp.cs | 29 +++++++--- Application/Main.cs | 4 +- Application/Manager.cs | 52 ++++++++++-------- Application/Server.cs | 20 ++++--- Plugins/ScriptPlugins/VPNDetection.js | 10 ++-- Plugins/Stats/Cheat/Detection.cs | 3 +- Plugins/Stats/Helpers/StatManager.cs | 7 ++- Plugins/Stats/Plugin.cs | 8 +++ Plugins/Tests/ManagerTests.cs | 5 +- SharedLibraryCore/Commands/NativeCommands.cs | 48 +++++++++++++--- .../Events}/EventAPI.cs | 19 +++---- .../{Event.cs => Events/GameEvent.cs} | 26 +++------ SharedLibraryCore/Events/GameEventArgs.cs | 23 ++++++++ SharedLibraryCore/Interfaces/IEventApi.cs | 21 ------- SharedLibraryCore/Interfaces/IEventHandler.cs | 2 +- .../Interfaces/IGameLogReader.cs | 2 +- SharedLibraryCore/Interfaces/IManager.cs | 1 - SharedLibraryCore/RCon/Connection.cs | 3 +- SharedLibraryCore/Server.cs | 16 ++---- WebfrontCore/Controllers/API/APIController.cs | 3 +- WebfrontCore/Controllers/ConsoleController.cs | 2 +- 25 files changed, 254 insertions(+), 164 deletions(-) rename {Application/API => SharedLibraryCore/Events}/EventAPI.cs (81%) rename SharedLibraryCore/{Event.cs => Events/GameEvent.cs} (82%) create mode 100644 SharedLibraryCore/Events/GameEventArgs.cs delete mode 100644 SharedLibraryCore/Interfaces/IEventApi.cs diff --git a/Application/Application.csproj b/Application/Application.csproj index 3550af1e0..1f36e5a01 100644 --- a/Application/Application.csproj +++ b/Application/Application.csproj @@ -5,7 +5,7 @@ netcoreapp2.1 false RaidMax.IW4MAdmin.Application - 2.1.5 + 2.1.6 RaidMax Forever None IW4MAdmin diff --git a/Application/GameEventHandler.cs b/Application/GameEventHandler.cs index d23d74d95..565afd459 100644 --- a/Application/GameEventHandler.cs +++ b/Application/GameEventHandler.cs @@ -1,27 +1,72 @@ using SharedLibraryCore; +using SharedLibraryCore.Events; using SharedLibraryCore.Interfaces; using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; namespace IW4MAdmin.Application { class GameEventHandler : IEventHandler { private readonly IManager Manager; + static long NextEventId = 1; + private readonly SortedList OutOfOrderEvents; + private readonly SemaphoreSlim ProcessingEvent; public GameEventHandler(IManager mgr) { Manager = mgr; + OutOfOrderEvents = new SortedList(); + ProcessingEvent = new SemaphoreSlim(0); + ProcessingEvent.Release(); } - public void AddEvent(GameEvent gameEvent) + public bool AddEvent(GameEvent gameEvent) { #if DEBUG - Manager.GetLogger().WriteDebug($"Got new event of type {gameEvent.Type} for {gameEvent.Owner}"); + Manager.GetLogger().WriteDebug($"Got new event of type {gameEvent.Type} for {gameEvent.Owner} with id {gameEvent.Id}"); #endif - // todo: later - ((Manager as ApplicationManager).OnServerEvent)(this, new ApplicationManager.GameEventArgs(null, false, gameEvent)); + while (OutOfOrderEvents.Values.FirstOrDefault()?.Id == Interlocked.Read(ref NextEventId)) + { + lock (OutOfOrderEvents) + { + var fixedEvent = OutOfOrderEvents.Values[0]; + OutOfOrderEvents.RemoveAt(0); + AddEvent(fixedEvent); + } + } + + // both the gameEvent Id and the LastEventId are thread safe because we want to synchronize when the + // event occurs + if (gameEvent.Id == Interlocked.Read(ref NextEventId)) + { + Manager.GetLogger().WriteDebug($"Starting to wait for event with id {gameEvent.Id}"); + ((Manager as ApplicationManager).OnServerEvent)(this, new GameEventArgs(null, false, gameEvent)); + Manager.GetLogger().WriteDebug($"Finished waiting for event with id {gameEvent.Id}"); + Interlocked.Increment(ref NextEventId); + } + + // a "newer" event has been added before and "older" one has been added (due to threads and context switching) + // so me must wait until the next expected one arrives + else + { + Manager.GetLogger().WriteWarning("Incoming event is out of order"); + Manager.GetLogger().WriteDebug($"Expected event Id is {Interlocked.Read(ref NextEventId)}, but got {gameEvent.Id} instead"); + + // this prevents multiple threads from adding simultaneously + lock (OutOfOrderEvents) + { + if (!OutOfOrderEvents.TryGetValue(gameEvent.Id, out GameEvent discard)) + { + OutOfOrderEvents.Add(gameEvent.Id, gameEvent); + } + } + } + return true; } } } diff --git a/Application/IO/GameLogEventDetection.cs b/Application/IO/GameLogEventDetection.cs index 61933bf2e..e2f056d96 100644 --- a/Application/IO/GameLogEventDetection.cs +++ b/Application/IO/GameLogEventDetection.cs @@ -1,7 +1,9 @@ using SharedLibraryCore; using SharedLibraryCore.Interfaces; using System; +using System.Collections.Generic; using System.IO; +using System.Threading; using System.Threading.Tasks; namespace IW4MAdmin.Application.IO @@ -31,44 +33,36 @@ namespace IW4MAdmin.Application.IO { Reader = new GameLogReader(gameLogPath, server.EventParser); } + Server = server; - - Task.Run(async () => - { - while (!server.Manager.ShutdownRequested()) - { - if ((server.Manager as ApplicationManager).IsInitialized) - { - OnEvent(new EventState() - { - Log = server.Manager.GetLogger(), - ServerId = server.ToString() - }); - } - await Task.Delay(100); - } - }); } - private void OnEvent(object state) + public void PollForChanges() { - long newLength = Reader.Length; - - try + while (!Server.Manager.ShutdownRequested()) { - UpdateLogEvents(newLength); - } + if ((Server.Manager as ApplicationManager).IsInitialized) + { + try + { + UpdateLogEvents(); + } - catch (Exception e) - { - ((EventState)state).Log.WriteWarning($"Failed to update log event for {((EventState)state).ServerId}"); - ((EventState)state).Log.WriteDebug($"Exception: {e.Message}"); - ((EventState)state).Log.WriteDebug($"StackTrace: {e.StackTrace}"); + catch (Exception e) + { + Server.Logger.WriteWarning($"Failed to update log event for {Server.GetHashCode()}"); + Server.Logger.WriteDebug($"Exception: {e.Message}"); + Server.Logger.WriteDebug($"StackTrace: {e.StackTrace}"); + } + } + Thread.Sleep(Reader.UpdateInterval); } } - private void UpdateLogEvents(long fileSize) + private void UpdateLogEvents() { + long fileSize = Reader.Length; + if (PreviousFileSize == 0) PreviousFileSize = fileSize; @@ -79,9 +73,12 @@ namespace IW4MAdmin.Application.IO PreviousFileSize = fileSize; - var events = Reader.EventsFromLog(Server, fileDiff, 0); + var events = Reader.ReadEventsFromLog(Server, fileDiff, 0); + foreach (var ev in events) + { Server.Manager.GetEventHandler().AddEvent(ev); + } PreviousFileSize = fileSize; } diff --git a/Application/IO/GameLogReader.cs b/Application/IO/GameLogReader.cs index 1b4ea78b9..9405a0642 100644 --- a/Application/IO/GameLogReader.cs +++ b/Application/IO/GameLogReader.cs @@ -14,7 +14,7 @@ namespace IW4MAdmin.Application.IO public long Length => new FileInfo(LogFile).Length; - public int UpdateInterval => 100; + public int UpdateInterval => 300; public GameLogReader(string logFile, IEventParser parser) { @@ -22,7 +22,7 @@ namespace IW4MAdmin.Application.IO Parser = parser; } - public ICollection EventsFromLog(Server server, long fileSizeDiff, long startPosition) + public ICollection ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition) { // allocate the bytes for the new log lines List logLines = new List(); diff --git a/Application/IO/GameLogReaderHttp.cs b/Application/IO/GameLogReaderHttp.cs index b132e3144..f79e4428d 100644 --- a/Application/IO/GameLogReaderHttp.cs +++ b/Application/IO/GameLogReaderHttp.cs @@ -29,11 +29,12 @@ namespace IW4MAdmin.Application.IO { using (var cl = new HttpClient()) { - using (var re = cl.GetAsync($"{LogFile}?length=1").Result) + using (var re = cl.GetAsync($"{LogFile}&length=1").Result) { using (var content = re.Content) { - return Convert.ToInt64(content.ReadAsStringAsync().Result ?? "0"); + string response = content.ReadAsStringAsync().Result ?? "0"; + return Convert.ToInt64(response); } } } @@ -42,12 +43,15 @@ namespace IW4MAdmin.Application.IO public int UpdateInterval => 1000; - public ICollection EventsFromLog(Server server, long fileSizeDiff, long startPosition) + public ICollection ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition) { +#if DEBUG == true + server.Logger.WriteDebug($"Begin reading {fileSizeDiff} from http log"); +#endif string log; using (var cl = new HttpClient()) { - using (var re = cl.GetAsync($"{LogFile}?start={fileSizeDiff}").Result) + using (var re = cl.GetAsync($"{LogFile}&start={fileSizeDiff}").Result) { using (var content = re.Content) { @@ -55,18 +59,29 @@ namespace IW4MAdmin.Application.IO } } } - +#if DEBUG == true + server.Logger.WriteDebug($"retrieved events from http log"); +#endif List events = new List(); + string[] lines = log.Split(Environment.NewLine); + +#if DEBUG == true + server.Logger.WriteDebug($"Begin parse of {lines.Length} lines from http log"); +#endif // parse each line - foreach (string eventLine in log.Split(Environment.NewLine)) + foreach (string eventLine in lines) { if (eventLine.Length > 0) { try { // todo: catch elsewhere - events.Add(Parser.GetEvent(server, eventLine)); + var e = Parser.GetEvent(server, eventLine); +#if DEBUG == true + server.Logger.WriteDebug($"Parsed event with id {e.Id} from http"); +#endif + events.Add(e); } catch (Exception e) diff --git a/Application/Main.cs b/Application/Main.cs index 560993ce3..9b6fba07c 100644 --- a/Application/Main.cs +++ b/Application/Main.cs @@ -106,7 +106,7 @@ namespace IW4MAdmin.Application ServerManager.Init().Wait(); - var consoleTask = Task.Run(() => + var consoleTask = Task.Run(async () => { String userInput; Player Origin = ServerManager.GetClientService().Get(1).Result.AsPlayer(); @@ -136,7 +136,7 @@ namespace IW4MAdmin.Application }; ServerManager.GetEventHandler().AddEvent(E); - E.OnProcessed.Wait(5000); + await E.OnProcessed.WaitAsync(30 * 1000); } Console.Write('>'); diff --git a/Application/Manager.cs b/Application/Manager.cs index b86e8b295..586c93d3a 100644 --- a/Application/Manager.cs +++ b/Application/Manager.cs @@ -22,6 +22,7 @@ using System.Text; using IW4MAdmin.Application.API.Master; using System.Reflection; using SharedLibraryCore.Database; +using SharedLibraryCore.Events; namespace IW4MAdmin.Application { @@ -33,7 +34,6 @@ namespace IW4MAdmin.Application public ILogger Logger { get; private set; } public bool Running { get; private set; } public bool IsInitialized { get; private set; } - //public EventHandler ServerEventOccurred { get; private set; } // define what the delagate function looks like public delegate void OnServerEventEventHandler(object sender, GameEventArgs e); // expose the event handler so we can execute the events @@ -48,22 +48,10 @@ namespace IW4MAdmin.Application readonly AliasService AliasSvc; readonly PenaltyService PenaltySvc; public BaseConfigurationHandler ConfigHandler; - EventApi Api; GameEventHandler Handler; ManualResetEventSlim OnQuit; readonly IPageList PageList; - public class GameEventArgs : System.ComponentModel.AsyncCompletedEventArgs - { - - public GameEventArgs(Exception error, bool cancelled, GameEvent userState) : base(error, cancelled, userState) - { - Event = userState; - } - - public GameEvent Event { get; } - } - private ApplicationManager() { Logger = new Logger($@"{Utilities.OperatingDirectory}IW4MAdmin.log"); @@ -75,13 +63,12 @@ namespace IW4MAdmin.Application AliasSvc = new AliasService(); PenaltySvc = new PenaltyService(); PrivilegedClients = new Dictionary(); - Api = new EventApi(); - //ServerEventOccurred += Api.OnServerEvent; ConfigHandler = new BaseConfigurationHandler("IW4MAdminSettings"); StartTime = DateTime.UtcNow; OnQuit = new ManualResetEventSlim(); PageList = new PageList(); OnServerEvent += OnServerEventAsync; + OnServerEvent += EventApi.OnGameEvent; } private async void OnServerEventAsync(object sender, GameEventArgs args) @@ -108,7 +95,7 @@ namespace IW4MAdmin.Application return; } - await newEvent.Owner.ExecuteEvent(newEvent); + //// todo: this is a hacky mess if (newEvent.Origin?.DelayedEvents.Count > 0 && @@ -119,7 +106,20 @@ namespace IW4MAdmin.Application // add the delayed event to the queue while(events.Count > 0) { - var e = events.Dequeue(); + var oldEvent = events.Dequeue(); + + var e = new GameEvent() + { + Type = oldEvent.Type, + Origin = newEvent.Origin, + Data = oldEvent.Data, + Extra = oldEvent.Extra, + Owner = oldEvent.Owner, + Message = oldEvent.Message, + Target = oldEvent.Target, + OnProcessed = oldEvent.OnProcessed, + Remote = oldEvent.Remote + }; e.Origin = newEvent.Origin; // check if the target was assigned @@ -140,9 +140,10 @@ namespace IW4MAdmin.Application } } - Api.OnServerEvent(this, newEvent); + await newEvent.Owner.ExecuteEvent(newEvent); + #if DEBUG - Logger.WriteDebug("Processed Event"); + Logger.WriteDebug($"Processed event with id {newEvent.Id}"); #endif } @@ -169,7 +170,7 @@ namespace IW4MAdmin.Application Logger.WriteDebug("Error Trace: " + ex.StackTrace); } // tell anyone waiting for the output that we're done - newEvent.OnProcessed.Set(); + newEvent.OnProcessed.Release(); } public IList GetServers() @@ -423,7 +424,15 @@ namespace IW4MAdmin.Application Logger.WriteVerbose($"{Utilities.CurrentLocalization.LocalizationIndex["MANAGER_MONITORING_TEXT"]} {ServerInstance.Hostname}"); // add the start event for this server - Handler.AddEvent(new GameEvent(GameEvent.EventType.Start, "Server started", null, null, ServerInstance)); + + var e = new GameEvent() + { + Type = GameEvent.EventType.Start, + Data = $"{ServerInstance.GameName} started", + Owner = ServerInstance + }; + + Handler.AddEvent(e); } catch (ServerException e) @@ -562,7 +571,6 @@ namespace IW4MAdmin.Application public PenaltyService GetPenaltyService() => PenaltySvc; public IConfigurationHandler GetApplicationSettings() => ConfigHandler; public IDictionary GetPrivilegedClients() => PrivilegedClients; - public IEventApi GetEventApi() => Api; public bool ShutdownRequested() => !Running; public IEventHandler GetEventHandler() => Handler; diff --git a/Application/Server.cs b/Application/Server.cs index 6fee44ce1..757abe17d 100644 --- a/Application/Server.cs +++ b/Application/Server.cs @@ -27,11 +27,9 @@ namespace IW4MAdmin { private static readonly Index loc = Utilities.CurrentLocalization.LocalizationIndex; private GameLogEventDetection LogEvent; - private ClientAuthentication AuthQueue; public IW4MServer(IManager mgr, ServerConfiguration cfg) : base(mgr, cfg) { - AuthQueue = new ClientAuthentication(); } public override int GetHashCode() @@ -603,7 +601,7 @@ namespace IW4MAdmin try { var polledClients = await PollPlayersAsync(); - var waiterList = new List(); + var waiterList = new List(); foreach (var disconnectingClient in polledClients[1]) { @@ -625,7 +623,7 @@ namespace IW4MAdmin waiterList.Add(e.OnProcessed); } // wait for all the disconnect tasks to finish - await Task.WhenAll(waiterList.Select(t => Task.Run(() => t.Wait(5000)))); + await Task.WhenAll(waiterList.Select(t => t.WaitAsync())); waiterList.Clear(); // this are our new connecting clients @@ -643,7 +641,7 @@ namespace IW4MAdmin } // wait for all the connect tasks to finish - await Task.WhenAll(waiterList.Select(t => Task.Run(() => t.Wait(5000)))); + await Task.WhenAll(waiterList.Select(t => t.WaitAsync())); if (ConnectionErrors > 0) { @@ -835,7 +833,7 @@ namespace IW4MAdmin logPath = Regex.Replace($"{Path.DirectorySeparatorChar}{logPath}", @"[A-Z]:", ""); } - if (!File.Exists(logPath)) + if (!File.Exists(logPath) && !logPath.StartsWith("http")) { Logger.WriteError($"{logPath} {loc["SERVER_ERROR_DNE"]}"); #if !DEBUG @@ -850,6 +848,8 @@ namespace IW4MAdmin } Logger.WriteInfo($"Log file is {logPath}"); + + Task.Run(() => LogEvent.PollForChanges()); #if !DEBUG await Broadcast(loc["BROADCAST_ONLINE"]); #endif @@ -1002,15 +1002,17 @@ namespace IW4MAdmin // this is set only because they're still in the server. Target.Level = Player.Permission.Banned; - // let the api know that a ban occured - Manager.GetEventHandler().AddEvent(new GameEvent() + var e = new GameEvent() { Type = GameEvent.EventType.Ban, Data = Message, Origin = Origin, Target = Target, Owner = this - }); + }; + + // let the api know that a ban occured + Manager.GetEventHandler().AddEvent(e); #if !DEBUG string formattedString = String.Format(RconParser.GetCommandPrefixes().Kick, Target.ClientNumber, $"{loc["SERVER_BAN_TEXT"]} - ^5{Message} ^7({loc["SERVER_BAN_APPEAL"]} {Website})^7"); diff --git a/Plugins/ScriptPlugins/VPNDetection.js b/Plugins/ScriptPlugins/VPNDetection.js index ef3604555..5d9af92e9 100644 --- a/Plugins/ScriptPlugins/VPNDetection.js +++ b/Plugins/ScriptPlugins/VPNDetection.js @@ -10,7 +10,7 @@ const plugin = { checkForVpn(origin) { let exempt = false; // prevent players that are exempt from being kicked - this.vpnExceptionIds.forEach(function(id) { + this.vpnExceptionIds.forEach(function (id) { if (id === origin.ClientId) { exempt = true; return false; @@ -48,7 +48,7 @@ const plugin = { onEventAsync(gameEvent, server) { // connect event if (gameEvent.Type === 3) { - this.checkForVpn(gameEvent.Origin) + this.checkForVpn(gameEvent.Origin); } }, @@ -57,7 +57,7 @@ const plugin = { this.logger = manager.GetLogger(); }, - onUnloadAsync() {}, + onUnloadAsync() { }, - onTickAsync(server) {} -} \ No newline at end of file + onTickAsync(server) { } +}; \ No newline at end of file diff --git a/Plugins/Stats/Cheat/Detection.cs b/Plugins/Stats/Cheat/Detection.cs index 37ac214c4..d359479cc 100644 --- a/Plugins/Stats/Cheat/Detection.cs +++ b/Plugins/Stats/Cheat/Detection.cs @@ -53,7 +53,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat if ((kill.DeathType != IW4Info.MeansOfDeath.MOD_PISTOL_BULLET && kill.DeathType != IW4Info.MeansOfDeath.MOD_RIFLE_BULLET && kill.DeathType != IW4Info.MeansOfDeath.MOD_HEAD_SHOT) || - kill.HitLoc == IW4Info.HitLocation.none) + kill.HitLoc == IW4Info.HitLocation.none || kill.TimeOffset - LastOffset < 0) return new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Any, @@ -131,7 +131,6 @@ namespace IW4MAdmin.Plugins.Stats.Cheat } double currentStrain = Strain.GetStrain(isDamage, kill.Damage, kill.Distance / 0.0254, kill.ViewAngles, Math.Max(50, kill.TimeOffset - LastOffset)); - //double currentWeightedStrain = (currentStrain * ClientStats.SPM) / 170.0; LastOffset = kill.TimeOffset; if (currentStrain > ClientStats.MaxStrain) diff --git a/Plugins/Stats/Helpers/StatManager.cs b/Plugins/Stats/Helpers/StatManager.cs index ad220b3a1..3e43035f6 100644 --- a/Plugins/Stats/Helpers/StatManager.cs +++ b/Plugins/Stats/Helpers/StatManager.cs @@ -470,6 +470,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers Owner = attacker.CurrentServer, Type = GameEvent.EventType.Flag }; + // because we created an event it must be processed by the manager + // even if it didn't really do anything + Manager.GetEventHandler().AddEvent(e); await new CFlag().ExecuteAsync(e); break; } @@ -834,9 +837,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers // calculate how much the KDR should weigh // 1.637 is a Eddie-Generated number that weights the KDR nicely double currentKDR = clientStats.SessionDeaths == 0 ? clientStats.SessionKills : clientStats.SessionKills / clientStats.SessionDeaths; - double alpha = Math.Sqrt(2) / Math.Min(600, clientStats.Kills + clientStats.Deaths); + double alpha = Math.Sqrt(2) / Math.Min(600, Math.Max(clientStats.Kills + clientStats.Deaths, 1)); clientStats.RollingWeightedKDR = (alpha * currentKDR) + (1.0 - alpha) * clientStats.KDR; - double KDRWeight = clientStats.RollingWeightedKDR != 0 ? Math.Round(Math.Pow(clientStats.RollingWeightedKDR, 1.637 / Math.E), 3) : 0; + double KDRWeight = Math.Round(Math.Pow(clientStats.RollingWeightedKDR, 1.637 / Math.E), 3); // calculate the weight of the new play time against last 10 hours of gameplay int totalPlayTime = (clientStats.TimePlayed == 0) ? diff --git a/Plugins/Stats/Plugin.cs b/Plugins/Stats/Plugin.cs index a6bb26326..1c9d40b0f 100644 --- a/Plugins/Stats/Plugin.cs +++ b/Plugins/Stats/Plugin.cs @@ -76,8 +76,12 @@ namespace IW4MAdmin.Plugins.Stats case GameEvent.EventType.ScriptKill: string[] killInfo = (E.Data != null) ? E.Data.Split(';') : new string[0]; if (killInfo.Length >= 13) + { + // todo: remove me + E.Owner.Logger.WriteDebug($"Starting Add script hit (kill) for event with id {E.Id}"); await Manager.AddScriptHit(false, E.Time, E.Origin, E.Target, S.GetHashCode(), S.CurrentMap.Name, killInfo[7], killInfo[8], killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13]); + } break; case GameEvent.EventType.Kill: if (!E.Owner.CustomCallback) @@ -90,8 +94,12 @@ namespace IW4MAdmin.Plugins.Stats case GameEvent.EventType.ScriptDamage: killInfo = (E.Data != null) ? E.Data.Split(';') : new string[0]; if (killInfo.Length >= 13) + { + // todo: remove me + E.Owner.Logger.WriteDebug($"Starting Add script hit (damage) for event with id {E.Id}"); await Manager.AddScriptHit(true, E.Time, E.Origin, E.Target, S.GetHashCode(), S.CurrentMap.Name, killInfo[7], killInfo[8], killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13]); + } break; } } diff --git a/Plugins/Tests/ManagerTests.cs b/Plugins/Tests/ManagerTests.cs index 1f08b075e..4b4d97cae 100644 --- a/Plugins/Tests/ManagerTests.cs +++ b/Plugins/Tests/ManagerTests.cs @@ -43,7 +43,7 @@ namespace Tests public void AddAndRemoveClientsViaJoinShouldSucceed() { var server = Manager.GetServers().First(); - var waiters = new Queue(); + var waiters = new Queue(); int clientStartIndex = 4; int clientNum = 10; @@ -103,7 +103,7 @@ namespace Tests public void AddAndRemoveClientsViaRconShouldSucceed() { var server = Manager.GetServers().First(); - var waiters = new Queue(); + var waiters = new Queue(); int clientIndexStart = 1; int clientNum = 8; @@ -187,7 +187,6 @@ namespace Tests resetEvent.Wait(5000); - } } } diff --git a/SharedLibraryCore/Commands/NativeCommands.cs b/SharedLibraryCore/Commands/NativeCommands.cs index b7142e62a..4f08e1891 100644 --- a/SharedLibraryCore/Commands/NativeCommands.cs +++ b/SharedLibraryCore/Commands/NativeCommands.cs @@ -119,7 +119,17 @@ namespace SharedLibraryCore.Commands { await E.Target.Kick(E.Data, E.Origin); await E.Origin.Tell($"^5{E.Target} ^7{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_KICK_SUCCESS"]}"); - E.Owner.Manager.GetEventHandler().AddEvent(new GameEvent(GameEvent.EventType.Kick, E.Data, E.Origin, E.Target, E.Owner)); + + var e = new GameEvent() + { + Type = GameEvent.EventType.Kick, + Data = E.Data, + Origin = E.Origin, + Target = E.Target, + Owner = E.Owner + }; + + E.Owner.Manager.GetEventHandler().AddEvent(e); } else await E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_KICK_FAIL"]} {E.Target.Name}"); @@ -179,14 +189,17 @@ namespace SharedLibraryCore.Commands { await E.Target.TempBan(Message, length, E.Origin); await E.Origin.Tell($"^5{E.Target} ^7{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_SUCCESS"]} ^5{length.TimeSpanText()}"); - E.Owner.Manager.GetEventHandler().AddEvent(new GameEvent() + + var e = new GameEvent() { Type = GameEvent.EventType.TempBan, Data = E.Data, Origin = E.Origin, Target = E.Target, Owner = E.Owner - }); + }; + + E.Owner.Manager.GetEventHandler().AddEvent(e); } else await E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_FAIL"]} {E.Target.Name}"); @@ -732,7 +745,17 @@ namespace SharedLibraryCore.Commands }; await E.Owner.Manager.GetPenaltyService().Create(newPenalty); - E.Owner.Manager.GetEventHandler().AddEvent(new GameEvent(GameEvent.EventType.Flag, E.Data, E.Origin, E.Target, E.Owner)); + + var e = new GameEvent() + { + Type = GameEvent.EventType.Flag, + Data = E.Data, + Origin = E.Origin, + Target = E.Target, + Owner = E.Owner + }; + + E.Owner.Manager.GetEventHandler().AddEvent(e); await E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_SUCCESS"]} ^5{E.Target.Name}"); } @@ -766,15 +789,16 @@ namespace SharedLibraryCore.Commands await E.Owner.Manager.GetClientService().Update(E.Target); await E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_UNFLAG"]} ^5{E.Target.Name}"); - E.Owner.Manager.GetEventHandler().AddEvent(new GameEvent() + var e = new GameEvent() { Data = E.Data, Origin = E.Origin, Target = E.Target, Owner = E.Owner, Type = GameEvent.EventType.Unflag - }); + }; + E.Owner.Manager.GetEventHandler().AddEvent(e); } else @@ -846,7 +870,17 @@ namespace SharedLibraryCore.Commands await E.Owner.Manager.GetPenaltyService().Create(newReport); await E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_SUCCESS"]); - E.Owner.Manager.GetEventHandler().AddEvent(new GameEvent(GameEvent.EventType.Report, E.Data, E.Origin, E.Target, E.Owner)); + + var e = new GameEvent() + { + Type = GameEvent.EventType.Report, + Data = E.Data, + Origin = E.Origin, + Target = E.Target, + Owner = E.Owner + }; + + E.Owner.Manager.GetEventHandler().AddEvent(e); await E.Owner.ToAdmins(String.Format("^5{0}^7->^1{1}^7: {2}", E.Origin.Name, E.Target.Name, E.Data)); } } diff --git a/Application/API/EventAPI.cs b/SharedLibraryCore/Events/EventAPI.cs similarity index 81% rename from Application/API/EventAPI.cs rename to SharedLibraryCore/Events/EventAPI.cs index 5005bf2c0..4fc37e5e2 100644 --- a/Application/API/EventAPI.cs +++ b/SharedLibraryCore/Events/EventAPI.cs @@ -1,19 +1,15 @@ using System; using System.Collections.Generic; - -using SharedLibraryCore; using SharedLibraryCore.Dtos; -using SharedLibraryCore.Interfaces; -using SharedLibraryCore.Objects; -namespace IW4MAdmin.Application.API +namespace SharedLibraryCore.Events { - class EventApi : IEventApi + public class EventApi { - private const int MaxEvents = 100; - private Queue RecentEvents = new Queue(); + const int MaxEvents = 100; + static Queue RecentEvents = new Queue(); - public IEnumerable GetEvents(bool shouldConsume) + public static IEnumerable GetEvents(bool shouldConsume) { var eventList = RecentEvents.ToArray(); @@ -26,8 +22,9 @@ namespace IW4MAdmin.Application.API return eventList; } - public void OnServerEvent(object sender, GameEvent E) + public static void OnGameEvent(object sender, GameEventArgs eventState) { + var E = eventState.Event; // don't want to clog up the api with unknown events if (E.Type == GameEvent.EventType.Unknown) return; @@ -71,7 +68,7 @@ namespace IW4MAdmin.Application.API /// Adds event to the list and removes first added if reached max capacity /// /// EventInfo to add - private void AddNewEvent(EventInfo info) + private static void AddNewEvent(EventInfo info) { // remove the first added event if (RecentEvents.Count >= MaxEvents) diff --git a/SharedLibraryCore/Event.cs b/SharedLibraryCore/Events/GameEvent.cs similarity index 82% rename from SharedLibraryCore/Event.cs rename to SharedLibraryCore/Events/GameEvent.cs index 8cb6d5733..141bebc25 100644 --- a/SharedLibraryCore/Event.cs +++ b/SharedLibraryCore/Events/GameEvent.cs @@ -44,29 +44,17 @@ namespace SharedLibraryCore StatusUpdate } - public GameEvent(EventType t, string d, Player O, Player T, Server S) - { - Type = t; - Data = d?.Trim(); - Origin = O; - Target = T; - Owner = S; - OnProcessed = new ManualResetEventSlim(); - Time = DateTime.UtcNow; - CurrentEventId++; - Id = CurrentEventId; - } + static long NextEventId; + static long GetNextEventId() => Interlocked.Increment(ref NextEventId); public GameEvent() { - OnProcessed = new ManualResetEventSlim(); + OnProcessed = new SemaphoreSlim(0); + OnProcessed.Release(); Time = DateTime.UtcNow; - CurrentEventId++; - Id = CurrentEventId; + Id = GetNextEventId(); } - private static long CurrentEventId; - public EventType Type; public string Data; // Data is usually the message sent by player public string Message; @@ -75,8 +63,8 @@ namespace SharedLibraryCore public Server Owner; public Boolean Remote = false; public object Extra { get; set; } - public ManualResetEventSlim OnProcessed { get; set; } - public DateTime Time { get; private set; } + public SemaphoreSlim OnProcessed { get; set; } + public DateTime Time { get; set; } public long Id { get; private set; } /// diff --git a/SharedLibraryCore/Events/GameEventArgs.cs b/SharedLibraryCore/Events/GameEventArgs.cs new file mode 100644 index 000000000..b3a795723 --- /dev/null +++ b/SharedLibraryCore/Events/GameEventArgs.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SharedLibraryCore.Events +{ + /// + /// represents the state of a game event for event processing + /// + public class GameEventArgs : System.ComponentModel.AsyncCompletedEventArgs + { + + public GameEventArgs(Exception error, bool cancelled, GameEvent userState) : base(error, cancelled, userState) + { + Event = userState; + } + + /// + /// Game event that occured on a server + /// + public GameEvent Event { get; } + } +} diff --git a/SharedLibraryCore/Interfaces/IEventApi.cs b/SharedLibraryCore/Interfaces/IEventApi.cs deleted file mode 100644 index bd2128a0f..000000000 --- a/SharedLibraryCore/Interfaces/IEventApi.cs +++ /dev/null @@ -1,21 +0,0 @@ -using SharedLibraryCore.Dtos; -using System.Collections.Generic; - -namespace SharedLibraryCore.Interfaces -{ - public interface IEventApi - { - /// - /// Processes event from server as event info - /// - /// Object state from Delegate method call - /// Event to process - void OnServerEvent(object sender, GameEvent E); - /// - /// Get list of recent events - /// - /// specify wether the request should clear all events after retrieving - /// List of recent event - IEnumerable GetEvents(bool shouldConsume); - } -} diff --git a/SharedLibraryCore/Interfaces/IEventHandler.cs b/SharedLibraryCore/Interfaces/IEventHandler.cs index c92c5afd7..3127c1eb8 100644 --- a/SharedLibraryCore/Interfaces/IEventHandler.cs +++ b/SharedLibraryCore/Interfaces/IEventHandler.cs @@ -13,6 +13,6 @@ namespace SharedLibraryCore.Interfaces /// Add a game event event to the queue to be processed /// /// Game event - void AddEvent(GameEvent gameEvent); + bool AddEvent(GameEvent gameEvent); } } diff --git a/SharedLibraryCore/Interfaces/IGameLogReader.cs b/SharedLibraryCore/Interfaces/IGameLogReader.cs index 5e95f293e..56ad2d3ea 100644 --- a/SharedLibraryCore/Interfaces/IGameLogReader.cs +++ b/SharedLibraryCore/Interfaces/IGameLogReader.cs @@ -16,7 +16,7 @@ namespace SharedLibraryCore.Interfaces /// /// /// - ICollection EventsFromLog(Server server, long fileSizeDiff, long startPosition); + ICollection ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition); /// /// how long the log file is /// diff --git a/SharedLibraryCore/Interfaces/IManager.cs b/SharedLibraryCore/Interfaces/IManager.cs index f14e0ff06..88198d143 100644 --- a/SharedLibraryCore/Interfaces/IManager.cs +++ b/SharedLibraryCore/Interfaces/IManager.cs @@ -23,7 +23,6 @@ namespace SharedLibraryCore.Interfaces AliasService GetAliasService(); PenaltyService GetPenaltyService(); IDictionary GetPrivilegedClients(); - IEventApi GetEventApi(); /// /// Get the event handlers /// diff --git a/SharedLibraryCore/RCon/Connection.cs b/SharedLibraryCore/RCon/Connection.cs index 39779f730..2bb6e9980 100644 --- a/SharedLibraryCore/RCon/Connection.cs +++ b/SharedLibraryCore/RCon/Connection.cs @@ -150,7 +150,8 @@ namespace SharedLibraryCore.RCon // will this really prevent flooding? if ((DateTime.Now - LastQuery).TotalMilliseconds < 350) { - await Task.Delay(350); + //await Task.Delay(350); + Thread.Sleep(350); } LastQuery = DateTime.Now; diff --git a/SharedLibraryCore/Server.cs b/SharedLibraryCore/Server.cs index c7955ab5d..47818d125 100644 --- a/SharedLibraryCore/Server.cs +++ b/SharedLibraryCore/Server.cs @@ -118,25 +118,17 @@ namespace SharedLibraryCore { #if !DEBUG string formattedMessage = String.Format(RconParser.GetCommandPrefixes().Say, Message); - - var e = new GameEvent() - { - Message = formattedMessage, - Data = formattedMessage, - Owner = this, - Type = GameEvent.EventType.Broadcast, - }; - - Manager.GetEventHandler().AddEvent(e); #else Logger.WriteVerbose(Message.StripColors()); #endif - Manager.GetEventHandler().AddEvent(new GameEvent() + var e = new GameEvent() { Type = GameEvent.EventType.Broadcast, Data = Message, Owner = this - }); + }; + + Manager.GetEventHandler().AddEvent(e); await Task.CompletedTask; } diff --git a/WebfrontCore/Controllers/API/APIController.cs b/WebfrontCore/Controllers/API/APIController.cs index 1f6990a0c..707329a2c 100644 --- a/WebfrontCore/Controllers/API/APIController.cs +++ b/WebfrontCore/Controllers/API/APIController.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Mvc; using SharedLibraryCore; +using SharedLibraryCore.Events; using System; using System.Collections.Generic; using System.Linq; @@ -14,7 +15,7 @@ namespace WebfrontCore.Controllers.API [HttpGet] public IActionResult Event(bool shouldConsume = true) { - var events = Manager.GetEventApi().GetEvents(shouldConsume); + var events = EventApi.GetEvents(shouldConsume); return Json(events); } diff --git a/WebfrontCore/Controllers/ConsoleController.cs b/WebfrontCore/Controllers/ConsoleController.cs index fb1187cec..b61ce1b7e 100644 --- a/WebfrontCore/Controllers/ConsoleController.cs +++ b/WebfrontCore/Controllers/ConsoleController.cs @@ -48,7 +48,7 @@ namespace WebfrontCore.Controllers Manager.GetEventHandler().AddEvent(remoteEvent); // wait for the event to process - await Task.Run(() => remoteEvent.OnProcessed.Wait(5000)); + await remoteEvent.OnProcessed.WaitAsync(60*1000); var response = server.CommandResult.Where(c => c.ClientId == client.ClientId).ToList(); // remove the added command response