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 { // validate if (instance == null) throw new InvalidOperationException("Can't proxy access to a null API."); // create instance InterfaceProxyBuilder builder = this.ObtainBuilder(instance.GetType(), typeof(TInterface), sourceModID, targetModID); return (TInterface)builder.CreateInstance(instance, this); } internal InterfaceProxyBuilder ObtainBuilder(Type targetType, Type interfaceType, string sourceModID, string targetModID) { lock (this.Builders) { // validate if (!interfaceType.IsInterface) throw new InvalidOperationException("The proxy type must be an interface, not a class."); // get proxy type string proxyTypeName = $"StardewModdingAPI.Proxies.From<{sourceModID}_{interfaceType.FullName}>_To<{targetModID}_{targetType.FullName}>"; if (!this.Builders.TryGetValue(proxyTypeName, out InterfaceProxyBuilder builder)) { builder = new InterfaceProxyBuilder(targetType, proxyTypeName); this.Builders[proxyTypeName] = builder; try { builder.SetupProxyType(this, this.ModuleBuilder, interfaceType, sourceModID, targetModID); } catch { this.Builders.Remove(proxyTypeName); throw; } } return builder; } } internal InterfaceProxyBuilder GetBuilderByProxyTypeName(string proxyTypeName) { lock (this.Builders) { return this.Builders.TryGetValue(proxyTypeName, out InterfaceProxyBuilder builder) ? builder : null; } } } }