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:
parent
f5e3010e48
commit
e8515ef42b
12 changed files with 111 additions and 64 deletions
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
55
TechbloxModdingAPI/Engines/EnginePatches.cs
Normal file
55
TechbloxModdingAPI/Engines/EnginePatches.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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>
|
|
@ -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}'"); };
|
||||
|
|
|
@ -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")
|
||||
{
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue