Fix events not firing and event exception handling

Copying to Plugins folder on build
Registering deterministic game engines automatically
Each event handler is wrapped so if one fails it will still trigger the rest
This commit is contained in:
Norbi Peti 2021-05-23 20:53:55 +02:00
parent f5e3010e48
commit e8515ef42b
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
12 changed files with 111 additions and 64 deletions

View file

@ -27,8 +27,8 @@ namespace TechbloxModdingAPI.App
/// An event that fires whenever the main menu is loaded. /// An event that fires whenever the main menu is loaded.
/// </summary> /// </summary>
public static event EventHandler<MenuEventArgs> EnterMenu public static event EventHandler<MenuEventArgs> EnterMenu
{ {
add => appEngine.EnterMenu += value; add => appEngine.EnterMenu += ExceptionUtil.WrapHandler(value);
remove => appEngine.EnterMenu -= value; remove => appEngine.EnterMenu -= value;
} }
@ -37,7 +37,7 @@ namespace TechbloxModdingAPI.App
/// </summary> /// </summary>
public static event EventHandler<MenuEventArgs> ExitMenu public static event EventHandler<MenuEventArgs> ExitMenu
{ {
add => appEngine.ExitMenu += value; add => appEngine.ExitMenu += ExceptionUtil.WrapHandler(value);
remove => appEngine.ExitMenu -= value; remove => appEngine.ExitMenu -= value;
} }

View file

@ -93,7 +93,7 @@ namespace TechbloxModdingAPI.App
/// </summary> /// </summary>
public static event EventHandler<GameEventArgs> Simulate public static event EventHandler<GameEventArgs> Simulate
{ {
add => buildSimEventEngine.SimulationMode += value; add => buildSimEventEngine.SimulationMode += ExceptionUtil.WrapHandler(value);
remove => buildSimEventEngine.SimulationMode -= value; remove => buildSimEventEngine.SimulationMode -= value;
} }
@ -103,7 +103,7 @@ namespace TechbloxModdingAPI.App
/// </summary> /// </summary>
public static event EventHandler<GameEventArgs> Edit public static event EventHandler<GameEventArgs> Edit
{ {
add => buildSimEventEngine.BuildMode += value; add => buildSimEventEngine.BuildMode += ExceptionUtil.WrapHandler(value);
remove => buildSimEventEngine.BuildMode -= value; remove => buildSimEventEngine.BuildMode -= value;
} }
@ -112,7 +112,7 @@ namespace TechbloxModdingAPI.App
/// </summary> /// </summary>
public static event EventHandler<GameEventArgs> Enter public static event EventHandler<GameEventArgs> Enter
{ {
add => gameEngine.EnterGame += value; add => gameEngine.EnterGame += ExceptionUtil.WrapHandler(value);
remove => gameEngine.EnterGame -= value; remove => gameEngine.EnterGame -= value;
} }
@ -122,7 +122,7 @@ namespace TechbloxModdingAPI.App
/// </summary> /// </summary>
public static event EventHandler<GameEventArgs> Exit public static event EventHandler<GameEventArgs> Exit
{ {
add => gameEngine.ExitGame += value; add => gameEngine.ExitGame += ExceptionUtil.WrapHandler(value);
remove => gameEngine.ExitGame -= value; remove => gameEngine.ExitGame -= value;
} }
@ -478,12 +478,8 @@ namespace TechbloxModdingAPI.App
{ {
GameEngineManager.AddGameEngine(gameEngine); GameEngineManager.AddGameEngine(gameEngine);
GameEngineManager.AddGameEngine(debugOverlayEngine); GameEngineManager.AddGameEngine(debugOverlayEngine);
GameEngineManager.AddGameEngine(buildSimEventEngine);
MenuEngineManager.AddMenuEngine(menuEngine); MenuEngineManager.AddMenuEngine(menuEngine);
} }
internal static void InitDeterministic(StateSyncRegistrationHelper stateSyncReg)
{
stateSyncReg.AddDeterministicEngine(buildSimEventEngine);
}
} }
} }

View file

@ -1,26 +0,0 @@
using System;
using System.Reflection;
using RobocraftX.CR.MainGame;
using RobocraftX.StateSync;
using HarmonyLib;
namespace TechbloxModdingAPI.App
{
[HarmonyPatch]
class StateSyncRegPatch
{
public static void Postfix(StateSyncRegistrationHelper stateSyncReg)
{
// register sim/build events engines
Game.InitDeterministic(stateSyncReg);
}
[HarmonyTargetMethod]
public static MethodBase Target()
{
return AccessTools.Method(typeof(MainGameCompositionRoot), "DeterministicCompose").MakeGenericMethod(typeof(object));
}
}
}

View file

