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.
|
/// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
/// </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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
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();
|
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();
|
||||||
|
|
|
@ -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>
|
|
@ -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}'"); };
|
||||||
|
|
|
@ -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")
|
||||||
{
|
{
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue