From 9cb6917d28d8ff48bd6ba57d5cea7a2aa411dae5 Mon Sep 17 00:00:00 2001 From: "NGnius (Graham)" Date: Wed, 13 May 2020 16:52:06 -0400 Subject: [PATCH] Create custom error types and error catching --- .../Commands/CommandBuilder.cs | 24 +++---- .../Commands/CommandExceptions.cs | 71 +++++++++++++++++++ .../Commands/CommandManager.cs | 2 +- .../Commands/ICustomCommandEngine.cs | 4 +- .../Commands/SimpleCustomCommandEngine.cs | 18 ++++- .../Commands/SimpleCustomCommandEngine1.cs | 18 ++++- .../Commands/SimpleCustomCommandEngine2.cs | 18 ++++- .../Commands/SimpleCustomCommandEngine3.cs | 18 ++++- GamecraftModdingAPI/Events/EventExceptions.cs | 55 ++++++++++++++ GamecraftModdingAPI/Events/EventManager.cs | 8 +++ .../Events/SimpleEventHandlerEngine.cs | 38 +++++++++- .../GamecraftModdingAPIException.cs | 24 +++++++ GamecraftModdingAPI/Player.cs | 4 +- .../Players/PlayerExceptions.cs | 29 ++++++++ .../Tests/GamecraftModdingAPIPluginTest.cs | 10 ++- 15 files changed, 317 insertions(+), 24 deletions(-) create mode 100644 GamecraftModdingAPI/Commands/CommandExceptions.cs create mode 100644 GamecraftModdingAPI/Events/EventExceptions.cs create mode 100644 GamecraftModdingAPI/GamecraftModdingAPIException.cs create mode 100644 GamecraftModdingAPI/Players/PlayerExceptions.cs diff --git a/GamecraftModdingAPI/Commands/CommandBuilder.cs b/GamecraftModdingAPI/Commands/CommandBuilder.cs index d5f1b51..7bd5ed9 100644 --- a/GamecraftModdingAPI/Commands/CommandBuilder.cs +++ b/GamecraftModdingAPI/Commands/CommandBuilder.cs @@ -148,11 +148,11 @@ namespace GamecraftModdingAPI.Commands { if (string.IsNullOrWhiteSpace(name)) { - throw new InvalidOperationException("Command name must be defined before FromExisting() is called"); + throw new CommandParameterMissingException("Command name must be defined before FromExisting() is called"); } if (!ExistingCommands.Exists(name)) { - throw new InvalidOperationException("Command cannot be built from existing because it does not exist."); + throw new CommandNotFoundException("Command cannot be built from existing because it does not exist."); } fromExisting = true; return new SimpleCustomCommandEngine( @@ -170,11 +170,11 @@ namespace GamecraftModdingAPI.Commands { if (string.IsNullOrWhiteSpace(name)) { - throw new InvalidOperationException("Command name must be defined before FromExisting() is called"); + throw new CommandParameterMissingException("Command name must be defined before FromExisting() is called"); } if (!ExistingCommands.Exists(name)) { - throw new InvalidOperationException("Command cannot be built from existing because it does not exist."); + throw new CommandNotFoundException("Command cannot be built from existing because it does not exist."); } fromExisting = true; return new SimpleCustomCommandEngine( @@ -193,11 +193,11 @@ namespace GamecraftModdingAPI.Commands { if (string.IsNullOrWhiteSpace(name)) { - throw new InvalidOperationException("Command name must be defined before FromExisting() is called"); + throw new CommandParameterMissingException("Command name must be defined before FromExisting() is called"); } if (!ExistingCommands.Exists(name)) { - throw new InvalidOperationException("Command cannot be built from existing because it does not exist."); + throw new CommandNotFoundException("Command cannot be built from existing because it does not exist."); } fromExisting = true; return new SimpleCustomCommandEngine( @@ -217,11 +217,11 @@ namespace GamecraftModdingAPI.Commands { if (string.IsNullOrWhiteSpace(name)) { - throw new InvalidOperationException("Command name must be defined before FromExisting() is called"); + throw new CommandParameterMissingException("Command name must be defined before FromExisting() is called"); } if (!ExistingCommands.Exists(name)) { - throw new InvalidOperationException("Command cannot be built from existing because it does not exist."); + throw new CommandNotFoundException("Command cannot be built from existing because it does not exist."); } fromExisting = true; return new SimpleCustomCommandEngine( @@ -239,19 +239,19 @@ namespace GamecraftModdingAPI.Commands { if (string.IsNullOrWhiteSpace(name)) { - throw new InvalidOperationException("Command name must be defined before Build() is called"); + throw new CommandParameterMissingException("Command name must be defined before Build() is called"); } if (fromExisting) { - throw new InvalidOperationException("Command was already built by FromExisting()"); + throw new CommandAlreadyBuiltException("Command was already built by FromExisting()"); } if (commandEngine == null) { - throw new InvalidOperationException("Command action must be defined before Build() is called"); + throw new CommandParameterMissingException("Command action must be defined before Build() is called"); } if (string.IsNullOrWhiteSpace(description)) { - Logging.LogWarning($"Command {name} was built without a description"); + Logging.LogWarning($"Command {FullName()} was built without a description"); } if (register) { diff --git a/GamecraftModdingAPI/Commands/CommandExceptions.cs b/GamecraftModdingAPI/Commands/CommandExceptions.cs new file mode 100644 index 0000000..39502a7 --- /dev/null +++ b/GamecraftModdingAPI/Commands/CommandExceptions.cs @@ -0,0 +1,71 @@ +using System; +namespace GamecraftModdingAPI.Commands +{ + public class CommandException : GamecraftModdingAPIException + { + public CommandException() : base() {} + + public CommandException(string msg) : base(msg) {} + + public CommandException(string msg, Exception innerException) : base(msg, innerException) {} + } + + public class CommandNotFoundException : CommandException + { + public CommandNotFoundException() + { + } + + public CommandNotFoundException(string msg) : base(msg) + { + } + } + + public class CommandAlreadyExistsException : CommandException + { + public CommandAlreadyExistsException() + { + } + + public CommandAlreadyExistsException(string msg) : base(msg) + { + } + } + + public class CommandRuntimeException : CommandException + { + public CommandRuntimeException() + { + } + + public CommandRuntimeException(string msg) : base(msg) + { + } + + public CommandRuntimeException(string msg, Exception innerException) : base(msg, innerException) + { + } + } + + public class CommandAlreadyBuiltException : CommandException + { + public CommandAlreadyBuiltException() + { + } + + public CommandAlreadyBuiltException(string msg) : base(msg) + { + } + } + + public class CommandParameterMissingException : CommandException + { + public CommandParameterMissingException() + { + } + + public CommandParameterMissingException(string msg) : base(msg) + { + } + } +} diff --git a/GamecraftModdingAPI/Commands/CommandManager.cs b/GamecraftModdingAPI/Commands/CommandManager.cs index 986405e..876d634 100644 --- a/GamecraftModdingAPI/Commands/CommandManager.cs +++ b/GamecraftModdingAPI/Commands/CommandManager.cs @@ -24,7 +24,7 @@ namespace GamecraftModdingAPI.Commands { if (ExistsCommand(engine)) { - Logging.LogWarning($"Command {engine.Name} already exists!"); + throw new CommandAlreadyExistsException($"Command {engine.Name} already exists"); } _customCommands[engine.Name] = engine; if (_lastEngineRoot != null) diff --git a/GamecraftModdingAPI/Commands/ICustomCommandEngine.cs b/GamecraftModdingAPI/Commands/ICustomCommandEngine.cs index aafd257..1704e84 100644 --- a/GamecraftModdingAPI/Commands/ICustomCommandEngine.cs +++ b/GamecraftModdingAPI/Commands/ICustomCommandEngine.cs @@ -12,7 +12,9 @@ using GamecraftModdingAPI.Engines; namespace GamecraftModdingAPI.Commands { /// - /// Engine interface to handle command operations + /// Engine interface to handle command operations. + /// If you are using implementing this yourself, you must manually register the command. + /// See SimpleCustomCommandEngine's Ready() and Dispose() methods for an example of command registration. /// public interface ICustomCommandEngine : IApiEngine { diff --git a/GamecraftModdingAPI/Commands/SimpleCustomCommandEngine.cs b/GamecraftModdingAPI/Commands/SimpleCustomCommandEngine.cs index ebf0758..2ce60a9 100644 --- a/GamecraftModdingAPI/Commands/SimpleCustomCommandEngine.cs +++ b/GamecraftModdingAPI/Commands/SimpleCustomCommandEngine.cs @@ -6,6 +6,8 @@ using System.Threading.Tasks; using Svelto.ECS; +using GamecraftModdingAPI.Utility; + namespace GamecraftModdingAPI.Commands { /// @@ -42,7 +44,7 @@ namespace GamecraftModdingAPI.Commands public void Ready() { GamecraftModdingAPI.Utility.Logging.MetaDebugLog($"Registering SimpleCustomCommandEngine {this.Name}"); - CommandRegistrationHelper.Register(this.Name, this.runCommand, this.Description); + CommandRegistrationHelper.Register(this.Name, this.InvokeCatchError, this.Description); } /// @@ -62,5 +64,19 @@ namespace GamecraftModdingAPI.Commands { runCommand(); } + + private void InvokeCatchError() + { + try + { + runCommand(); + } + catch (Exception e) + { + CommandRuntimeException wrappedException = new CommandRuntimeException($"Command {Name} threw an exception when executed", e); + Logging.LogWarning(wrappedException.ToString()); + Logging.CommandLogError(wrappedException.ToString()); + } + } } } diff --git a/GamecraftModdingAPI/Commands/SimpleCustomCommandEngine1.cs b/GamecraftModdingAPI/Commands/SimpleCustomCommandEngine1.cs index a8dd96b..bac16ef 100644 --- a/GamecraftModdingAPI/Commands/SimpleCustomCommandEngine1.cs +++ b/GamecraftModdingAPI/Commands/SimpleCustomCommandEngine1.cs @@ -6,6 +6,8 @@ using System.Threading.Tasks; using Svelto.ECS; +using GamecraftModdingAPI.Utility; + namespace GamecraftModdingAPI.Commands { /// @@ -33,7 +35,7 @@ namespace GamecraftModdingAPI.Commands public void Ready() { GamecraftModdingAPI.Utility.Logging.MetaDebugLog($"Registering SimpleCustomCommandEngine {this.Name}"); - CommandRegistrationHelper.Register(this.Name, this.runCommand, this.Description); + CommandRegistrationHelper.Register(this.Name, this.InvokeCatchError, this.Description); } /// @@ -52,6 +54,20 @@ namespace GamecraftModdingAPI.Commands public void Invoke(A a) { runCommand(a); + } + + private void InvokeCatchError(A a) + { + try + { + runCommand(a); + } + catch (Exception e) + { + CommandRuntimeException wrappedException = new CommandRuntimeException($"Command {Name} threw an exception when executed", e); + Logging.LogWarning(wrappedException.ToString()); + Logging.CommandLogError(wrappedException.ToString()); + } } } } diff --git a/GamecraftModdingAPI/Commands/SimpleCustomCommandEngine2.cs b/GamecraftModdingAPI/Commands/SimpleCustomCommandEngine2.cs index 8333cd2..4b2b38d 100644 --- a/GamecraftModdingAPI/Commands/SimpleCustomCommandEngine2.cs +++ b/GamecraftModdingAPI/Commands/SimpleCustomCommandEngine2.cs @@ -6,6 +6,8 @@ using System.Threading.Tasks; using Svelto.ECS; +using GamecraftModdingAPI.Utility; + namespace GamecraftModdingAPI.Commands { /// @@ -33,7 +35,7 @@ namespace GamecraftModdingAPI.Commands public void Ready() { GamecraftModdingAPI.Utility.Logging.MetaDebugLog($"Registering SimpleCustomCommandEngine {this.Name}"); - CommandRegistrationHelper.Register(this.Name, this.runCommand, this.Description); + CommandRegistrationHelper.Register(this.Name, this.InvokeCatchError, this.Description); } /// @@ -52,6 +54,20 @@ namespace GamecraftModdingAPI.Commands public void Invoke(A a, B b) { runCommand(a, b); + } + + private void InvokeCatchError(A a, B b) + { + try + { + runCommand(a, b); + } + catch (Exception e) + { + CommandRuntimeException wrappedException = new CommandRuntimeException($"Command {Name} threw an exception when executed", e); + Logging.LogWarning(wrappedException.ToString()); + Logging.CommandLogError(wrappedException.ToString()); + } } } } diff --git a/GamecraftModdingAPI/Commands/SimpleCustomCommandEngine3.cs b/GamecraftModdingAPI/Commands/SimpleCustomCommandEngine3.cs index e5570c3..c9c942e 100644 --- a/GamecraftModdingAPI/Commands/SimpleCustomCommandEngine3.cs +++ b/GamecraftModdingAPI/Commands/SimpleCustomCommandEngine3.cs @@ -6,6 +6,8 @@ using System.Threading.Tasks; using Svelto.ECS; +using GamecraftModdingAPI.Utility; + namespace GamecraftModdingAPI.Commands { /// @@ -33,7 +35,7 @@ namespace GamecraftModdingAPI.Commands public void Ready() { GamecraftModdingAPI.Utility.Logging.MetaDebugLog($"Registering SimpleCustomCommandEngine {this.Name}"); - CommandRegistrationHelper.Register(this.Name, this.runCommand, this.Description); + CommandRegistrationHelper.Register(this.Name, this.InvokeCatchError, this.Description); } /// @@ -52,6 +54,20 @@ namespace GamecraftModdingAPI.Commands public void Invoke(A a, B b, C c) { runCommand(a, b, c); + } + + private void InvokeCatchError(A a, B b, C c) + { + try + { + runCommand(a, b, c); + } + catch (Exception e) + { + CommandRuntimeException wrappedException = new CommandRuntimeException($"Command {Name} threw an exception when executed", e); + Logging.LogWarning(wrappedException.ToString()); + Logging.CommandLogError(wrappedException.ToString()); + } } } } diff --git a/GamecraftModdingAPI/Events/EventExceptions.cs b/GamecraftModdingAPI/Events/EventExceptions.cs new file mode 100644 index 0000000..ee29480 --- /dev/null +++ b/GamecraftModdingAPI/Events/EventExceptions.cs @@ -0,0 +1,55 @@ +using System; +namespace GamecraftModdingAPI.Events +{ + public class EventException : GamecraftModdingAPIException + { + public EventException() + { + } + + public EventException(string message) : base(message) + { + } + + public EventException(string message, Exception innerException) : base(message, innerException) + { + } + } + + public class EventNotFoundException : EventException + { + public EventNotFoundException() + { + } + + public EventNotFoundException(string message) : base(message) + { + } + } + + public class EventAlreadyExistsException : EventException + { + public EventAlreadyExistsException() + { + } + + public EventAlreadyExistsException(string message) : base(message) + { + } + } + + public class EventRuntimeException : EventException + { + public EventRuntimeException() + { + } + + public EventRuntimeException(string message) : base(message) + { + } + + public EventRuntimeException(string message, Exception innerException) : base(message, innerException) + { + } + } +} diff --git a/GamecraftModdingAPI/Events/EventManager.cs b/GamecraftModdingAPI/Events/EventManager.cs index 227c7fd..efbf377 100644 --- a/GamecraftModdingAPI/Events/EventManager.cs +++ b/GamecraftModdingAPI/Events/EventManager.cs @@ -26,6 +26,10 @@ namespace GamecraftModdingAPI.Events public static void AddEventHandler(IEventHandlerEngine engine) { + if (ExistsEventHandler(engine)) + { + throw new EventAlreadyExistsException($"IEventHandlerEngine {engine.Name} already exists"); + } _eventHandlers[engine.Name] = engine; if (_lastEngineRoot != null) { @@ -63,6 +67,10 @@ namespace GamecraftModdingAPI.Events public static void AddEventEmitter(IEventEmitterEngine engine) { + if (ExistsEventEmitter(engine)) + { + throw new EventAlreadyExistsException($"IEventEmitterEngine {engine.Name} already exists"); + } _eventEmitters[engine.Name] = engine; if (_lastEngineRoot != null) { diff --git a/GamecraftModdingAPI/Events/SimpleEventHandlerEngine.cs b/GamecraftModdingAPI/Events/SimpleEventHandlerEngine.cs index b656273..133ad29 100644 --- a/GamecraftModdingAPI/Events/SimpleEventHandlerEngine.cs +++ b/GamecraftModdingAPI/Events/SimpleEventHandlerEngine.cs @@ -6,6 +6,8 @@ using System.Threading.Tasks; using Svelto.ECS; +using GamecraftModdingAPI.Utility; + namespace GamecraftModdingAPI.Events { /// @@ -31,7 +33,7 @@ namespace GamecraftModdingAPI.Events if (entityView.type.Equals(this.type)) { isActivated = true; - onActivated.Invoke(entitiesDB); + onActivatedInvokeCatchError(entitiesDB); } } @@ -43,6 +45,10 @@ namespace GamecraftModdingAPI.Events public void Activate(bool handle = false) { isActivated = true; + if (handle && entitiesDB != null) + { + onActivatedInvokeCatchError(entitiesDB); + } } public void Ready() { } @@ -52,7 +58,7 @@ namespace GamecraftModdingAPI.Events if (entityView.type.Equals(this.type) && isActivated) { isActivated = false; - onDestroyed.Invoke(entitiesDB); + onDestroyedInvokeCatchError(entitiesDB); } } @@ -61,7 +67,7 @@ namespace GamecraftModdingAPI.Events if (isActivated) { isActivated = false; - onDestroyed.Invoke(entitiesDB); + onDestroyedInvokeCatchError(entitiesDB); } } @@ -91,6 +97,32 @@ namespace GamecraftModdingAPI.Events this.onDestroyed = removed; } + private void onActivatedInvokeCatchError(EntitiesDB _entitiesDB) + { + try + { + onActivated.Invoke(_entitiesDB); + } + catch (Exception e) + { + EventRuntimeException wrappedException = new EventRuntimeException($"EventHandler {Name} threw an exception when activated", e); + Logging.LogWarning(wrappedException.ToString()); + } + } + + private void onDestroyedInvokeCatchError(EntitiesDB _entitiesDB) + { + try + { + onDestroyed.Invoke(_entitiesDB); + } + catch (Exception e) + { + EventRuntimeException wrappedException = new EventRuntimeException($"EventHandler {Name} threw an exception when destroyed", e); + Logging.LogWarning(wrappedException.ToString()); + } + } + public SimpleEventHandlerEngine(Action activated, Action removed, EventType type, string name, bool simple = true) : this((EntitiesDB _) => { activated.Invoke(); }, (EntitiesDB _) => { removed.Invoke(); }, (int)type, name) { } diff --git a/GamecraftModdingAPI/GamecraftModdingAPIException.cs b/GamecraftModdingAPI/GamecraftModdingAPIException.cs new file mode 100644 index 0000000..bc944aa --- /dev/null +++ b/GamecraftModdingAPI/GamecraftModdingAPIException.cs @@ -0,0 +1,24 @@ +using System; +using System.Runtime.Serialization; + +namespace GamecraftModdingAPI +{ + public class GamecraftModdingAPIException : Exception + { + public GamecraftModdingAPIException() + { + } + + public GamecraftModdingAPIException(string message) : base(message) + { + } + + public GamecraftModdingAPIException(string message, Exception innerException) : base(message, innerException) + { + } + + protected GamecraftModdingAPIException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/GamecraftModdingAPI/Player.cs b/GamecraftModdingAPI/Player.cs index 29546da..965ba45 100644 --- a/GamecraftModdingAPI/Player.cs +++ b/GamecraftModdingAPI/Player.cs @@ -50,7 +50,7 @@ namespace GamecraftModdingAPI this.Id = id; if (!Exists(id)) { - throw new InvalidOperationException($"No player with id {id} exists"); + throw new PlayerNotFoundException($"No player with id {id} exists"); } this.Type = playerEngine.GetLocalPlayer() == id ? PlayerType.Local : PlayerType.Remote; } @@ -73,7 +73,7 @@ namespace GamecraftModdingAPI } if (this.Id == uint.MaxValue) { - throw new InvalidOperationException($"No player of {player} type exists"); + throw new PlayerNotFoundException($"No player of {player} type exists"); } this.Type = player; } diff --git a/GamecraftModdingAPI/Players/PlayerExceptions.cs b/GamecraftModdingAPI/Players/PlayerExceptions.cs new file mode 100644 index 0000000..94312b9 --- /dev/null +++ b/GamecraftModdingAPI/Players/PlayerExceptions.cs @@ -0,0 +1,29 @@ +using System; +namespace GamecraftModdingAPI.Players +{ + public class PlayerException : GamecraftModdingAPIException + { + public PlayerException() + { + } + + public PlayerException(string message) : base(message) + { + } + + public PlayerException(string message, Exception innerException) : base(message, innerException) + { + } + } + + public class PlayerNotFoundException : PlayerException + { + public PlayerNotFoundException() + { + } + + public PlayerNotFoundException(string message) : base(message) + { + } + } +} diff --git a/GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs b/GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs index c241159..183b4f0 100644 --- a/GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs +++ b/GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs @@ -25,7 +25,8 @@ namespace GamecraftModdingAPI.Tests #if DEBUG : IllusionPlugin.IEnhancedPlugin #endif - { + { + private static Harmony harmony { get; set; } public string[] Filter { get; } = new string[] { "Gamecraft", "GamecraftPreview" }; @@ -80,6 +81,9 @@ namespace GamecraftModdingAPI.Tests EventManager.AddEventHandler(new SimpleEventHandlerEngine(() => { Logging.Log("Game Mode Build Switched To event!"); }, () => { }, EventType.BuildSwitchedTo, "buildswitch API debug")); + EventManager.AddEventHandler(new SimpleEventHandlerEngine(() => { throw new Exception(""); }, () => {}, + EventType.Menu, "menu activated API error thrower test")); + // debug/test commands if (Dependency.Hell("ExtraCommands")) { @@ -148,6 +152,10 @@ namespace GamecraftModdingAPI.Tests .Action(() => new Player(Players.PlayerType.Local).GetBlockLookedAt().Type = BlockIDs.AluminiumCube) .Build(); + CommandBuilder.Builder("Error", "Throw an error to make sure SimpleCustomCommandEngine's wrapper catches it.") + .Action(() => { throw new Exception("Error Command always throws an error"); }) + .Build(); + /* CommandManager.AddCommand(new SimpleCustomCommandEngine((float d) => { UnityEngine.Camera.main.fieldOfView = d; }, "SetFOV", "Set the player camera's field of view"));