@ -74,7 +74,7 @@ namespace TechbloxModdingAPI
/// </summary> /// </summary>
public static event EventHandler<BlockPlacedRemovedEventArgs> Placed public static event EventHandler<BlockPlacedRemovedEventArgs> Placed
{ {
add => BlockEventsEngine.Placed += value; add => BlockEventsEngine.Placed += ExceptionUtil.WrapHandler(value);
remove => BlockEventsEngine.Placed -= value; remove => BlockEventsEngine.Placed -= value;
} }
@ -83,7 +83,7 @@ namespace TechbloxModdingAPI
/// </summary> /// </summary>
public static event EventHandler<BlockPlacedRemovedEventArgs> Removed public static event EventHandler<BlockPlacedRemovedEventArgs> Removed
{ {
add => BlockEventsEngine.Removed += value; add => BlockEventsEngine.Removed += ExceptionUtil.WrapHandler(value);
remove => BlockEventsEngine.Removed -= value; remove => BlockEventsEngine.Removed -= value;
} }

View file

@ -0,0 +1,55 @@
using System.Reflection;
using HarmonyLib;
using RobocraftX;
using RobocraftX.CR.MainGame;
using RobocraftX.FrontEnd;
using RobocraftX.StateSync;
using Svelto.ECS;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Engines
{
[HarmonyPatch]
class GameLoadedEnginePatch
{
public static void Postfix(StateSyncRegistrationHelper stateSyncReg)
{
// register all game engines, including deterministic
GameEngineManager.RegisterEngines(stateSyncReg);
}
public static MethodBase TargetMethod()
{
return AccessTools.Method(typeof(MainGameCompositionRoot), "DeterministicCompose").MakeGenericMethod(typeof(object));
}
}
[HarmonyPatch]
class MenuLoadedEnginePatch
{
public static void Postfix(EnginesRoot enginesRoot)
{
// register menu engines
MenuEngineManager.RegisterEngines(enginesRoot);
}
public static MethodBase TargetMethod()
{
return AccessTools.Method(typeof(FrontEndCompositionRoot), "Compose").MakeGenericMethod(typeof(object));
}
}
[HarmonyPatch]
class FullGameCreatedEnginePatch
{
public static void Postfix(FullGameCompositionRoot __instance)
{
FullGameFields.Init(__instance);
}
public static MethodBase TargetMethod()
{
return AccessTools.DeclaredConstructor(typeof(FullGameCompositionRoot));
}
}
}

View file

@ -75,10 +75,10 @@ namespace TechbloxModdingAPI
Block.Init(); Block.Init();
BlockGroup.Init(); BlockGroup.Init();
Wire.Init(); Wire.Init();
Logging.MetaDebugLog($"Initializing Client");
GameClient.Init(); GameClient.Init();
Client.Init(); Client.Init();
Game.Init(); Game.Init();
//CustomBlock.Init();
// init UI // init UI
Interface.IMGUI.Constants.Init(); Interface.IMGUI.Constants.Init();
Interface.IMGUI.IMGUIManager.Init(); Interface.IMGUI.IMGUIManager.Init();

View file

@ -1085,4 +1085,8 @@
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<!--End Dependencies--> <!--End Dependencies-->
<Target Name="CopyToPlugins" AfterTargets="AfterBuild">
<Copy SourceFiles="$(MSBuildProjectDirectory)\$(OutputPath)\TechbloxModdingAPI.dll" DestinationFolder="$(MSBuildProjectDirectory)\..\..\ref\Plugins" />
</Target>
</Project> </Project>

View file

