Browse Source

Add event handling framework

tags/v0.1.1.0
NGnius (Graham) 4 years ago
parent
commit
864efca755
19 changed files with 609 additions and 33 deletions
  1. +34
    -0
      GamecraftModdingAPI/Commands/CommandPatch.cs
  2. +18
    -0
      GamecraftModdingAPI/Commands/ICustomCommandEngine.cs
  3. +20
    -0
      GamecraftModdingAPI/Events/EventType.cs
  4. +34
    -0
      GamecraftModdingAPI/Events/GameInitPatch.cs
  5. +19
    -0
      GamecraftModdingAPI/Events/IEventEmitterEngine.cs
  6. +15
    -0
      GamecraftModdingAPI/Events/IEventHandlerEngine.cs
  7. +100
    -0
      GamecraftModdingAPI/Events/Manager.cs
  8. +13
    -0
      GamecraftModdingAPI/Events/ModEventEntityDescriptor.cs
  9. +17
    -0
      GamecraftModdingAPI/Events/ModEventEntityStruct.cs
  10. +43
    -0
      GamecraftModdingAPI/Events/SimpleEventEmitterEngine.cs
  11. +40
    -0
      GamecraftModdingAPI/Events/SimpleEventHandlerEngine.cs
  12. +18
    -0
      GamecraftModdingAPI/GamecraftModdingAPI.csproj
  13. +39
    -0
      GamecraftModdingAPI/Main.cs
  14. +26
    -0
      GamecraftModdingAPI/Properties/Settings.Designer.cs
  15. +6
    -0
      GamecraftModdingAPI/Properties/Settings.settings
  16. +0
    -22
      GamecraftModdingAPI/TestPatch.cs
  17. +4
    -11
      GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs
  18. +16
    -0
      GamecraftModdingAPI/Utility/ApiExclusiveGroups.cs
  19. +147
    -0
      GamecraftModdingAPI/Utility/Logging.cs

+ 34
- 0
GamecraftModdingAPI/Commands/CommandPatch.cs 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;
}
}
}

+ 18
- 0
GamecraftModdingAPI/Commands/ICustomCommandEngine.cs 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();
}
}

+ 20
- 0
GamecraftModdingAPI/Events/EventType.cs 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
}
}

+ 34
- 0
GamecraftModdingAPI/Events/GameInitPatch.cs 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();
}
}
}

+ 19
- 0
GamecraftModdingAPI/Events/IEventEmitterEngine.cs 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();
}
}

+ 15
- 0
GamecraftModdingAPI/Events/IEventHandlerEngine.cs 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; }
}
}

+ 100
- 0
GamecraftModdingAPI/Events/Manager.cs 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]);
}
}

}
}

+ 13
- 0
GamecraftModdingAPI/Events/ModEventEntityDescriptor.cs 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>
{
}
}

+ 17
- 0
GamecraftModdingAPI/Events/ModEventEntityStruct.cs 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; }
}
}

+ 43
- 0
GamecraftModdingAPI/Events/SimpleEventEmitterEngine.cs 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;
}
}
}

+ 40
- 0
GamecraftModdingAPI/Events/SimpleEventHandlerEngine.cs 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;
}
}
}

+ 18
- 0
GamecraftModdingAPI/GamecraftModdingAPI.csproj 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>

+ 39
- 0
GamecraftModdingAPI/Main.cs 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");
}
}
}

+ 26
- 0
GamecraftModdingAPI/Properties/Settings.Designer.cs 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;
}
}
}
}

+ 6
- 0
GamecraftModdingAPI/Properties/Settings.settings 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>

+ 0
- 22
GamecraftModdingAPI/TestPatch.cs 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();
}
}
}

GamecraftModdingAPI/GamecraftModdingAPIPlugin.cs → GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs 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() { }

+ 16
- 0
GamecraftModdingAPI/Utility/ApiExclusiveGroups.cs 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;
}
}

+ 147
- 0
GamecraftModdingAPI/Utility/Logging.cs 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());
}
}
}

Loading…
Cancel
Save