switch to simpler approach for mod-provided APIs (#409)
This commit is contained in:
parent
2c909f26fc
commit
7d644aeabe
|
@ -31,7 +31,7 @@ namespace StardewModdingAPI.Framework
|
|||
IMod Mod { get; }
|
||||
|
||||
/// <summary>The mod-provided API (if any).</summary>
|
||||
IModProvidedApi Api { get; }
|
||||
object Api { get; }
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -45,7 +45,10 @@ namespace StardewModdingAPI.Framework
|
|||
|
||||
/// <summary>Set the mod instance.</summary>
|
||||
/// <param name="mod">The mod instance to set.</param>
|
||||
/// <param name="api">The mod-provided API (if any).</param>
|
||||
IModMetadata SetMod(IMod mod, IModProvidedApi api);
|
||||
IModMetadata SetMod(IMod mod);
|
||||
|
||||
/// <summary>Set the mod-provided API instance.</summary>
|
||||
/// <param name="api">The mod-provided API.</param>
|
||||
IModMetadata SetApi(object api);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
}
|
||||
|
||||
/// <summary>Get the API provided by a mod, or <c>null</c> if it has none. This signature requires using the <see cref="IModHelper.Reflection"/> API to access the API's properties and methods.</summary>
|
||||
public IModProvidedApi GetApi(string uniqueID)
|
||||
public object GetApi(string uniqueID)
|
||||
{
|
||||
return this.Registry.Get(uniqueID)?.Api;
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
public IMod Mod { get; private set; }
|
||||
|
||||
/// <summary>The mod-provided API (if any).</summary>
|
||||
public IModProvidedApi Api { get; private set; }
|
||||
public object Api { get; private set; }
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -62,10 +62,16 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
|
||||
/// <summary>Set the mod instance.</summary>
|
||||
/// <param name="mod">The mod instance to set.</param>
|
||||
/// <param name="api">The mod-provided API (if any).</param>
|
||||
public IModMetadata SetMod(IMod mod, IModProvidedApi api)
|
||||
public IModMetadata SetMod(IMod mod)
|
||||
{
|
||||
this.Mod = mod;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>Set the mod-provided API instance.</summary>
|
||||
/// <param name="api">The mod-provided API.</param>
|
||||
public IModMetadata SetApi(object api)
|
||||
{
|
||||
this.Api = api;
|
||||
return this;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
namespace StardewModdingAPI
|
||||
namespace StardewModdingAPI
|
||||
{
|
||||
/// <summary>The implementation for a Stardew Valley mod.</summary>
|
||||
public interface IMod
|
||||
|
@ -22,5 +22,8 @@
|
|||
/// <summary>The mod entry point, called after the mod is first loaded.</summary>
|
||||
/// <param name="helper">Provides simplified APIs for writing mods.</param>
|
||||
void Entry(IModHelper helper);
|
||||
|
||||
/// <summary>Get an API that other mods can access. This is always called after <see cref="Entry"/>.</summary>
|
||||
object GetApi();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
namespace StardewModdingAPI
|
||||
{
|
||||
/// <summary>An API provided by a mod for other mods to use.</summary>
|
||||
/// <remarks>This is a marker interface. Each mod can only have one implementation of <see cref="IModProvidedApi"/>.</remarks>
|
||||
public interface IModProvidedApi { }
|
||||
}
|
|
@ -19,6 +19,6 @@ namespace StardewModdingAPI
|
|||
|
||||
/// <summary>Get the API provided by a mod, or <c>null</c> if it has none. This signature requires using the <see cref="IModHelper.Reflection"/> API to access the API's properties and methods.</summary>
|
||||
/// <param name="uniqueID">The mod's unique ID.</param>
|
||||
IModProvidedApi GetApi(string uniqueID);
|
||||
object GetApi(string uniqueID);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,9 @@ namespace StardewModdingAPI
|
|||
/// <param name="helper">Provides simplified APIs for writing mods.</param>
|
||||
public abstract void Entry(IModHelper helper);
|
||||
|
||||
/// <summary>Get an API that other mods can access. This is always called after <see cref="Entry"/>.</summary>
|
||||
public virtual object GetApi() => null;
|
||||
|
||||
/// <summary>Release or reset unmanaged resources.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
|
|
|
@ -711,11 +711,9 @@ namespace StardewModdingAPI
|
|||
modHelper = new ModHelper(manifest.UniqueID, metadata.DirectoryPath, jsonHelper, contentHelper, commandHelper, modRegistryHelper, reflectionHelper, translationHelper);
|
||||
}
|
||||
|
||||
// get mod instances
|
||||
// get mod instance
|
||||
if (!this.TryLoadModEntry(modAssembly, error => TrackSkip(metadata, error), out Mod mod))
|
||||
continue;
|
||||
if (this.TryLoadModProvidedApi(modAssembly, modHelper, monitor, error => this.Monitor.Log($"Failed loading {metadata.DisplayName}'s mod-provided API. Integrations may not work correctly. Error: {error}", LogLevel.Warn), out IModProvidedApi api))
|
||||
this.Monitor.Log($" Found mod-provided API ({api.GetType().FullName}).", LogLevel.Trace);
|
||||
|
||||
// init mod
|
||||
mod.ModManifest = manifest;
|
||||
|
@ -723,7 +721,7 @@ namespace StardewModdingAPI
|
|||
mod.Monitor = monitor;
|
||||
|
||||
// track mod
|
||||
metadata.SetMod(mod, api);
|
||||
metadata.SetMod(mod);
|
||||
this.ModRegistry.Add(metadata);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -796,6 +794,19 @@ namespace StardewModdingAPI
|
|||
{
|
||||
this.Monitor.Log($"{metadata.DisplayName} failed on entry and might not work correctly. Technical details:\n{ex.GetLogSummary()}", LogLevel.Error);
|
||||
}
|
||||
|
||||
// get mod API
|
||||
try
|
||||
{
|
||||
object api = metadata.Mod.GetApi();
|
||||
if (api != null)
|
||||
this.Monitor.Log($" Found mod-provided API ({api.GetType().FullName}).", LogLevel.Trace);
|
||||
metadata.SetApi(api);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.Monitor.Log($"Failed loading mod-provided API for {metadata.DisplayName}. Integrations with other mods may not work. Error: {ex.GetLogSummary()}", LogLevel.Error);
|
||||
}
|
||||
}
|
||||
|
||||
// invalidate cache entries when needed
|
||||
|
@ -865,73 +876,6 @@ namespace StardewModdingAPI
|
|||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Load a mod's <see cref="IModProvidedApi"/> implementation.</summary>
|
||||
/// <param name="modAssembly">The mod assembly.</param>
|
||||
/// <param name="modHelper">The mod's helper instance.</param>
|
||||
/// <param name="monitor">The mod's monitor instance.</param>
|
||||
/// <param name="onError">A callback invoked when loading fails.</param>
|
||||
/// <param name="api">The loaded instance.</param>
|
||||
private bool TryLoadModProvidedApi(Assembly modAssembly, IModHelper modHelper, IMonitor monitor, Action<string> onError, out IModProvidedApi api)
|
||||
{
|
||||
api = null;
|
||||
|
||||
// find type
|
||||
TypeInfo[] apis = modAssembly.DefinedTypes.Where(type => typeof(IModProvidedApi).IsAssignableFrom(type) && !type.IsAbstract).Take(2).ToArray();
|
||||
if (apis.Length == 0)
|
||||
return false;
|
||||
if (apis.Length > 1)
|
||||
{
|
||||
onError($"its DLL contains multiple '{nameof(IModProvidedApi)}' implementations.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// get constructor
|
||||
ConstructorInfo constructor = (
|
||||
from constr in apis[0].GetConstructors()
|
||||
let args = constr.GetParameters()
|
||||
where
|
||||
!args.Any()
|
||||
|| args.All(arg => typeof(IModHelper).IsAssignableFrom(arg.ParameterType) || typeof(IMonitor).IsAssignableFrom(arg.ParameterType))
|
||||
orderby args.Length descending
|
||||
select constr
|
||||
).FirstOrDefault();
|
||||
if (constructor == null)
|
||||
{
|
||||
onError($"its {nameof(IModProvidedApi)} must have a constructor with zero arguments, or only arguments of type {nameof(IModHelper)} or {nameof(IMonitor)}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// construct instance
|
||||
try
|
||||
{
|
||||
// prepare constructor args
|
||||
ParameterInfo[] args = constructor.GetParameters();
|
||||
object[] values = new object[args.Length];
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
if (typeof(IModHelper).IsAssignableFrom(args[i].ParameterType))
|
||||
values[i] = modHelper;
|
||||
else if (typeof(IMonitor).IsAssignableFrom(args[i].ParameterType))
|
||||
values[i] = monitor;
|
||||
else
|
||||
{
|
||||
// shouldn't happen
|
||||
onError($"its {nameof(IModProvidedApi)} instance's constructor has unexpected argument type {args[i].ParameterType.FullName}.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// instantiate
|
||||
api = (IModProvidedApi)constructor.Invoke(values);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
onError($"its {nameof(IModProvidedApi)} couldn't be constructed: {ex.GetLogSummary()}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Reload translations for all mods.</summary>
|
||||
private void ReloadTranslations()
|
||||
{
|
||||
|
|
|
@ -114,7 +114,6 @@
|
|||
<Compile Include="IReflectedField.cs" />
|
||||
<Compile Include="IReflectedMethod.cs" />
|
||||
<Compile Include="IReflectedProperty.cs" />
|
||||
<Compile Include="IModProvidedApi.cs" />
|
||||
<Compile Include="Metadata\CoreAssets.cs" />
|
||||
<Compile Include="ContentSource.cs" />
|
||||
<Compile Include="Events\ContentEvents.cs" />
|
||||
|
|
Loading…
Reference in New Issue