@ -13,6 +13,7 @@ using RobocraftX.Common.Input;
using TechbloxModdingAPI.Blocks; using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Commands; using TechbloxModdingAPI.Commands;
using TechbloxModdingAPI.Input; using TechbloxModdingAPI.Input;
using TechbloxModdingAPI.Interface.IMGUI;
using TechbloxModdingAPI.Players; using TechbloxModdingAPI.Players;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
@ -210,10 +211,10 @@ namespace TechbloxModdingAPI.Tests
Logging.Log("Compatible TechbloxScripting detected"); Logging.Log("Compatible TechbloxScripting detected");
} }
// Interface test // Interface test
/*Interface.IMGUI.Group uiGroup = new Group(new Rect(20, 20, 200, 500), "TechbloxModdingAPI_UITestGroup", true); /*Group uiGroup = new Group(new Rect(20, 20, 200, 500), "TechbloxModdingAPI_UITestGroup", true);
Interface.IMGUI.Button button = new Button("TEST"); var button = new Button("TEST");
button.OnClick += (b, __) => { Logging.MetaDebugLog($"Click on {((Interface.IMGUI.Button)b).Name}");}; button.OnClick += (b, __) => { Logging.MetaDebugLog($"Click on {((Interface.IMGUI.Button)b).Name}");};
Interface.IMGUI.Button button2 = new Button("TEST2"); var button2 = new Button("TEST2");
button2.OnClick += (b, __) => { Logging.MetaDebugLog($"Click on {((Interface.IMGUI.Button)b).Name}");}; button2.OnClick += (b, __) => { Logging.MetaDebugLog($"Click on {((Interface.IMGUI.Button)b).Name}");};
Text uiText = new Text("This is text!", multiline: true); Text uiText = new Text("This is text!", multiline: true);
uiText.OnEdit += (t, txt) => { Logging.MetaDebugLog($"Text in {((Text)t).Name} is now '{txt}'"); }; uiText.OnEdit += (t, txt) => { Logging.MetaDebugLog($"Text in {((Text)t).Name} is now '{txt}'"); };

View file

@ -66,7 +66,7 @@ namespace TechbloxModdingAPI.Tests
// flow control // flow control
Game.Enter += (sender, args) => { GameTests().RunOn(RobocraftX.Schedulers.Lean.EveryFrameStepRunner_TimeRunningAndStopped); }; Game.Enter += (sender, args) => { GameTests().RunOn(RobocraftX.Schedulers.Lean.EveryFrameStepRunner_TimeRunningAndStopped); };
Game.Exit += (s, a) => state = "ReturningFromGame"; Game.Exit += (s, a) => state = "ReturningFromGame";
Client.EnterMenu += (sender, args) => Client.EnterMenu += (sender, args) =>
{ {
if (state == "EnteringMenu") if (state == "EnteringMenu")
{ {

View file

@ -6,7 +6,7 @@ namespace TechbloxModdingAPI.Utility
public static class ExceptionUtil public static class ExceptionUtil
{ {
/// <summary> /// <summary>
/// Invokes an event in a try-catch block to avoid propagating exceptions. /// Invokes an event with a null-check.
/// </summary> /// </summary>
/// <param name="handler">The event to emit, can be null</param> /// <param name="handler">The event to emit, can be null</param>
/// <param name="sender">Event sender</param> /// <param name="sender">Event sender</param>
@ -14,16 +14,30 @@ namespace TechbloxModdingAPI.Utility
/// <typeparam name="T">Type of the event arguments</typeparam> /// <typeparam name="T">Type of the event arguments</typeparam>
public static void InvokeEvent<T>(EventHandler<T> handler, object sender, T args) public static void InvokeEvent<T>(EventHandler<T> handler, object sender, T args)
{ {
try handler?.Invoke(sender, args);
}
/// <summary>
/// Wraps the event handler in a try-catch block to avoid propagating exceptions.
/// </summary>
/// <param name="handler">The handler to wrap (not null)</param>
/// <typeparam name="T">Type of the event arguments</typeparam>
/// <returns>The wrapped handler</returns>
public static EventHandler<T> WrapHandler<T>(EventHandler<T> handler)
{
return (sender, e) =>
{ {
handler?.Invoke(sender, args); try
} {
catch (Exception e) handler(sender, e);
{ }
EventRuntimeException wrappedException = catch (Exception e1)
new EventRuntimeException($"EventHandler with arg type {typeof(T).Name} threw an exception", e); {
Logging.LogWarning(wrappedException.ToString()); EventRuntimeException wrappedException =
} new EventRuntimeException($"EventHandler with arg type {typeof(T).Name} threw an exception", e1);
Logging.LogWarning(wrappedException.ToString());
}
};
} }
} }
} }

View file

@ -4,6 +4,7 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using RobocraftX.StateSync;
using Svelto.ECS; using Svelto.ECS;
using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Engines;
@ -60,18 +61,20 @@ namespace TechbloxModdingAPI.Utility
} }
} }
public static void RegisterEngines(EnginesRoot enginesRoot) public static void RegisterEngines(StateSyncRegistrationHelper helper)
{ {
var enginesRoot = helper.enginesRoot;
_lastEngineRoot = enginesRoot; _lastEngineRoot = enginesRoot;
IEntityFactory factory = enginesRoot.GenerateEntityFactory(); IEntityFactory factory = enginesRoot.GenerateEntityFactory();
foreach (var key in _gameEngines.Keys) foreach (var key in _gameEngines.Keys)
{ {
Logging.MetaDebugLog($"Registering Game IApiEngine {_gameEngines[key].Name}"); Logging.MetaDebugLog($"Registering Game IApiEngine {_gameEngines[key].Name}");
enginesRoot.AddEngine(_gameEngines[key]); if (_gameEngines[key] is IDeterministicEngine detEngine)
if (typeof(IFactoryEngine).IsAssignableFrom(_gameEngines[key].GetType())) helper.AddDeterministicEngine(detEngine);
{ else
((IFactoryEngine)_gameEngines[key]).Factory = factory; enginesRoot.AddEngine(_gameEngines[key]);
} if (_gameEngines[key] is IFactoryEngine factEngine)
factEngine.Factory = factory;
} }
} }
} }

View file

@ -69,9 +69,9 @@ namespace TechbloxModdingAPI.Utility
{ {
Logging.MetaDebugLog($"Registering Menu IApiEngine {_menuEngines[key].Name}"); Logging.MetaDebugLog($"Registering Menu IApiEngine {_menuEngines[key].Name}");
enginesRoot.AddEngine(_menuEngines[key]); enginesRoot.AddEngine(_menuEngines[key]);
if (typeof(IFactoryEngine).IsAssignableFrom(_menuEngines[key].GetType())) if (_menuEngines[key] is IFactoryEngine factEngine)
{ {
((IFactoryEngine)_menuEngines[key]).Factory = factory; factEngine.Factory = factory;
} }
} }
} }