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.
/// </summary>
public static event EventHandler<MenuEventArgs> EnterMenu
{
add => appEngine.EnterMenu += value;
{
add => appEngine.EnterMenu += ExceptionUtil.WrapHandler(value);
remove => appEngine.EnterMenu -= value;
}
@ -37,7 +37,7 @@ namespace TechbloxModdingAPI.App
/// </summary>
public static event EventHandler<MenuEventArgs> ExitMenu
{
add => appEngine.ExitMenu += value;
add => appEngine.ExitMenu += ExceptionUtil.WrapHandler(value);
remove => appEngine.ExitMenu -= value;
}

View file

@ -93,7 +93,7 @@ namespace TechbloxModdingAPI.App
/// </summary>
public static event EventHandler<GameEventArgs> Simulate
{
add => buildSimEventEngine.SimulationMode += value;
add => buildSimEventEngine.SimulationMode += ExceptionUtil.WrapHandler(value);
remove => buildSimEventEngine.SimulationMode -= value;
}
@ -103,7 +103,7 @@ namespace TechbloxModdingAPI.App
/// </summary>
public static event EventHandler<GameEventArgs> Edit
{
add => buildSimEventEngine.BuildMode += value;
add => buildSimEventEngine.BuildMode += ExceptionUtil.WrapHandler(value);
remove => buildSimEventEngine.BuildMode -= value;
}
@ -112,7 +112,7 @@ namespace TechbloxModdingAPI.App
/// </summary>
public static event EventHandler<GameEventArgs> Enter
{
add => gameEngine.EnterGame += value;
add => gameEngine.EnterGame += ExceptionUtil.WrapHandler(value);
remove => gameEngine.EnterGame -= value;
}
@ -122,7 +122,7 @@ namespace TechbloxModdingAPI.App
/// </summary>
public static event EventHandler<GameEventArgs> Exit
{
add => gameEngine.ExitGame += value;
add => gameEngine.ExitGame += ExceptionUtil.WrapHandler(value);
remove => gameEngine.ExitGame -= value;
}
@ -478,12 +478,8 @@ namespace TechbloxModdingAPI.App
{
GameEngineManager.AddGameEngine(gameEngine);
GameEngineManager.AddGameEngine(debugOverlayEngine);
GameEngineManager.AddGameEngine(buildSimEventEngine);
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>
public static event EventHandler<BlockPlacedRemovedEventArgs> Placed
{
add => BlockEventsEngine.Placed += value;
add => BlockEventsEngine.Placed += ExceptionUtil.WrapHandler(value);
remove => BlockEventsEngine.Placed -= value;
}
@ -83,7 +83,7 @@ namespace TechbloxModdingAPI
/// </summary>
public static event EventHandler<BlockPlacedRemovedEventArgs> Removed
{
add => BlockEventsEngine.Removed += value;
add => BlockEventsEngine.Removed += ExceptionUtil.WrapHandler(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();
BlockGroup.Init();
Wire.Init();
Logging.MetaDebugLog($"Initializing Client");
GameClient.Init();
Client.Init();
Game.Init();
//CustomBlock.Init();
// init UI
Interface.IMGUI.Constants.Init();
Interface.IMGUI.IMGUIManager.Init();

View file

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

View file

@ -13,6 +13,7 @@ using RobocraftX.Common.Input;
using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Commands;
using TechbloxModdingAPI.Input;
using TechbloxModdingAPI.Interface.IMGUI;
using TechbloxModdingAPI.Players;
using TechbloxModdingAPI.Utility;
@ -210,10 +211,10 @@ namespace TechbloxModdingAPI.Tests
Logging.Log("Compatible TechbloxScripting detected");
}
// Interface test
/*Interface.IMGUI.Group uiGroup = new Group(new Rect(20, 20, 200, 500), "TechbloxModdingAPI_UITestGroup", true);
Interface.IMGUI.Button button = new Button("TEST");
/*Group uiGroup = new Group(new Rect(20, 20, 200, 500), "TechbloxModdingAPI_UITestGroup", true);
var button = new Button("TEST");
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}");};
Text uiText = new Text("This is text!", multiline: true);
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
Game.Enter += (sender, args) => { GameTests().RunOn(RobocraftX.Schedulers.Lean.EveryFrameStepRunner_TimeRunningAndStopped); };
Game.Exit += (s, a) => state = "ReturningFromGame";
Client.EnterMenu += (sender, args) =>
Client.EnterMenu += (sender, args) =>
{
if (state == "EnteringMenu")
{

View file

@ -6,7 +6,7 @@ namespace TechbloxModdingAPI.Utility
public static class ExceptionUtil
{
/// <summary>
/// Invokes an event in a try-catch block to avoid propagating exceptions.
/// Invokes an event with a null-check.
/// </summary>
/// <param name="handler">The event to emit, can be null</param>
/// <param name="sender">Event sender</param>
@ -14,16 +14,30 @@ namespace TechbloxModdingAPI.Utility
/// <typeparam name="T">Type of the event arguments</typeparam>
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);
}
catch (Exception e)
{
EventRuntimeException wrappedException =
new EventRuntimeException($"EventHandler with arg type {typeof(T).Name} threw an exception", e);
Logging.LogWarning(wrappedException.ToString());
}
try
{
handler(sender, e);
}
catch (Exception e1)
{
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.Threading.Tasks;
using RobocraftX.StateSync;
using Svelto.ECS;
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;
IEntityFactory factory = enginesRoot.GenerateEntityFactory();
IEntityFactory factory = enginesRoot.GenerateEntityFactory();
foreach (var key in _gameEngines.Keys)
{
Logging.MetaDebugLog($"Registering Game IApiEngine {_gameEngines[key].Name}");
enginesRoot.AddEngine(_gameEngines[key]);
if (typeof(IFactoryEngine).IsAssignableFrom(_gameEngines[key].GetType()))
{
((IFactoryEngine)_gameEngines[key]).Factory = factory;
}
if (_gameEngines[key] is IDeterministicEngine detEngine)
helper.AddDeterministicEngine(detEngine);
else
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}");
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;
}
}
}