diff --git a/build/common.targets b/build/common.targets
index 86624b62..bcb0e9e1 100644
--- a/build/common.targets
+++ b/build/common.targets
@@ -53,6 +53,7 @@
+
diff --git a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs
index ef1ad30c..93ea6028 100644
--- a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs
+++ b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using Nanoray.Pintail;
using StardewModdingAPI.Framework.Reflection;
namespace StardewModdingAPI.Framework.ModHelpers
@@ -19,7 +20,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
private readonly HashSet AccessedModApis = new HashSet();
/// Generates proxy classes to access mod APIs through an arbitrary interface.
- private readonly InterfaceProxyFactory ProxyFactory;
+ private readonly IProxyManager ProxyManager;
/*********
@@ -28,13 +29,13 @@ namespace StardewModdingAPI.Framework.ModHelpers
/// Construct an instance.
/// The unique ID of the relevant mod.
/// The underlying mod registry.
- /// Generates proxy classes to access mod APIs through an arbitrary interface.
+ /// Generates proxy classes to access mod APIs through an arbitrary interface.
/// Encapsulates monitoring and logging for the mod.
- public ModRegistryHelper(string modID, ModRegistry registry, InterfaceProxyFactory proxyFactory, IMonitor monitor)
+ public ModRegistryHelper(string modID, ModRegistry registry, IProxyManager proxyManager, IMonitor monitor)
: base(modID)
{
this.Registry = registry;
- this.ProxyFactory = proxyFactory;
+ this.ProxyManager = proxyManager;
this.Monitor = monitor;
}
@@ -96,7 +97,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
// get API of type
if (api is TInterface castApi)
return castApi;
- return this.ProxyFactory.CreateProxy(api, this.ModID, uniqueID);
+ return this.ProxyManager.ObtainProxy(api, this.ModID, uniqueID);
}
}
}
diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs
deleted file mode 100644
index 70ef81f8..00000000
--- a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs
+++ /dev/null
@@ -1,118 +0,0 @@
-using System;
-using System.Linq;
-using System.Reflection;
-using System.Reflection.Emit;
-
-namespace StardewModdingAPI.Framework.Reflection
-{
- /// Generates a proxy class to access a mod API through an arbitrary interface.
- internal class InterfaceProxyBuilder
- {
- /*********
- ** Fields
- *********/
- /// The target class type.
- private readonly Type TargetType;
-
- /// The generated proxy type.
- private readonly Type ProxyType;
-
-
- /*********
- ** Public methods
- *********/
- /// Construct an instance.
- /// The type name to generate.
- /// The CLR module in which to create proxy classes.
- /// The interface type to implement.
- /// The target type.
- public InterfaceProxyBuilder(string name, ModuleBuilder moduleBuilder, Type interfaceType, Type targetType)
- {
- // validate
- if (name == null)
- throw new ArgumentNullException(nameof(name));
- if (targetType == null)
- throw new ArgumentNullException(nameof(targetType));
-
- // define proxy type
- TypeBuilder proxyBuilder = moduleBuilder.DefineType(name, TypeAttributes.Public | TypeAttributes.Class);
- proxyBuilder.AddInterfaceImplementation(interfaceType);
-
- // create field to store target instance
- FieldBuilder targetField = proxyBuilder.DefineField("__Target", targetType, FieldAttributes.Private);
-
- // 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();
-
- il.Emit(OpCodes.Ldarg_0); // this
- // ReSharper disable once AssignNullToNotNullAttribute -- never null
- 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, targetField); // set field to loaded argument
- il.Emit(OpCodes.Ret);
- }
-
- // proxy methods
- foreach (MethodInfo proxyMethod in interfaceType.GetMethods())
- {
- var targetMethod = targetType.GetMethod(proxyMethod.Name, proxyMethod.GetParameters().Select(a => a.ParameterType).ToArray());
- 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, targetField);
- }
-
- // save info
- this.TargetType = targetType;
- this.ProxyType = proxyBuilder.CreateType();
- }
-
- /// Create an instance of the proxy for a target instance.
- /// The target instance.
- 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
- *********/
- /// Define a method which proxies access to a method on the target.
- /// The proxy type being generated.
- /// The target method.
- /// The proxy field containing the API instance.
- private void ProxyMethod(TypeBuilder proxyBuilder, MethodInfo target, FieldBuilder instanceField)
- {
- Type[] argTypes = target.GetParameters().Select(a => a.ParameterType).ToArray();
-
- // create method
- MethodBuilder methodBuilder = proxyBuilder.DefineMethod(target.Name, MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual);
- methodBuilder.SetParameters(argTypes);
- methodBuilder.SetReturnType(target.ReturnType);
-
- // create method body
- {
- ILGenerator il = methodBuilder.GetILGenerator();
-
- // load target instance
- il.Emit(OpCodes.Ldarg_0);
- il.Emit(OpCodes.Ldfld, instanceField);
-
- // invoke target method on instance
- for (int i = 0; i < argTypes.Length; i++)
- il.Emit(OpCodes.Ldarg, i + 1);
- il.Emit(OpCodes.Call, target);
-
- // return result
- il.Emit(OpCodes.Ret);
- }
- }
- }
-}
diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs
deleted file mode 100644
index 5acba569..00000000
--- a/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Reflection;
-using System.Reflection.Emit;
-
-namespace StardewModdingAPI.Framework.Reflection
-{
- /// Generates proxy classes to access mod APIs through an arbitrary interface.
- internal class InterfaceProxyFactory
- {
- /*********
- ** Fields
- *********/
- /// The CLR module in which to create proxy classes.
- private readonly ModuleBuilder ModuleBuilder;
-
- /// The generated proxy types.
- private readonly IDictionary Builders = new Dictionary();
-
-
- /*********
- ** Public methods
- *********/
- /// Construct an instance.
- public InterfaceProxyFactory()
- {
- AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName($"StardewModdingAPI.Proxies, Version={this.GetType().Assembly.GetName().Version}, Culture=neutral"), AssemblyBuilderAccess.Run);
- this.ModuleBuilder = assemblyBuilder.DefineDynamicModule("StardewModdingAPI.Proxies");
- }
-
- /// Create an API proxy.
- /// The interface through which to access the API.
- /// The API instance to access.
- /// The unique ID of the mod consuming the API.
- /// The unique ID of the mod providing the API.
- public TInterface CreateProxy(object instance, string sourceModID, string targetModID)
- where TInterface : class
- {
- lock (this.Builders)
- {
- // 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/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs
index bfbd64ca..8f810644 100644
--- a/src/SMAPI/Framework/SCore.cs
+++ b/src/SMAPI/Framework/SCore.cs
@@ -48,6 +48,8 @@ using xTile.Display;
using MiniMonoModHotfix = MonoMod.Utils.MiniMonoModHotfix;
using PathUtilities = StardewModdingAPI.Toolkit.Utilities.PathUtilities;
using SObject = StardewValley.Object;
+using Nanoray.Pintail;
+using System.Reflection.Emit;
namespace StardewModdingAPI.Framework
{
@@ -1488,12 +1490,17 @@ namespace StardewModdingAPI.Framework
{
// init
HashSet suppressUpdateChecks = new HashSet(this.Settings.SuppressUpdateChecks, StringComparer.OrdinalIgnoreCase);
- InterfaceProxyFactory proxyFactory = new InterfaceProxyFactory();
+ AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName($"StardewModdingAPI.Proxies, Version={this.GetType().Assembly.GetName().Version}, Culture=neutral"), AssemblyBuilderAccess.Run);
+ ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("StardewModdingAPI.Proxies");
+ IProxyManager proxyManager = new ProxyManager(moduleBuilder, new ProxyManagerConfiguration(
+ proxyPrepareBehavior: ProxyManagerProxyPrepareBehavior.Eager,
+ proxyObjectInterfaceMarking: ProxyObjectInterfaceMarking.Disabled
+ ));
// load mods
foreach (IModMetadata mod in mods)
{
- if (!this.TryLoadMod(mod, mods, modAssemblyLoader, proxyFactory, jsonHelper, contentCore, modDatabase, suppressUpdateChecks, out ModFailReason? failReason, out string errorPhrase, out string errorDetails))
+ if (!this.TryLoadMod(mod, mods, modAssemblyLoader, proxyManager, jsonHelper, contentCore, modDatabase, suppressUpdateChecks, out ModFailReason? failReason, out string errorPhrase, out string errorDetails))
{
failReason ??= ModFailReason.LoadFailed;
mod.SetStatus(ModMetadataStatus.Failed, failReason.Value, errorPhrase, errorDetails);
@@ -1596,7 +1603,7 @@ namespace StardewModdingAPI.Framework
/// The mod to load.
/// The mods being loaded.
/// Preprocesses and loads mod assemblies.
- /// Generates proxy classes to access mod APIs through an arbitrary interface.
+ /// Generates proxy classes to access mod APIs through an arbitrary interface.
/// The JSON helper with which to read mods' JSON files.
/// The content manager to use for mod content.
/// Handles access to SMAPI's internal mod metadata list.
@@ -1605,7 +1612,7 @@ namespace StardewModdingAPI.Framework
/// The user-facing reason phrase explaining why the mod couldn't be loaded (if applicable).
/// More detailed details about the error intended for developers (if any).
/// Returns whether the mod was successfully loaded.
- private bool TryLoadMod(IModMetadata mod, IModMetadata[] mods, AssemblyLoader assemblyLoader, InterfaceProxyFactory proxyFactory, JsonHelper jsonHelper, ContentCoordinator contentCore, ModDatabase modDatabase, HashSet suppressUpdateChecks, out ModFailReason? failReason, out string errorReasonPhrase, out string errorDetails)
+ private bool TryLoadMod(IModMetadata mod, IModMetadata[] mods, AssemblyLoader assemblyLoader, IProxyManager proxyManager, JsonHelper jsonHelper, ContentCoordinator contentCore, ModDatabase modDatabase, HashSet suppressUpdateChecks, out ModFailReason? failReason, out string errorReasonPhrase, out string errorDetails)
{
errorDetails = null;
@@ -1748,7 +1755,7 @@ namespace StardewModdingAPI.Framework
IContentPackHelper contentPackHelper = new ContentPackHelper(manifest.UniqueID, new Lazy(GetContentPacks), CreateFakeContentPack);
IDataHelper dataHelper = new DataHelper(manifest.UniqueID, mod.DirectoryPath, jsonHelper);
IReflectionHelper reflectionHelper = new ReflectionHelper(manifest.UniqueID, mod.DisplayName, this.Reflection);
- IModRegistry modRegistryHelper = new ModRegistryHelper(manifest.UniqueID, this.ModRegistry, proxyFactory, monitor);
+ IModRegistry modRegistryHelper = new ModRegistryHelper(manifest.UniqueID, this.ModRegistry, proxyManager, monitor);
IMultiplayerHelper multiplayerHelper = new MultiplayerHelper(manifest.UniqueID, this.Multiplayer);
modHelper = new ModHelper(manifest.UniqueID, mod.DirectoryPath, () => this.GetCurrentGameInstance().Input, events, contentHelper, contentPackHelper, commandHelper, dataHelper, modRegistryHelper, reflectionHelper, multiplayerHelper, translationHelper);
diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj
index f07ede87..1540ee42 100644
--- a/src/SMAPI/SMAPI.csproj
+++ b/src/SMAPI/SMAPI.csproj
@@ -25,6 +25,7 @@
+