allow switching between Pintail & original API proxying
This commit is contained in:
parent
7dec519234
commit
0a69cb4bf7
|
@ -29,6 +29,12 @@ namespace SMAPI.Tests.Core
|
||||||
/// <summary>The random number generator with which to create sample values.</summary>
|
/// <summary>The random number generator with which to create sample values.</summary>
|
||||||
private readonly Random Random = new();
|
private readonly Random Random = new();
|
||||||
|
|
||||||
|
/// <summary>Sample user inputs for season names.</summary>
|
||||||
|
private static readonly IInterfaceProxyFactory[] ProxyFactories = {
|
||||||
|
new InterfaceProxyFactory(),
|
||||||
|
new OriginalInterfaceProxyFactory()
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/*********
|
/*********
|
||||||
** Unit tests
|
** Unit tests
|
||||||
|
@ -37,8 +43,9 @@ namespace SMAPI.Tests.Core
|
||||||
** Events
|
** Events
|
||||||
****/
|
****/
|
||||||
/// <summary>Assert that an event field can be proxied correctly.</summary>
|
/// <summary>Assert that an event field can be proxied correctly.</summary>
|
||||||
|
/// <param name="proxyFactory">The proxy factory to test.</param>
|
||||||
[Test]
|
[Test]
|
||||||
public void CanProxy_EventField()
|
public void CanProxy_EventField([ValueSource(nameof(InterfaceProxyTests.ProxyFactories))] IInterfaceProxyFactory proxyFactory)
|
||||||
{
|
{
|
||||||
// arrange
|
// arrange
|
||||||
ProviderMod providerMod = new();
|
ProviderMod providerMod = new();
|
||||||
|
@ -46,7 +53,7 @@ namespace SMAPI.Tests.Core
|
||||||
int expectedValue = this.Random.Next();
|
int expectedValue = this.Random.Next();
|
||||||
|
|
||||||
// act
|
// act
|
||||||
ISimpleApi proxy = this.GetProxy(implementation);
|
ISimpleApi proxy = this.GetProxy(proxyFactory, implementation);
|
||||||
new ApiConsumer().UseEventField(proxy, out Func<(int timesCalled, int lastValue)> getValues);
|
new ApiConsumer().UseEventField(proxy, out Func<(int timesCalled, int lastValue)> getValues);
|
||||||
providerMod.RaiseEvent(expectedValue);
|
providerMod.RaiseEvent(expectedValue);
|
||||||
(int timesCalled, int lastValue) = getValues();
|
(int timesCalled, int lastValue) = getValues();
|
||||||
|
@ -57,8 +64,9 @@ namespace SMAPI.Tests.Core
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Assert that an event property can be proxied correctly.</summary>
|
/// <summary>Assert that an event property can be proxied correctly.</summary>
|
||||||
|
/// <param name="proxyFactory">The proxy factory to test.</param>
|
||||||
[Test]
|
[Test]
|
||||||
public void CanProxy_EventProperty()
|
public void CanProxy_EventProperty([ValueSource(nameof(InterfaceProxyTests.ProxyFactories))] IInterfaceProxyFactory proxyFactory)
|
||||||
{
|
{
|
||||||
// arrange
|
// arrange
|
||||||
ProviderMod providerMod = new();
|
ProviderMod providerMod = new();
|
||||||
|
@ -66,7 +74,7 @@ namespace SMAPI.Tests.Core
|
||||||
int expectedValue = this.Random.Next();
|
int expectedValue = this.Random.Next();
|
||||||
|
|
||||||
// act
|
// act
|
||||||
ISimpleApi proxy = this.GetProxy(implementation);
|
ISimpleApi proxy = this.GetProxy(proxyFactory, implementation);
|
||||||
new ApiConsumer().UseEventProperty(proxy, out Func<(int timesCalled, int lastValue)> getValues);
|
new ApiConsumer().UseEventProperty(proxy, out Func<(int timesCalled, int lastValue)> getValues);
|
||||||
providerMod.RaiseEvent(expectedValue);
|
providerMod.RaiseEvent(expectedValue);
|
||||||
(int timesCalled, int lastValue) = getValues();
|
(int timesCalled, int lastValue) = getValues();
|
||||||
|
@ -80,10 +88,10 @@ namespace SMAPI.Tests.Core
|
||||||
** Properties
|
** Properties
|
||||||
****/
|
****/
|
||||||
/// <summary>Assert that properties can be proxied correctly.</summary>
|
/// <summary>Assert that properties can be proxied correctly.</summary>
|
||||||
|
/// <param name="proxyFactory">The proxy factory to test.</param>
|
||||||
/// <param name="setVia">Whether to set the properties through the <c>provider mod</c> or <c>proxy interface</c>.</param>
|
/// <param name="setVia">Whether to set the properties through the <c>provider mod</c> or <c>proxy interface</c>.</param>
|
||||||
[TestCase("set via provider mod")]
|
[Test]
|
||||||
[TestCase("set via proxy interface")]
|
public void CanProxy_Properties([ValueSource(nameof(InterfaceProxyTests.ProxyFactories))] IInterfaceProxyFactory proxyFactory, [Values("set via provider mod", "set via proxy interface")] string setVia)
|
||||||
public void CanProxy_Properties(string setVia)
|
|
||||||
{
|
{
|
||||||
// arrange
|
// arrange
|
||||||
ProviderMod providerMod = new();
|
ProviderMod providerMod = new();
|
||||||
|
@ -98,7 +106,7 @@ namespace SMAPI.Tests.Core
|
||||||
BindingFlags expectedEnum = BindingFlags.Instance | BindingFlags.Public;
|
BindingFlags expectedEnum = BindingFlags.Instance | BindingFlags.Public;
|
||||||
|
|
||||||
// act
|
// act
|
||||||
ISimpleApi proxy = this.GetProxy(implementation);
|
ISimpleApi proxy = this.GetProxy(proxyFactory, implementation);
|
||||||
switch (setVia)
|
switch (setVia)
|
||||||
{
|
{
|
||||||
case "set via provider mod":
|
case "set via provider mod":
|
||||||
|
@ -198,27 +206,29 @@ namespace SMAPI.Tests.Core
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Assert that a simple method with no return value can be proxied correctly.</summary>
|
/// <summary>Assert that a simple method with no return value can be proxied correctly.</summary>
|
||||||
|
/// <param name="proxyFactory">The proxy factory to test.</param>
|
||||||
[Test]
|
[Test]
|
||||||
public void CanProxy_SimpleMethod_Void()
|
public void CanProxy_SimpleMethod_Void([ValueSource(nameof(InterfaceProxyTests.ProxyFactories))] IInterfaceProxyFactory proxyFactory)
|
||||||
{
|
{
|
||||||
// arrange
|
// arrange
|
||||||
object implementation = new ProviderMod().GetModApi();
|
object implementation = new ProviderMod().GetModApi();
|
||||||
|
|
||||||
// act
|
// act
|
||||||
ISimpleApi proxy = this.GetProxy(implementation);
|
ISimpleApi proxy = this.GetProxy(proxyFactory, implementation);
|
||||||
proxy.GetNothing();
|
proxy.GetNothing();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Assert that a simple int method can be proxied correctly.</summary>
|
/// <summary>Assert that a simple int method can be proxied correctly.</summary>
|
||||||
|
/// <param name="proxyFactory">The proxy factory to test.</param>
|
||||||
[Test]
|
[Test]
|
||||||
public void CanProxy_SimpleMethod_Int()
|
public void CanProxy_SimpleMethod_Int([ValueSource(nameof(InterfaceProxyTests.ProxyFactories))] IInterfaceProxyFactory proxyFactory)
|
||||||
{
|
{
|
||||||
// arrange
|
// arrange
|
||||||
object implementation = new ProviderMod().GetModApi();
|
object implementation = new ProviderMod().GetModApi();
|
||||||
int expectedValue = this.Random.Next();
|
int expectedValue = this.Random.Next();
|
||||||
|
|
||||||
// act
|
// act
|
||||||
ISimpleApi proxy = this.GetProxy(implementation);
|
ISimpleApi proxy = this.GetProxy(proxyFactory, implementation);
|
||||||
int actualValue = proxy.GetInt(expectedValue);
|
int actualValue = proxy.GetInt(expectedValue);
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
|
@ -226,15 +236,16 @@ namespace SMAPI.Tests.Core
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Assert that a simple object method can be proxied correctly.</summary>
|
/// <summary>Assert that a simple object method can be proxied correctly.</summary>
|
||||||
|
/// <param name="proxyFactory">The proxy factory to test.</param>
|
||||||
[Test]
|
[Test]
|
||||||
public void CanProxy_SimpleMethod_Object()
|
public void CanProxy_SimpleMethod_Object([ValueSource(nameof(InterfaceProxyTests.ProxyFactories))] IInterfaceProxyFactory proxyFactory)
|
||||||
{
|
{
|
||||||
// arrange
|
// arrange
|
||||||
object implementation = new ProviderMod().GetModApi();
|
object implementation = new ProviderMod().GetModApi();
|
||||||
object expectedValue = new();
|
object expectedValue = new();
|
||||||
|
|
||||||
// act
|
// act
|
||||||
ISimpleApi proxy = this.GetProxy(implementation);
|
ISimpleApi proxy = this.GetProxy(proxyFactory, implementation);
|
||||||
object actualValue = proxy.GetObject(expectedValue);
|
object actualValue = proxy.GetObject(expectedValue);
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
|
@ -242,15 +253,16 @@ namespace SMAPI.Tests.Core
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Assert that a simple list method can be proxied correctly.</summary>
|
/// <summary>Assert that a simple list method can be proxied correctly.</summary>
|
||||||
|
/// <param name="proxyFactory">The proxy factory to test.</param>
|
||||||
[Test]
|
[Test]
|
||||||
public void CanProxy_SimpleMethod_List()
|
public void CanProxy_SimpleMethod_List([ValueSource(nameof(InterfaceProxyTests.ProxyFactories))] IInterfaceProxyFactory proxyFactory)
|
||||||
{
|
{
|
||||||
// arrange
|
// arrange
|
||||||
object implementation = new ProviderMod().GetModApi();
|
object implementation = new ProviderMod().GetModApi();
|
||||||
string expectedValue = this.GetRandomString();
|
string expectedValue = this.GetRandomString();
|
||||||
|
|
||||||
// act
|
// act
|
||||||
ISimpleApi proxy = this.GetProxy(implementation);
|
ISimpleApi proxy = this.GetProxy(proxyFactory, implementation);
|
||||||
IList<string> actualValue = proxy.GetList(expectedValue);
|
IList<string> actualValue = proxy.GetList(expectedValue);
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
|
@ -258,15 +270,16 @@ namespace SMAPI.Tests.Core
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Assert that a simple list with interface method can be proxied correctly.</summary>
|
/// <summary>Assert that a simple list with interface method can be proxied correctly.</summary>
|
||||||
|
/// <param name="proxyFactory">The proxy factory to test.</param>
|
||||||
[Test]
|
[Test]
|
||||||
public void CanProxy_SimpleMethod_ListWithInterface()
|
public void CanProxy_SimpleMethod_ListWithInterface([ValueSource(nameof(InterfaceProxyTests.ProxyFactories))] IInterfaceProxyFactory proxyFactory)
|
||||||
{
|
{
|
||||||
// arrange
|
// arrange
|
||||||
object implementation = new ProviderMod().GetModApi();
|
object implementation = new ProviderMod().GetModApi();
|
||||||
string expectedValue = this.GetRandomString();
|
string expectedValue = this.GetRandomString();
|
||||||
|
|
||||||
// act
|
// act
|
||||||
ISimpleApi proxy = this.GetProxy(implementation);
|
ISimpleApi proxy = this.GetProxy(proxyFactory, implementation);
|
||||||
IList<string> actualValue = proxy.GetListWithInterface(expectedValue);
|
IList<string> actualValue = proxy.GetListWithInterface(expectedValue);
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
|
@ -274,8 +287,9 @@ namespace SMAPI.Tests.Core
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Assert that a simple method which returns generic types can be proxied correctly.</summary>
|
/// <summary>Assert that a simple method which returns generic types can be proxied correctly.</summary>
|
||||||
|
/// <param name="proxyFactory">The proxy factory to test.</param>
|
||||||
[Test]
|
[Test]
|
||||||
public void CanProxy_SimpleMethod_GenericTypes()
|
public void CanProxy_SimpleMethod_GenericTypes([ValueSource(nameof(InterfaceProxyTests.ProxyFactories))] IInterfaceProxyFactory proxyFactory)
|
||||||
{
|
{
|
||||||
// arrange
|
// arrange
|
||||||
object implementation = new ProviderMod().GetModApi();
|
object implementation = new ProviderMod().GetModApi();
|
||||||
|
@ -283,7 +297,7 @@ namespace SMAPI.Tests.Core
|
||||||
string expectedValue = this.GetRandomString();
|
string expectedValue = this.GetRandomString();
|
||||||
|
|
||||||
// act
|
// act
|
||||||
ISimpleApi proxy = this.GetProxy(implementation);
|
ISimpleApi proxy = this.GetProxy(proxyFactory, implementation);
|
||||||
IDictionary<string, IList<string>> actualValue = proxy.GetGenerics(expectedKey, expectedValue);
|
IDictionary<string, IList<string>> actualValue = proxy.GetGenerics(expectedKey, expectedValue);
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
|
@ -294,16 +308,17 @@ namespace SMAPI.Tests.Core
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Assert that a simple lambda method can be proxied correctly.</summary>
|
/// <summary>Assert that a simple lambda method can be proxied correctly.</summary>
|
||||||
|
/// <param name="proxyFactory">The proxy factory to test.</param>
|
||||||
[Test]
|
[Test]
|
||||||
[SuppressMessage("ReSharper", "ConvertToLocalFunction")]
|
[SuppressMessage("ReSharper", "ConvertToLocalFunction")]
|
||||||
public void CanProxy_SimpleMethod_Lambda()
|
public void CanProxy_SimpleMethod_Lambda([ValueSource(nameof(InterfaceProxyTests.ProxyFactories))] IInterfaceProxyFactory proxyFactory)
|
||||||
{
|
{
|
||||||
// arrange
|
// arrange
|
||||||
object implementation = new ProviderMod().GetModApi();
|
object implementation = new ProviderMod().GetModApi();
|
||||||
Func<string, string> expectedValue = _ => "test";
|
Func<string, string> expectedValue = _ => "test";
|
||||||
|
|
||||||
// act
|
// act
|
||||||
ISimpleApi proxy = this.GetProxy(implementation);
|
ISimpleApi proxy = this.GetProxy(proxyFactory, implementation);
|
||||||
object actualValue = proxy.GetObject(expectedValue);
|
object actualValue = proxy.GetObject(expectedValue);
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
|
@ -311,16 +326,17 @@ namespace SMAPI.Tests.Core
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Assert that a method with out parameters can be proxied correctly.</summary>
|
/// <summary>Assert that a method with out parameters can be proxied correctly.</summary>
|
||||||
|
/// <param name="proxyFactory">The proxy factory to test.</param>
|
||||||
[Test]
|
[Test]
|
||||||
[SuppressMessage("ReSharper", "ConvertToLocalFunction")]
|
[SuppressMessage("ReSharper", "ConvertToLocalFunction")]
|
||||||
public void CanProxy_Method_OutParameters()
|
public void CanProxy_Method_OutParameters([ValueSource(nameof(InterfaceProxyTests.ProxyFactories))] IInterfaceProxyFactory proxyFactory)
|
||||||
{
|
{
|
||||||
// arrange
|
// arrange
|
||||||
object implementation = new ProviderMod().GetModApi();
|
object implementation = new ProviderMod().GetModApi();
|
||||||
const int expectedNumber = 42;
|
const int expectedNumber = 42;
|
||||||
|
|
||||||
// act
|
// act
|
||||||
ISimpleApi proxy = this.GetProxy(implementation);
|
ISimpleApi proxy = this.GetProxy(proxyFactory, implementation);
|
||||||
bool result = proxy.TryGetOutParameter(
|
bool result = proxy.TryGetOutParameter(
|
||||||
inputNumber: expectedNumber,
|
inputNumber: expectedNumber,
|
||||||
|
|
||||||
|
@ -374,10 +390,10 @@ namespace SMAPI.Tests.Core
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Get a proxy API instance.</summary>
|
/// <summary>Get a proxy API instance.</summary>
|
||||||
|
/// <param name="proxyFactory">The proxy factory to use.</param>
|
||||||
/// <param name="implementation">The underlying API instance.</param>
|
/// <param name="implementation">The underlying API instance.</param>
|
||||||
private ISimpleApi GetProxy(object implementation)
|
private ISimpleApi GetProxy(IInterfaceProxyFactory proxyFactory, object implementation)
|
||||||
{
|
{
|
||||||
var proxyFactory = new InterfaceProxyFactory();
|
|
||||||
return proxyFactory.CreateProxy<ISimpleApi>(implementation, this.FromModId, this.ToModId);
|
return proxyFactory.CreateProxy<ISimpleApi>(implementation, this.FromModId, this.ToModId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,11 +61,13 @@
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Pastebin/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Pastebin/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=pathfinding/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=pathfinding/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Pathoschild/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Pathoschild/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Pintail/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=premultiplied/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=premultiplied/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=premultiply/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=premultiply/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Prenormalize/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Prenormalize/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Preprocesses/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Preprocesses/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=prerelease/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=prerelease/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=proxying/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=pufferchick/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=pufferchick/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=rasterizer/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=rasterizer/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=reimplements/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=reimplements/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
|
|
@ -19,7 +19,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
||||||
private readonly HashSet<string> AccessedModApis = new();
|
private readonly HashSet<string> AccessedModApis = new();
|
||||||
|
|
||||||
/// <summary>Generates proxy classes to access mod APIs through an arbitrary interface.</summary>
|
/// <summary>Generates proxy classes to access mod APIs through an arbitrary interface.</summary>
|
||||||
private readonly InterfaceProxyFactory ProxyFactory;
|
private readonly IInterfaceProxyFactory ProxyFactory;
|
||||||
|
|
||||||
|
|
||||||
/*********
|
/*********
|
||||||
|
@ -30,7 +30,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
||||||
/// <param name="registry">The underlying mod registry.</param>
|
/// <param name="registry">The underlying mod registry.</param>
|
||||||
/// <param name="proxyFactory">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>
|
/// <param name="monitor">Encapsulates monitoring and logging for the mod.</param>
|
||||||
public ModRegistryHelper(IModMetadata mod, ModRegistry registry, InterfaceProxyFactory proxyFactory, IMonitor monitor)
|
public ModRegistryHelper(IModMetadata mod, ModRegistry registry, IInterfaceProxyFactory proxyFactory, IMonitor monitor)
|
||||||
: base(mod)
|
: base(mod)
|
||||||
{
|
{
|
||||||
this.Registry = registry;
|
this.Registry = registry;
|
||||||
|
|
|
@ -22,7 +22,8 @@ namespace StardewModdingAPI.Framework.Models
|
||||||
[nameof(VerboseLogging)] = false,
|
[nameof(VerboseLogging)] = false,
|
||||||
[nameof(LogNetworkTraffic)] = false,
|
[nameof(LogNetworkTraffic)] = false,
|
||||||
[nameof(RewriteMods)] = true,
|
[nameof(RewriteMods)] = true,
|
||||||
[nameof(AggressiveMemoryOptimizations)] = false
|
[nameof(AggressiveMemoryOptimizations)] = false,
|
||||||
|
[nameof(UsePintail)] = true
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>The default values for <see cref="SuppressUpdateChecks"/>, to log changes if different.</summary>
|
/// <summary>The default values for <see cref="SuppressUpdateChecks"/>, to log changes if different.</summary>
|
||||||
|
@ -64,6 +65,9 @@ namespace StardewModdingAPI.Framework.Models
|
||||||
/// <summary>Whether to enable more aggressive memory optimizations.</summary>
|
/// <summary>Whether to enable more aggressive memory optimizations.</summary>
|
||||||
public bool AggressiveMemoryOptimizations { get; }
|
public bool AggressiveMemoryOptimizations { get; }
|
||||||
|
|
||||||
|
/// <summary>Whether to use the experimental Pintail API proxying library, instead of the original proxying built into SMAPI itself.</summary>
|
||||||
|
public bool UsePintail { get; }
|
||||||
|
|
||||||
/// <summary>Whether SMAPI should log network traffic. Best combined with <see cref="VerboseLogging"/>, which includes network metadata.</summary>
|
/// <summary>Whether SMAPI should log network traffic. Best combined with <see cref="VerboseLogging"/>, which includes network metadata.</summary>
|
||||||
public bool LogNetworkTraffic { get; }
|
public bool LogNetworkTraffic { get; }
|
||||||
|
|
||||||
|
@ -87,10 +91,11 @@ namespace StardewModdingAPI.Framework.Models
|
||||||
/// <param name="verboseLogging">Whether SMAPI should log more information about the game context.</param>
|
/// <param name="verboseLogging">Whether SMAPI should log more information about the game context.</param>
|
||||||
/// <param name="rewriteMods">Whether SMAPI should rewrite mods for compatibility.</param>
|
/// <param name="rewriteMods">Whether SMAPI should rewrite mods for compatibility.</param>
|
||||||
/// <param name="aggressiveMemoryOptimizations">Whether to enable more aggressive memory optimizations.</param>
|
/// <param name="aggressiveMemoryOptimizations">Whether to enable more aggressive memory optimizations.</param>
|
||||||
|
/// <param name="usePintail">Whether to use the experimental Pintail API proxying library, instead of the original proxying built into SMAPI itself.</param>
|
||||||
/// <param name="logNetworkTraffic">Whether SMAPI should log network traffic.</param>
|
/// <param name="logNetworkTraffic">Whether SMAPI should log network traffic.</param>
|
||||||
/// <param name="consoleColors">The colors to use for text written to the SMAPI console.</param>
|
/// <param name="consoleColors">The colors to use for text written to the SMAPI console.</param>
|
||||||
/// <param name="suppressUpdateChecks">The mod IDs SMAPI should ignore when performing update checks or validating update keys.</param>
|
/// <param name="suppressUpdateChecks">The mod IDs SMAPI should ignore when performing update checks or validating update keys.</param>
|
||||||
public SConfig(bool developerMode, bool checkForUpdates, bool? paranoidWarnings, bool? useBetaChannel, string gitHubProjectName, string webApiBaseUrl, bool verboseLogging, bool? rewriteMods, bool? aggressiveMemoryOptimizations, bool logNetworkTraffic, ColorSchemeConfig consoleColors, string[]? suppressUpdateChecks)
|
public SConfig(bool developerMode, bool checkForUpdates, bool? paranoidWarnings, bool? useBetaChannel, string gitHubProjectName, string webApiBaseUrl, bool verboseLogging, bool? rewriteMods, bool? aggressiveMemoryOptimizations, bool usePintail, bool logNetworkTraffic, ColorSchemeConfig consoleColors, string[]? suppressUpdateChecks)
|
||||||
{
|
{
|
||||||
this.DeveloperMode = developerMode;
|
this.DeveloperMode = developerMode;
|
||||||
this.CheckForUpdates = checkForUpdates;
|
this.CheckForUpdates = checkForUpdates;
|
||||||
|
@ -101,6 +106,7 @@ namespace StardewModdingAPI.Framework.Models
|
||||||
this.VerboseLogging = verboseLogging;
|
this.VerboseLogging = verboseLogging;
|
||||||
this.RewriteMods = rewriteMods ?? (bool)SConfig.DefaultValues[nameof(SConfig.RewriteMods)];
|
this.RewriteMods = rewriteMods ?? (bool)SConfig.DefaultValues[nameof(SConfig.RewriteMods)];
|
||||||
this.AggressiveMemoryOptimizations = aggressiveMemoryOptimizations ?? (bool)SConfig.DefaultValues[nameof(SConfig.AggressiveMemoryOptimizations)];
|
this.AggressiveMemoryOptimizations = aggressiveMemoryOptimizations ?? (bool)SConfig.DefaultValues[nameof(SConfig.AggressiveMemoryOptimizations)];
|
||||||
|
this.UsePintail = usePintail;
|
||||||
this.LogNetworkTraffic = logNetworkTraffic;
|
this.LogNetworkTraffic = logNetworkTraffic;
|
||||||
this.ConsoleColors = consoleColors;
|
this.ConsoleColors = consoleColors;
|
||||||
this.SuppressUpdateChecks = suppressUpdateChecks ?? Array.Empty<string>();
|
this.SuppressUpdateChecks = suppressUpdateChecks ?? Array.Empty<string>();
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
namespace StardewModdingAPI.Framework.Reflection
|
||||||
|
{
|
||||||
|
/// <summary>Generates proxy classes to access mod APIs through an arbitrary interface.</summary>
|
||||||
|
internal interface IInterfaceProxyFactory
|
||||||
|
{
|
||||||
|
/*********
|
||||||
|
** Methods
|
||||||
|
*********/
|
||||||
|
/// <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>
|
||||||
|
TInterface CreateProxy<TInterface>(object instance, string sourceModID, string targetModID)
|
||||||
|
where TInterface : class;
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,8 +4,8 @@ using Nanoray.Pintail;
|
||||||
|
|
||||||
namespace StardewModdingAPI.Framework.Reflection
|
namespace StardewModdingAPI.Framework.Reflection
|
||||||
{
|
{
|
||||||
/// <summary>Generates proxy classes to access mod APIs through an arbitrary interface.</summary>
|
/// <inheritdoc />
|
||||||
internal class InterfaceProxyFactory
|
internal class InterfaceProxyFactory : IInterfaceProxyFactory
|
||||||
{
|
{
|
||||||
/*********
|
/*********
|
||||||
** Fields
|
** Fields
|
||||||
|
@ -28,11 +28,7 @@ namespace StardewModdingAPI.Framework.Reflection
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Create an API proxy.</summary>
|
/// <inheritdoc />
|
||||||
/// <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)
|
public TInterface CreateProxy<TInterface>(object instance, string sourceModID, string targetModID)
|
||||||
where TInterface : class
|
where TInterface : class
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,118 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Reflection.Emit;
|
||||||
|
|
||||||
|
namespace StardewModdingAPI.Framework.Reflection
|
||||||
|
{
|
||||||
|
/// <summary>Generates a proxy class to access a mod API through an arbitrary interface.</summary>
|
||||||
|
internal class OriginalInterfaceProxyBuilder
|
||||||
|
{
|
||||||
|
/*********
|
||||||
|
** Fields
|
||||||
|
*********/
|
||||||
|
/// <summary>The target class type.</summary>
|
||||||
|
private readonly Type TargetType;
|
||||||
|
|
||||||
|
/// <summary>The generated proxy type.</summary>
|
||||||
|
private readonly Type ProxyType;
|
||||||
|
|
||||||
|
|
||||||
|
/*********
|
||||||
|
** Public methods
|
||||||
|
*********/
|
||||||
|
/// <summary>Construct an instance.</summary>
|
||||||
|
/// <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 OriginalInterfaceProxyBuilder(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(Type.EmptyTypes)!); // 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()!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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>
|
||||||
|
/// <param name="instanceField">The proxy field containing the API instance.</param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Reflection.Emit;
|
||||||
|
|
||||||
|
namespace StardewModdingAPI.Framework.Reflection
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
internal class OriginalInterfaceProxyFactory : IInterfaceProxyFactory
|
||||||
|
{
|
||||||
|
/*********
|
||||||
|
** Fields
|
||||||
|
*********/
|
||||||
|
/// <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, OriginalInterfaceProxyBuilder> Builders = new Dictionary<string, OriginalInterfaceProxyBuilder>();
|
||||||
|
|
||||||
|
|
||||||
|
/*********
|
||||||
|
** Public methods
|
||||||
|
*********/
|
||||||
|
/// <summary>Construct an instance.</summary>
|
||||||
|
public OriginalInterfaceProxyFactory()
|
||||||
|
{
|
||||||
|
AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName($"StardewModdingAPI.Proxies, Version={this.GetType().Assembly.GetName().Version}, Culture=neutral"), AssemblyBuilderAccess.Run);
|
||||||
|
this.ModuleBuilder = assemblyBuilder.DefineDynamicModule("StardewModdingAPI.Proxies");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public TInterface CreateProxy<TInterface>(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 OriginalInterfaceProxyBuilder? builder))
|
||||||
|
{
|
||||||
|
builder = new OriginalInterfaceProxyBuilder(proxyTypeName, this.ModuleBuilder, typeof(TInterface), targetType);
|
||||||
|
this.Builders[proxyTypeName] = builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create instance
|
||||||
|
return (TInterface)builder.CreateInstance(instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1560,7 +1560,9 @@ namespace StardewModdingAPI.Framework
|
||||||
{
|
{
|
||||||
// init
|
// init
|
||||||
HashSet<string> suppressUpdateChecks = new HashSet<string>(this.Settings.SuppressUpdateChecks, StringComparer.OrdinalIgnoreCase);
|
HashSet<string> suppressUpdateChecks = new HashSet<string>(this.Settings.SuppressUpdateChecks, StringComparer.OrdinalIgnoreCase);
|
||||||
InterfaceProxyFactory proxyFactory = new();
|
IInterfaceProxyFactory proxyFactory = this.Settings.UsePintail
|
||||||
|
? new InterfaceProxyFactory()
|
||||||
|
: new OriginalInterfaceProxyFactory();
|
||||||
|
|
||||||
// load mods
|
// load mods
|
||||||
foreach (IModMetadata mod in mods)
|
foreach (IModMetadata mod in mods)
|
||||||
|
@ -1699,7 +1701,7 @@ namespace StardewModdingAPI.Framework
|
||||||
/// <param name="errorReasonPhrase">The user-facing reason phrase explaining why the mod couldn't be loaded (if applicable).</param>
|
/// <param name="errorReasonPhrase">The user-facing reason phrase explaining why the mod couldn't be loaded (if applicable).</param>
|
||||||
/// <param name="errorDetails">More detailed details about the error intended for developers (if any).</param>
|
/// <param name="errorDetails">More detailed details about the error intended for developers (if any).</param>
|
||||||
/// <returns>Returns whether the mod was successfully loaded.</returns>
|
/// <returns>Returns whether the mod was successfully loaded.</returns>
|
||||||
private bool TryLoadMod(IModMetadata mod, IModMetadata[] mods, AssemblyLoader assemblyLoader, InterfaceProxyFactory proxyFactory, JsonHelper jsonHelper, ContentCoordinator contentCore, ModDatabase modDatabase, HashSet<string> suppressUpdateChecks, [NotNullWhen(false)] out ModFailReason? failReason, out string? errorReasonPhrase, out string? errorDetails)
|
private bool TryLoadMod(IModMetadata mod, IModMetadata[] mods, AssemblyLoader assemblyLoader, IInterfaceProxyFactory proxyFactory, JsonHelper jsonHelper, ContentCoordinator contentCore, ModDatabase modDatabase, HashSet<string> suppressUpdateChecks, [NotNullWhen(false)] out ModFailReason? failReason, out string? errorReasonPhrase, out string? errorDetails)
|
||||||
{
|
{
|
||||||
errorDetails = null;
|
errorDetails = null;
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,12 @@ copy all the settings, or you may cause bugs due to overridden changes in future
|
||||||
*/
|
*/
|
||||||
"AggressiveMemoryOptimizations": false,
|
"AggressiveMemoryOptimizations": false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to use the experimental Pintail API proxying library, instead of the original
|
||||||
|
* proxying built into SMAPI itself.
|
||||||
|
*/
|
||||||
|
"UsePintail": true,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to add a section to the 'mod issues' list for mods which directly use potentially
|
* Whether to add a section to the 'mod issues' list for mods which directly use potentially
|
||||||
* sensitive .NET APIs like file or shell access. Note that many mods do this legitimately as
|
* sensitive .NET APIs like file or shell access. Note that many mods do this legitimately as
|
||||||
|
|
Loading…
Reference in New Issue