summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <github@jplamondonw.com>2018-02-17 18:43:19 -0500
committerJesse Plamondon-Willard <github@jplamondonw.com>2018-02-17 18:43:19 -0500
commit84330e86809dff4a3e70c3cbd59f0373f7017799 (patch)
tree7aabc7f3f4d08189e197d298a98529b37f9b2a21
parente64326f9fe5a388e3a0638567bf4bdf8aab4b639 (diff)
downloadSMAPI-84330e86809dff4a3e70c3cbd59f0373f7017799.tar.gz
SMAPI-84330e86809dff4a3e70c3cbd59f0373f7017799.tar.bz2
SMAPI-84330e86809dff4a3e70c3cbd59f0373f7017799.zip
split proxy builder & factory (#435)
-rw-r--r--src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs10
-rw-r--r--src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs92
-rw-r--r--src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs58
-rw-r--r--src/SMAPI/Program.cs4
-rw-r--r--src/SMAPI/StardewModdingAPI.csproj1
5 files changed, 102 insertions, 63 deletions
diff --git a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs
index ea0dbb38..e579a830 100644
--- a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs
+++ b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs
@@ -20,7 +20,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
private readonly HashSet<string> AccessedModApis = new HashSet<string>();
/// <summary>Generates proxy classes to access mod APIs through an arbitrary interface.</summary>
- private readonly InterfaceProxyBuilder ProxyBuilder;
+ private readonly InterfaceProxyFactory ProxyFactory;
/*********
@@ -29,13 +29,13 @@ namespace StardewModdingAPI.Framework.ModHelpers
/// <summary>Construct an instance.</summary>
/// <param name="modID">The unique ID of the relevant mod.</param>
/// <param name="registry">The underlying mod registry.</param>
- /// <param name="proxyBuilder">Generates proxy classes to access mod APIs through an arbitrary interface.</param>
+ /// <param name="proxyFactory">Generates proxy classes to access mod APIs through an arbitrary interface.</param>
/// <param name="monitor">Encapsulates monitoring and logging for the mod.</param>
- public ModRegistryHelper(string modID, ModRegistry registry, InterfaceProxyBuilder proxyBuilder, IMonitor monitor)
+ public ModRegistryHelper(string modID, ModRegistry registry, InterfaceProxyFactory proxyFactory, IMonitor monitor)
: base(modID)
{
this.Registry = registry;
- this.ProxyBuilder = proxyBuilder;
+ this.ProxyFactory = proxyFactory;
this.Monitor = monitor;
}
@@ -99,7 +99,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
// get API of type
if (api is TInterface castApi)
return castApi;
- return this.ProxyBuilder.CreateProxy<TInterface>(api, this.ModID, uniqueID);
+ return this.ProxyFactory.CreateProxy<TInterface>(api, this.ModID, uniqueID);
}
}
}
diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs
index 5abebc18..7a2958fb 100644
--- a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs
+++ b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs
@@ -1,82 +1,47 @@
using System;
-using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
namespace StardewModdingAPI.Framework.Reflection
{
- /// <summary>Generates proxy classes to access mod APIs through an arbitrary interface.</summary>
+ /// <summary>Generates a proxy class to access a mod API through an arbitrary interface.</summary>
internal class InterfaceProxyBuilder
{
/*********
** Properties
*********/
- /// <summary>The CLR module in which to create proxy classes.</summary>
- private readonly ModuleBuilder ModuleBuilder;
+ /// <summary>The target class type.</summary>
+ private readonly Type TargetType;
- /// <summary>The generated proxy types.</summary>
- private readonly IDictionary<string, Type> GeneratedTypes = new Dictionary<string, Type>();
+ /// <summary>The generated proxy type.</summary>
+ private readonly Type ProxyType;
/*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
- public InterfaceProxyBuilder()
- {
- AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName($"StardewModdingAPI.Proxies, Version={this.GetType().Assembly.GetName().Version}, Culture=neutral"), AssemblyBuilderAccess.Run);
- this.ModuleBuilder = assemblyBuilder.DefineDynamicModule("StardewModdingAPI.Proxies");
- }
-
- /// <summary>Create an API proxy.</summary>
- /// <typeparam name="TInterface">The interface through which to access the API.</typeparam>
- /// <param name="instance">The API instance to access.</param>
- /// <param name="sourceModID">The unique ID of the mod consuming the API.</param>
- /// <param name="targetModID">The unique ID of the mod providing the API.</param>
- public TInterface CreateProxy<TInterface>(object instance, string sourceModID, string targetModID)
- where TInterface : class
+ /// <param name="name">The type name to generate.</param>
+ /// <param name="moduleBuilder">The CLR module in which to create proxy classes.</param>
+ /// <param name="interfaceType">The interface type to implement.</param>
+ /// <param name="targetType">The target type.</param>
+ public InterfaceProxyBuilder(string name, ModuleBuilder moduleBuilder, Type interfaceType, Type targetType)
{
// validate
- if (instance == null)
- throw new InvalidOperationException("Can't proxy access to a null API.");
- if (!typeof(TInterface).IsInterface)
- throw new InvalidOperationException("The proxy type must be an interface, not a class.");
-
- // get proxy type
- Type targetType = instance.GetType();
- string proxyTypeName = $"StardewModdingAPI.Proxies.From<{sourceModID}_{typeof(TInterface).FullName}>_To<{targetModID}_{targetType.FullName}>";
- if (!this.GeneratedTypes.TryGetValue(proxyTypeName, out Type type))
- {
- type = this.CreateProxyType(proxyTypeName, typeof(TInterface), targetType);
- this.GeneratedTypes[proxyTypeName] = type;
- }
+ if (name == null)
+ throw new ArgumentNullException(nameof(name));
+ if (targetType == null)
+ throw new ArgumentNullException(nameof(targetType));
- // create instance
- ConstructorInfo constructor = type.GetConstructor(new[] { targetType });
- if (constructor == null)
- throw new InvalidOperationException($"Couldn't find the constructor for generated proxy type '{proxyTypeName}'."); // should never happen
- return (TInterface)constructor.Invoke(new[] { instance });
- }
-
-
- /*********
- ** Private methods
- *********/
- /// <summary>Define a class which proxies access to a target type through an interface.</summary>
- /// <param name="proxyTypeName">The name of the proxy type to generate.</param>
- /// <param name="interfaceType">The interface type through which to access the target.</param>
- /// <param name="targetType">The target type to access.</param>
- private Type CreateProxyType(string proxyTypeName, Type interfaceType, Type targetType)
- {
// define proxy type
- TypeBuilder proxyBuilder = this.ModuleBuilder.DefineType(proxyTypeName, TypeAttributes.Public | TypeAttributes.Class);
+ TypeBuilder proxyBuilder = moduleBuilder.DefineType(name, TypeAttributes.Public | TypeAttributes.Class);
proxyBuilder.AddInterfaceImplementation(interfaceType);
// create field to store target instance
- FieldBuilder field = proxyBuilder.DefineField("__Target", targetType, FieldAttributes.Private);
+ FieldBuilder targetField = proxyBuilder.DefineField("__Target", targetType, FieldAttributes.Private);
- // create constructor which accepts target instance
+ // create constructor which accepts target instance and sets field
{
ConstructorBuilder constructor = proxyBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard | CallingConventions.HasThis, new[] { targetType });
ILGenerator il = constructor.GetILGenerator();
@@ -86,7 +51,7 @@ namespace StardewModdingAPI.Framework.Reflection
il.Emit(OpCodes.Call, typeof(object).GetConstructor(new Type[0])); // call base constructor
il.Emit(OpCodes.Ldarg_0); // this
il.Emit(OpCodes.Ldarg_1); // load argument
- il.Emit(OpCodes.Stfld, field); // set field to loaded argument
+ il.Emit(OpCodes.Stfld, targetField); // set field to loaded argument
il.Emit(OpCodes.Ret);
}
@@ -97,13 +62,28 @@ namespace StardewModdingAPI.Framework.Reflection
if (targetMethod == null)
throw new InvalidOperationException($"The {interfaceType.FullName} interface defines method {proxyMethod.Name} which doesn't exist in the API.");
- this.ProxyMethod(proxyBuilder, targetMethod, field);
+ this.ProxyMethod(proxyBuilder, targetMethod, targetField);
}
- // create type
- return proxyBuilder.CreateType();
+ // save info
+ this.TargetType = targetType;
+ this.ProxyType = proxyBuilder.CreateType();
+ }
+
+ /// <summary>Create an instance of the proxy for a target instance.</summary>
+ /// <param name="targetInstance">The target instance.</param>
+ public object CreateInstance(object targetInstance)
+ {
+ ConstructorInfo constructor = this.ProxyType.GetConstructor(new[] { this.TargetType });
+ if (constructor == null)
+ throw new InvalidOperationException($"Couldn't find the constructor for generated proxy type '{this.ProxyType.Name}'."); // should never happen
+ return constructor.Invoke(new[] { targetInstance });
}
+
+ /*********
+ ** Private methods
+ *********/
/// <summary>Define a method which proxies access to a method on the target.</summary>
/// <param name="proxyBuilder">The proxy type being generated.</param>
/// <param name="target">The target method.</param>
diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs
new file mode 100644
index 00000000..e14a9f08
--- /dev/null
+++ b/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace StardewModdingAPI.Framework.Reflection
+{
+ /// <summary>Generates proxy classes to access mod APIs through an arbitrary interface.</summary>
+ internal class InterfaceProxyFactory
+ {
+ /*********
+ ** Properties
+ *********/
+ /// <summary>The CLR module in which to create proxy classes.</summary>
+ private readonly ModuleBuilder ModuleBuilder;
+
+ /// <summary>The generated proxy types.</summary>
+ private readonly IDictionary<string, InterfaceProxyBuilder> Builders = new Dictionary<string, InterfaceProxyBuilder>();
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ public InterfaceProxyFactory()
+ {
+ AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName($"StardewModdingAPI.Proxies, Version={this.GetType().Assembly.GetName().Version}, Culture=neutral"), AssemblyBuilderAccess.Run);
+ this.ModuleBuilder = assemblyBuilder.DefineDynamicModule("StardewModdingAPI.Proxies");
+ }
+
+ /// <summary>Create an API proxy.</summary>
+ /// <typeparam name="TInterface">The interface through which to access the API.</typeparam>
+ /// <param name="instance">The API instance to access.</param>
+ /// <param name="sourceModID">The unique ID of the mod consuming the API.</param>
+ /// <param name="targetModID">The unique ID of the mod providing the API.</param>
+ public TInterface CreateProxy<TInterface>(object instance, string sourceModID, string targetModID)
+ where TInterface : class
+ {
+ // validate
+ if (instance == null)
+ throw new InvalidOperationException("Can't proxy access to a null API.");
+ if (!typeof(TInterface).IsInterface)
+ throw new InvalidOperationException("The proxy type must be an interface, not a class.");
+
+ // get proxy type
+ Type targetType = instance.GetType();
+ string proxyTypeName = $"StardewModdingAPI.Proxies.From<{sourceModID}_{typeof(TInterface).FullName}>_To<{targetModID}_{targetType.FullName}>";
+ if (!this.Builders.TryGetValue(proxyTypeName, out InterfaceProxyBuilder builder))
+ {
+ builder = new InterfaceProxyBuilder(proxyTypeName, this.ModuleBuilder, typeof(TInterface), targetType);
+ this.Builders[proxyTypeName] = builder;
+ }
+
+ // create instance
+ return (TInterface)builder.CreateInstance(instance);
+ }
+ }
+}
diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs
index 275876dd..88e27768 100644
--- a/src/SMAPI/Program.cs
+++ b/src/SMAPI/Program.cs
@@ -659,7 +659,7 @@ namespace StardewModdingAPI
AssemblyLoader modAssemblyLoader = new AssemblyLoader(Constants.TargetPlatform, this.Monitor, this.Settings.DeveloperMode);
AppDomain.CurrentDomain.AssemblyResolve += (sender, e) => modAssemblyLoader.ResolveAssembly(e.Name);
- InterfaceProxyBuilder proxyBuilder = new InterfaceProxyBuilder();
+ InterfaceProxyFactory proxyFactory = new InterfaceProxyFactory();
foreach (IModMetadata metadata in mods)
{
// get basic info
@@ -711,7 +711,7 @@ namespace StardewModdingAPI
ICommandHelper commandHelper = new CommandHelper(manifest.UniqueID, metadata.DisplayName, this.CommandManager);
IContentHelper contentHelper = new ContentHelper(contentManager, metadata.DirectoryPath, manifest.UniqueID, metadata.DisplayName, monitor);
IReflectionHelper reflectionHelper = new ReflectionHelper(manifest.UniqueID, metadata.DisplayName, this.Reflection, this.DeprecationManager);
- IModRegistry modRegistryHelper = new ModRegistryHelper(manifest.UniqueID, this.ModRegistry, proxyBuilder, monitor);
+ IModRegistry modRegistryHelper = new ModRegistryHelper(manifest.UniqueID, this.ModRegistry, proxyFactory, monitor);
ITranslationHelper translationHelper = new TranslationHelper(manifest.UniqueID, manifest.Name, contentManager.GetLocale(), contentManager.GetCurrentLanguage());
modHelper = new ModHelper(manifest.UniqueID, metadata.DirectoryPath, jsonHelper, contentHelper, commandHelper, modRegistryHelper, reflectionHelper, translationHelper);
}
diff --git a/src/SMAPI/StardewModdingAPI.csproj b/src/SMAPI/StardewModdingAPI.csproj
index ab247f05..eb403309 100644
--- a/src/SMAPI/StardewModdingAPI.csproj
+++ b/src/SMAPI/StardewModdingAPI.csproj
@@ -113,6 +113,7 @@
<Compile Include="Framework\Exceptions\SAssemblyLoadFailedException.cs" />
<Compile Include="Framework\ModLoading\AssemblyLoadStatus.cs" />
<Compile Include="Framework\Reflection\InterfaceProxyBuilder.cs" />
+ <Compile Include="Framework\Reflection\InterfaceProxyFactory.cs" />
<Compile Include="Framework\Serialisation\SmapiConverters\ManifestDependencyArrayConverter.cs" />
<Compile Include="Framework\Serialisation\SmapiConverters\SemanticVersionConverter.cs" />
<Compile Include="Framework\Serialisation\SimpleReadOnlyConverter.cs" />