Add event handling framework

This commit is contained in:
NGnius (Graham) 2019-12-13 20:42:55 -08:00
parent 47fdc9e45c
commit 864efca755
19 changed files with 609 additions and 33 deletions

View file

@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using Harmony;
using Svelto.Context;
using Svelto.ECS;
using RobocraftX;
using RobocraftX.Multiplayer;
using Unity.Entities;
using GamecraftModdingAPI.Utility;
namespace GamecraftModdingAPI.Commands
{
[HarmonyPatch]
class CommandPatch
{
public static void Prefix(UnityContext<FullGameCompositionRoot> contextHolder, EnginesRoot enginesRoot, World physicsWorld, Action reloadGame, MultiplayerInitParameters multiplayerParameters)
{
Logging.Log("Command Line was loaded");
// When a game is loaded, register the command engines
// TODO
}
public static MethodBase TargetMethod(HarmonyInstance instance)
{
var func = (Action<UnityContext<FullGameCompositionRoot>, EnginesRoot, World, Action, MultiplayerInitParameters>)RobocraftX.GUI.CommandLine.CommandLineCompositionRoot.Compose<UnityContext<FullGameCompositionRoot>>;
return func.Method;
}
}
}

View file

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Svelto.ECS;
namespace GamecraftModdingAPI.Commands
{
interface ICustomCommandEngine : IEngine, IQueryingEntitiesEngine
{
string Name { get; }
string Description { get; }
void ExecuteCommand();
}
}

View file

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GamecraftModdingAPI.Events
{
public enum EventType
{
ApplicationInitialized,
MenuActivated,
MenuDestroyed,
MenuSwitchedTo,
GameActivated,
GameDestroyed,
GameReloaded,
GameSwitchedTo
}
}

View file

@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Harmony;
using RobocraftX;
using Svelto.ECS;
using GamecraftModdingAPI.Utility;
namespace GamecraftModdingAPI.Events
{
[HarmonyPatch(typeof(FullGameCompositionRoot), "ActivateMenu")]
class GameInitPatch
{
private static bool firstLoad = true;
public static void Postfix(ref EnginesRoot ____frontEndEnginesRoot)
{
// A new EnginesRoot is always created when ActivateMenu is called
// so all event emitters and handlers must be re-registered.
Manager.RegisterEngines(____frontEndEnginesRoot);
if (firstLoad)
{
firstLoad = false;
Logging.Log("Dispatching App Init event");
Manager.GetEventEmitter("GamecraftModdingAPIApplicationInitializedEventEmitter").Emit();
}
Logging.Log("Dispatching Menu Activated event");
Manager.GetEventEmitter("GamecraftModdingAPIMenuActivatedEventEmitter").Emit();
}
}
}

View file

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Svelto.ECS;
namespace GamecraftModdingAPI.Events
{
public interface IEventEmitterEngine : IEngine, IQueryingEntitiesEngine
{
string Name { get; }
object type { get; }
IEntityFactory Factory { set; }
void Emit();
}
}

View file

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Svelto.ECS;
using Svelto.ECS.Internal;
namespace GamecraftModdingAPI.Events
{
public interface IEventHandlerEngine : IEngine, IQueryingEntitiesEngine, IReactOnAddAndRemove<ModEventEntityStruct>, IReactOnAddAndRemove
{
string Name { get; }
}
}

View file

