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 resttags/v2.0.0
@@ -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; | |||
} | |||
@@ -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); | |||
} | |||
catch (Exception e) | |||
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) => | |||
{ | |||
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; | |||
} | |||
} | |||
} | |||