make GetApi methods mutually exclusive & improve docs

This commit is contained in:
Jesse Plamondon-Willard 2022-10-08 19:59:21 -04:00
parent e8da8fff51
commit a565ac9405
No known key found for this signature in database
GPG Key ID: CF8B1456B3E29F49
3 changed files with 37 additions and 36 deletions

View File

@ -68,49 +68,43 @@ namespace StardewModdingAPI.Framework.ModHelpers
return null; return null;
} }
// get our cached API if one is available // get the target mod
IModMetadata? mod = this.Registry.Get(uniqueID); IModMetadata? mod = this.Registry.Get(uniqueID);
if (mod == null) if (mod == null)
return null; return null;
if (this.AccessedModApis.ContainsKey(mod.Manifest.UniqueID)) // fetch API
if (!this.AccessedModApis.TryGetValue(mod.Manifest.UniqueID, out object? api))
{ {
return this.AccessedModApis[mod.Manifest.UniqueID]; // if the target has a global API, this is mutually exclusive with per-mod APIs
} if (mod.Api != null)
api = mod.Api;
object? api; // else try to get a per-mod API
else
// safely request a specific API instance
try
{
api = mod.Mod?.GetApi(this.Mod.Manifest);
if (api != null && !api.GetType().IsPublic)
{ {
api = null; try
this.Monitor.Log($"{mod.DisplayName} provided a specific API instance with a non-public type. This isn't currently supported, so the specific API won't be available to the requesting mod.", LogLevel.Warn); {
api = mod.Mod?.GetApi(this.Mod.Manifest);
if (api != null && !api.GetType().IsPublic)
{
api = null;
this.Monitor.Log($"{mod.DisplayName} provides a per-mod API instance with a non-public type. This isn't currently supported, so the API won't be available to other mods.", LogLevel.Warn);
}
}
catch (Exception ex)
{
this.Monitor.Log($"Failed loading the per-mod API instance from {mod.DisplayName}. Integrations with other mods may not work. Error: {ex.GetLogSummary()}", LogLevel.Error);
api = null;
}
} }
// cache & log API access
this.AccessedModApis[mod.Manifest.UniqueID] = api;
if (api != null) if (api != null)
this.Monitor.Log($"Accessed specific mod-provided API ({api.GetType().FullName}) for {mod.DisplayName}."); this.Monitor.Log($"Accessed mod-provided API ({api.GetType().FullName}) for {mod.DisplayName}.");
}
catch (Exception ex)
{
this.Monitor.Log($"Failed loading specific mod-provided API for {mod.DisplayName}. Integrations with other mods may not work. Error: {ex.GetLogSummary()}", LogLevel.Error);
api = null;
} }
// fall back to the generic API instance
if (api == null)
{
api = mod.Api;
if (api != null)
{
this.Monitor.Log($"Accessed mod-provided API for {mod.DisplayName}.");
}
}
// cache the API instance and return it
this.AccessedModApis[mod.Manifest.UniqueID] = api;
return api; return api;
} }

View File

@ -1779,6 +1779,11 @@ namespace StardewModdingAPI.Framework
{ {
this.Monitor.Log($"Failed loading mod-provided API for {metadata.DisplayName}. Integrations with other mods may not work. Error: {ex.GetLogSummary()}", LogLevel.Error); this.Monitor.Log($"Failed loading mod-provided API for {metadata.DisplayName}. Integrations with other mods may not work. Error: {ex.GetLogSummary()}", LogLevel.Error);
} }
// validate mod doesn't implement both GetApi() and GetApi(mod)
if (metadata.Api != null && metadata.Mod!.GetType().GetMethod(nameof(Mod.GetApi), new Type[] { typeof(IManifest) })!.DeclaringType != typeof(Mod))
metadata.LogAsMod($"Mod implements both {nameof(Mod.GetApi)}() and {nameof(Mod.GetApi)}({nameof(IManifest)}), which isn't allowed. The latter will be ignored.", LogLevel.Error);
Context.HeuristicModsRunningCode.TryPop(out _); Context.HeuristicModsRunningCode.TryPop(out _);
} }

View File

@ -23,13 +23,15 @@ namespace StardewModdingAPI
/// <param name="helper">Provides simplified APIs for writing mods.</param> /// <param name="helper">Provides simplified APIs for writing mods.</param>
void Entry(IModHelper helper); void Entry(IModHelper helper);
/// <summary>Get an API that other mods can access. This is always called after <see cref="Entry"/>.</summary> /// <summary>Get an <a href="https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Integrations">API that other mods can access</a>. This is always called after <see cref="Entry"/>, and is only called once even if multiple mods access it.</summary>
/// <remarks>You can implement <see cref="GetApi()"/> to provide one instance to all mods, or <see cref="GetApi(IManifest)"/> to provide a separate instance per mod. These are mutually exclusive, so you can only implement one of them.</remarks>
/// <remarks>Returns the API instance, or <c>null</c> if the mod has no API.</remarks>
object? GetApi(); object? GetApi();
/// <summary>Get an API that a specific other mod can access. This method is called the first time the other mod calls <see cref="IModRegistry.GetApi(string)"/> for this mod.</summary> /// <summary>Get an <a href="https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Integrations">API that other mods can access</a>. This is always called after <see cref="Entry"/>, and is called once per mod that accesses the API (even if they access it multiple times).</summary>
/// <param name="manifest">The other mod's manifest.</param> /// <param name="manifest">The manifest for the mod accessing the API.</param>
/// <returns>Returns an API for another mod, or <c>null</c> if the other mod should use the general API returned from <see cref="GetApi()"/>.</returns> /// <remarks>Returns the API instance, or <c>null</c> if the mod has no API. Note that the manifest is provided for informational purposes only, and that denying API access to specific mods is strongly discouraged and may be considered abusive.</remarks>
/// <inheritdoc cref="GetApi()" include="/Remarks" />
object? GetApi(IManifest manifest); object? GetApi(IManifest manifest);
} }
} }