@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Svelto.ECS;
namespace GamecraftModdingAPI.Events
{
/// <summary>
/// Keeps track of event handlers and emitters.
/// This class can be used to add, remove and get event handlers and emitters.
/// </summary>
public static class Manager
{
private static Dictionary<string, IEventEmitterEngine> _eventEmitters = new Dictionary<string, IEventEmitterEngine>();
private static Dictionary<string, IEventHandlerEngine> _eventHandlers = new Dictionary<string, IEventHandlerEngine>();
// event handler management
public static void AddEventHandler(IEventHandlerEngine engine)
{
_eventHandlers[engine.Name] = engine;
}
public static bool ExistsEventHandler(string name)
{
return _eventHandlers.ContainsKey(name);
}
public static bool ExistsEventHandler(IEventHandlerEngine engine)
{
return ExistsEventHandler(engine.Name);
}
public static IEventHandlerEngine GetEventHandler(string name)
{
return _eventHandlers[name];
}
public static Dictionary<string, IEventHandlerEngine> GetEventHandlers()
{
return _eventHandlers;
}
public static void RemoveEventHandler(string name)
{
_eventHandlers.Remove(name);
}
// event emitter management
public static void AddEventEmitter(IEventEmitterEngine engine)
{
_eventEmitters[engine.Name] = engine;
}
public static bool ExistsEventEmitter(string name)
{
return _eventEmitters.ContainsKey(name);
}
public static bool ExistsEventEmitter(IEventEmitterEngine engine)
{
return ExistsEventEmitter(engine.Name);
}
public static IEventEmitterEngine GetEventEmitter(string name)
{
return _eventEmitters[name];
}
public static Dictionary<string, IEventEmitterEngine> GetEventEmitters()
{
return _eventEmitters;
}
public static void RemoveEventEmitter(string name)
{
_eventEmitters.Remove(name);
}
public static void RegisterEngines(EnginesRoot enginesRoot)
{
// Register handlers before emitters so no events are missed
var entityFactory = enginesRoot.GenerateEntityFactory();
foreach (var key in _eventHandlers.Keys)
{
enginesRoot.AddEngine(_eventHandlers[key]);
}
foreach (var key in _eventEmitters.Keys)
{
_eventEmitters[key].Factory = entityFactory;
enginesRoot.AddEngine(_eventEmitters[key]);
}
}
}
}

View file

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Svelto.ECS;
namespace GamecraftModdingAPI.Events
{
public class ModEventEntityDescriptor : GenericEntityDescriptor<ModEventEntityStruct>
{
}
}

View file

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Svelto.ECS;
using Svelto.ECS.Hybrid;
namespace GamecraftModdingAPI.Events
{
public struct ModEventEntityStruct : IEntityStruct
{
public object type;
public EGID ID { get; set; }
}
}

View file

@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Svelto.ECS;
using GamecraftModdingAPI.Utility;
namespace GamecraftModdingAPI.Events
{
class SimpleEventEmitterEngine : IEventEmitterEngine
{
public string Name { get; set; }
public object type { get; set; }
public IEntityFactory Factory { private get; set; }
public IEntitiesDB entitiesDB { set; private get; }
public void Ready() { }
public void Emit()
{
Factory.BuildEntity<ModEventEntityDescriptor>(ApiExclusiveGroups.eventID++, ApiExclusiveGroups.eventsExclusiveGroup)
.Init(new ModEventEntityStruct
{
type = type
});
}
public SimpleEventEmitterEngine(EventType type, string name)
{
this.type = type;
this.Name = name;
}
public SimpleEventEmitterEngine(object type, string name)
{
this.type = type;
this.Name = name;
}
}
}

View file

@ -0,0 +1,40 @@
using Svelto.ECS;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GamecraftModdingAPI.Events
{
class SimpleEventHandlerEngine : IEventHandlerEngine
{
public object type { get; set; }
public string Name { get; set; }
private readonly Action<IEntitiesDB> onEvent;
public IEntitiesDB entitiesDB { set; private get; }
public void Add(ref ModEventEntityStruct entityView, EGID egid)
{
if (entityView.type.Equals(this.type))
{
onEvent.Invoke(entitiesDB);
}
}
public void Ready() { }
public void Remove(ref ModEventEntityStruct entityView, EGID egid) { }
public SimpleEventHandlerEngine(Action handleEvent, object type, string name) : this((IEntitiesDB db) => { handleEvent.Invoke(); }, type, name) { }
public SimpleEventHandlerEngine(Action<IEntitiesDB> handleEvent, object type, string name)
{
this.type = type;
this.Name = name;
this.onEvent = handleEvent;
}
}
}

