-
-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #48 from Extremelyd1/hkmp-api
HKMP API with addon system
- Loading branch information
Showing
201 changed files
with
8,632 additions
and
2,916 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
namespace Hkmp.Api.Addon { | ||
/// <summary> | ||
/// Abstract base class for addons. | ||
/// </summary> | ||
public abstract class Addon { | ||
/// <summary> | ||
/// The maximum length of the name string for an addon. | ||
/// </summary> | ||
public const int MaxNameLength = 20; | ||
/// <summary> | ||
/// The maximum length of the version string for an addon. | ||
/// </summary> | ||
public const int MaxVersionLength = 10; | ||
|
||
/// <summary> | ||
/// The internal ID assigned to this addon. | ||
/// </summary> | ||
internal byte Id { get; set; } | ||
|
||
/// <summary> | ||
/// The network sender object if it has been registered. | ||
/// </summary> | ||
internal object NetworkSender; | ||
/// <summary> | ||
/// The network receiver object if it has been registered. | ||
/// </summary> | ||
internal object NetworkReceiver; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Reflection; | ||
|
||
namespace Hkmp.Api.Addon { | ||
/// <summary> | ||
/// Abstract base class for loading addons from file. | ||
/// </summary> | ||
public abstract class AddonLoader { | ||
/// <summary> | ||
/// The file pattern to look for when obtaining candidate files to load. | ||
/// </summary> | ||
private const string AssemblyFilePattern = "*.dll"; | ||
|
||
/// <summary> | ||
/// The directory in which to look for assembly files. | ||
/// </summary> | ||
/// <returns>A string denoting the path of the current directory.</returns> | ||
protected abstract string GetCurrentDirectoryPath(); | ||
|
||
/// <summary> | ||
/// Get the paths for all assembly files in the HKMP directory. | ||
/// </summary> | ||
/// <returns>A string array containing file paths.</returns> | ||
private string[] GetAssemblyPaths() { | ||
return Directory.GetFiles(GetCurrentDirectoryPath(), AssemblyFilePattern); | ||
} | ||
|
||
/// <summary> | ||
/// Get all loadable types from the given assembly. | ||
/// </summary> | ||
/// <param name="assembly">The assembly instance to get the types from.</param> | ||
/// <returns>An enumerator that traverses the loadable types.</returns> | ||
private static IEnumerable<Type> GetLoadableTypes(Assembly assembly) { | ||
try { | ||
return assembly.GetTypes(); | ||
} catch (ReflectionTypeLoadException e) { | ||
return e.Types.Where(t => t != null); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Load all addons given their type and given an API interface instance. | ||
/// </summary> | ||
/// <param name="apiObject">The API interface instance in object form.</param> | ||
/// <typeparam name="TAddon">The type of the addon.</typeparam> | ||
/// <typeparam name="TApiInterface">The type of the API interface.</typeparam> | ||
/// <returns>A list of addon instance of type TAddon.</returns> | ||
protected List<TAddon> LoadAddons<TAddon, TApiInterface>(object apiObject) { | ||
var addons = new List<TAddon>(); | ||
|
||
var assemblyPaths = GetAssemblyPaths(); | ||
|
||
foreach (var assemblyPath in assemblyPaths) { | ||
Logger.Get().Info(this, $"Trying to load assembly at: {assemblyPath}"); | ||
|
||
Assembly assembly; | ||
try { | ||
assembly = Assembly.LoadFrom(assemblyPath); | ||
} catch (Exception e) { | ||
Logger.Get().Warn(this, | ||
$" Could not load assembly, exception: {e.GetType()}, message: {e.Message}"); | ||
continue; | ||
} | ||
|
||
foreach (var type in GetLoadableTypes(assembly)) { | ||
if (!type.IsClass | ||
|| type.IsAbstract | ||
|| type.IsInterface | ||
|| !type.IsSubclassOf(typeof(TAddon)) | ||
) { | ||
continue; | ||
} | ||
|
||
Logger.Get().Info(this, $" Found {typeof(TAddon)} extending class, constructing addon"); | ||
|
||
var constructor = type.GetConstructor(new[] {typeof(TApiInterface)}); | ||
if (constructor == null) { | ||
Logger.Get().Warn(this, " Could not find constructor for addon"); | ||
continue; | ||
} | ||
|
||
object addonObject; | ||
try { | ||
addonObject = constructor.Invoke(new[] {apiObject}); | ||
} catch (Exception e) { | ||
Logger.Get().Warn(this, $" Could not invoke constructor for addon, exception: {e.GetType()}, message: {e.Message}"); | ||
continue; | ||
} | ||
|
||
if (!(addonObject is TAddon addon)) { | ||
Logger.Get().Warn(this, $" Addon is not of type {typeof(TAddon).Name}"); | ||
continue; | ||
} | ||
|
||
addons.Add(addon); | ||
// We only allow a single class extending the addon subclass | ||
break; | ||
} | ||
} | ||
|
||
return addons; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
using JetBrains.Annotations; | ||
|
||
namespace Hkmp.Api.Client { | ||
/// <summary> | ||
/// Abstract base class for a client addon. Inheriting this will allow the addon class to be loaded. | ||
/// </summary> | ||
[PublicAPI] | ||
public abstract class ClientAddon : Addon.Addon { | ||
|
||
/// <summary> | ||
/// The client API interface. | ||
/// </summary> | ||
protected IClientApi ClientApi { get; } | ||
|
||
/// <summary> | ||
/// The logger for logging information. | ||
/// </summary> | ||
protected ILogger Logger => Hkmp.Logger.Get(); | ||
|
||
/// <summary> | ||
/// The name (and also identifier) of the addon. | ||
/// </summary> | ||
protected abstract string Name { get; } | ||
|
||
/// <summary> | ||
/// The version (also identifying) of the addon. | ||
/// </summary> | ||
protected abstract string Version { get; } | ||
|
||
/// <summary> | ||
/// Whether this addon requires network access. | ||
/// </summary> | ||
public abstract bool NeedsNetwork { get; } | ||
|
||
/// <summary> | ||
/// Called when the addon is loaded and can be initialized. | ||
/// </summary> | ||
public abstract void Initialize(); | ||
|
||
public ClientAddon(IClientApi clientApi) { | ||
ClientApi = clientApi; | ||
} | ||
|
||
/// <summary> | ||
/// Internal method for obtaining the length-valid addon name. | ||
/// </summary> | ||
/// <returns>The name of the addon or a substring of the first valid characters of its name.</returns> | ||
public string GetName() { | ||
if (Name.Length > MaxNameLength) { | ||
return Name.Substring(0, MaxNameLength); | ||
} | ||
|
||
return Name; | ||
} | ||
|
||
/// <summary> | ||
/// Internal method for obtaining the length-valid addon version. | ||
/// </summary> | ||
/// <returns>The version of the addon or a substring of the first valid characters of its version.</returns> | ||
public string GetVersion() { | ||
if (Version.Length > MaxVersionLength) { | ||
return Version.Substring(0, MaxVersionLength); | ||
} | ||
|
||
return Version; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Reflection; | ||
using Hkmp.Api.Addon; | ||
|
||
namespace Hkmp.Api.Client { | ||
/// <summary> | ||
/// Addon loader for the client-side. | ||
/// </summary> | ||
public class ClientAddonLoader : AddonLoader { | ||
/// <summary> | ||
/// The client API instance to pass onto newly loaded addons. | ||
/// </summary> | ||
private readonly ClientApi _clientApi; | ||
|
||
public ClientAddonLoader(ClientApi clientApi) { | ||
_clientApi = clientApi; | ||
} | ||
|
||
/// <summary> | ||
/// Loads all client addons. | ||
/// </summary> | ||
/// <returns>A list of ClientAddon instances.</returns> | ||
public List<ClientAddon> LoadAddons() { | ||
return LoadAddons<ClientAddon, IClientApi>(_clientApi); | ||
} | ||
|
||
protected override string GetCurrentDirectoryPath() { | ||
return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using Hkmp.Networking.Packet.Data; | ||
|
||
namespace Hkmp.Api.Client { | ||
/// <summary> | ||
/// Manager class for client addons. | ||
/// </summary> | ||
public class ClientAddonManager { | ||
/// <summary> | ||
/// A list of all loaded addons, the order is important as it is the exact order | ||
/// in which we sent it to the server and are expected to act on when receiving a response. | ||
/// </summary> | ||
private readonly List<ClientAddon> _addons; | ||
|
||
public ClientAddonManager(ClientApi clientApi) { | ||
_addons = new List<ClientAddon>(); | ||
|
||
var addonLoader = new ClientAddonLoader(clientApi); | ||
|
||
var addons = addonLoader.LoadAddons(); | ||
foreach (var addon in addons) { | ||
Logger.Get().Info(this, | ||
$"Initializing client addon: {addon.GetName()} {addon.GetVersion()}"); | ||
|
||
try { | ||
addon.Initialize(); | ||
} catch (Exception e) { | ||
Logger.Get().Warn(this, $"Could not initialize addon {addon.GetName()}, exception: {e.GetType()}, {e.Message}, {e.StackTrace}"); | ||
continue; | ||
} | ||
|
||
_addons.Add(addon); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Get a list of addon data for all networked addons. | ||
/// </summary> | ||
/// <returns>A list of AddonData instances.</returns> | ||
public List<AddonData> GetNetworkedAddonData() { | ||
var addonData = new List<AddonData>(); | ||
|
||
foreach (var addon in _addons) { | ||
if (!addon.NeedsNetwork) { | ||
continue; | ||
} | ||
|
||
addonData.Add(new AddonData { | ||
Identifier = addon.GetName(), | ||
Version = addon.GetVersion() | ||
}); | ||
} | ||
|
||
return addonData; | ||
} | ||
|
||
/// <summary> | ||
/// Updates the order of all networked addons according to the given order. | ||
/// </summary> | ||
/// <param name="addonOrder">A byte array containing the IDs the addons should have.</param> | ||
public void UpdateNetworkedAddonOrder(byte[] addonOrder) { | ||
var index = 0; | ||
|
||
// The order of the addons in our local list should stay the same | ||
// between connection and obtaining the addon order from the server | ||
foreach (var addon in _addons) { | ||
// Skip all non-networked addons | ||
if (!addon.NeedsNetwork) { | ||
continue; | ||
} | ||
|
||
// Retrieve the ID that this networked addon should have | ||
var id = addonOrder[index++]; | ||
|
||
// Set the internal ID of the addon | ||
addon.Id = id; | ||
|
||
Logger.Get().Info(this, $"Retrieved addon {addon.GetName()} v{addon.GetVersion()} ID: {id}"); | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.