View file

@ -3,6 +3,11 @@
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Version>0.1.0.0</Version>
<Authors>Exmods</Authors>
<PackageLicenseExpression>GNU General Public Licence 3+</PackageLicenseExpression>
<PackageProjectUrl>https://git.exmods.org/modtainers/GamecraftModdingAPI</PackageProjectUrl>
<NeutralLanguage>en-CA</NeutralLanguage>
</PropertyGroup>
<ItemGroup>
@ -531,6 +536,19 @@
<HintPath>..\ref\Gamecraft_Data\Managed\VisualProfiler.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Settings.Designer.cs">
<DesignTimeSharedInput>True</DesignTimeSharedInput>
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<None Update="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
<!--End Dependencies-->
</Project>

View file

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Harmony;
using System.Reflection;
using GamecraftModdingAPI.Utility;
using GamecraftModdingAPI.Events;
namespace GamecraftModdingAPI
{
static class Main
{
private static HarmonyInstance harmony;
public static void Init()
{
var currentAssembly = Assembly.GetExecutingAssembly();
if (harmony == null)
{
harmony = HarmonyInstance.Create(currentAssembly.GetName().Name);
harmony.PatchAll(currentAssembly);
}
// create default event objects
Manager.AddEventHandler(new SimpleEventHandlerEngine(() => { Logging.Log("App Inited event!"); },
EventType.ApplicationInitialized, "appinit API debug"));
Manager.AddEventEmitter(new SimpleEventEmitterEngine(EventType.ApplicationInitialized, "GamecraftModdingAPIApplicationInitializedEventEmitter"));
Manager.AddEventEmitter(new SimpleEventEmitterEngine(EventType.MenuActivated, "GamecraftModdingAPIMenuActivatedEventEmitter"));
Logging.Log($"{currentAssembly.GetName().Name} {currentAssembly.GetName().Version} start & patch complete");
}
public static void Shutdown()
{
var currentAssembly = Assembly.GetExecutingAssembly();
harmony.UnpatchAll(currentAssembly.GetName().Name);
Logging.Log($"{currentAssembly.GetName().Name} {currentAssembly.GetName().Version} shutdown & unpatch complete");
}
}
}

View file

@ -0,0 +1,26 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace GamecraftModdingAPI.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.4.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
}
}

View file

@ -0,0 +1,6 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
</SettingsFile>

View file

@ -1,22 +0,0 @@
using System;
using System.Reflection;
using Harmony;
using UnityEngine;
namespace TestMod
{
[HarmonyPatch]
class TestPatch
{
static void Prefix()
{
Debug.Log("Test Patch Prefix");
}
[HarmonyTargetMethod]
static MethodBase HTargetMethod(HarmonyInstance instance)
{
throw new NotImplementedException();
}
}
}

View file

@ -1,13 +1,12 @@
using System;
using IllusionPlugin;
using UnityEngine;
using Harmony;
using System.Reflection;
namespace GamecraftModdingAPI
namespace GamecraftModdingAPI.Tests
{
// unused by design
public class GamecraftModdingAPIPlugin //: IllusionPlugin.IEnhancedPlugin
public class GamecraftModdingAPIPluginTest //: IllusionPlugin.IEnhancedPlugin
{
public static HarmonyInstance harmony { get; protected set; }
@ -21,18 +20,12 @@ namespace GamecraftModdingAPI
public void OnApplicationQuit()
{
harmony.UnpatchAll(HarmonyID);
Debug.Log(Name + " shutdown complete");
GamecraftModdingAPI.Main.Shutdown();
}
public void OnApplicationStart()
{
if (harmony == null)
{
harmony = HarmonyInstance.Create(HarmonyID);
harmony.PatchAll(Assembly.GetExecutingAssembly());
}
Debug.Log(Name + " start & patch complete");
GamecraftModdingAPI.Main.Init();
}
public void OnFixedUpdate() { }

View file

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Svelto.ECS;
namespace GamecraftModdingAPI.Utility
{
static class ApiExclusiveGroups
{
public static readonly ExclusiveGroup eventsExclusiveGroup = new ExclusiveGroup();
public static uint eventID;
}
}

View file

@ -0,0 +1,147 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GamecraftModdingAPI.Utility
{
/// <summary>
/// Utility class to access Gamecraft's built-in logging capabilities.
/// The log is saved to %APPDATA%\..\LocalLow\FreeJam\Gamecraft\Player.Log
/// </summary>
static class Logging
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Log(string msg)
{
Svelto.Console.Log(msg);
}
/// <summary>
/// Write a regular message to Gamecraft's log
/// </summary>
/// <param name="obj">The object to log</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Log(object obj)
{
Svelto.Console.Log(obj.ToString());
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void LogDebug(string msg)
{
Svelto.Console.LogDebug(msg);
}
/// <summary>
/// Write a debug message to Gamecraft's log
/// </summary>
/// <param name="obj">The object to log</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void LogDebug(object obj)
{
Svelto.Console.LogDebug(obj.ToString());
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void LogDebug<T>(string msg, T extraDebug)
{
Svelto.Console.LogDebug<T>(msg, extraDebug);
}
/// <summary>
/// Write a debug message and object to Gamecraft's log
/// The reason this method exists in Svelto.Console is beyond my understanding
/// </summary>
/// <typeparam name="T">The type of the extra debug object</typeparam>
/// <param name="obj">The object to log</param>
/// <param name="extraDebug">The extra object to log</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void LogDebug<T>(object obj, T extraDebug)
{
Svelto.Console.LogDebug<T>(obj.ToString(), extraDebug);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void LogError(string msg, Dictionary<string, string> extraData = null)
{
Svelto.Console.LogError(msg, extraData);
}
/// <summary>
/// Write an error message to Gamecraft's log
/// </summary>
/// <param name="obj">The object to log</param>
/// <param name="extraData">The extra data to pass to the ILogger</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void LogError(object obj, Dictionary<string, string> extraData = null)
{
Svelto.Console.LogError(obj.ToString(), extraData);
}
/// <summary>
/// Write an exception to Gamecraft's log
/// </summary>
/// <param name="e">The exception to log</param>
/// <param name="extraData">The extra data to pass to the ILogger.
/// This is automatically populated with "OuterException#" and "OuterStacktrace#" entries</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void LogException(Exception e, Dictionary<string, string> extraData = null)
{
Svelto.Console.LogException(e, extraData);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void LogException(string msg, Exception e, Dictionary<string, string> extraData = null)
{
Svelto.Console.LogException(msg, e, extraData);
}
/// <summary>
/// Write an exception message to Gamecraft's log
/// </summary>
/// <param name="obj">The object to log</param>
/// <param name="e">The exception to log</param>
/// <param name="extraData">The extra data to pass to the ILogger.
/// This is implemented similar to LogException(Exception e, Dictionary extraData)'s extraData</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void LogException(object obj, Exception e, Dictionary<string, string> extraData = null)
{
Svelto.Console.LogException(obj.ToString(), e, extraData);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void LogWarning(string msg)
{
Svelto.Console.LogWarning(msg);
}
/// <summary>
/// Write a warning message to Gamecraft's log
/// </summary>
/// <param name="obj">The object to log</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void LogWarning(object obj)
{
Svelto.Console.LogWarning(obj.ToString());
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SystemLog(string msg)
{
Svelto.Console.SystemLog(msg);
}
/// <summary>
/// Write a message to stdout (usually the terminal which is running, like Command Prompt or PowerShell)
/// </summary>
/// <param name="obj">The object to log</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SystemLog(object obj)
{
Svelto.Console.SystemLog(obj.ToString());
}
}
}