/* * Copyright 2022 Peter Han * Permission is hereby granted, free of charge, to any person obtaining a copy of this software * and associated documentation files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or * substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ using HarmonyLib; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Reflection; using UnityEngine; namespace PeterHan.PLib.Core { /// /// A custom component added to manage shared data between mods, especially instances of /// PForwardedComponent used by both PLib and other mods. /// internal sealed class PRegistryComponent : MonoBehaviour, IPLibRegistry { /// /// The Harmony instance name used when patching via PLib. /// internal const string PLIB_HARMONY = "PeterHan.PLib"; /// /// A pointer to the active PLib registry. /// private static PRegistryComponent instance = null; /// /// true if the forwarded components have been instantiated, or false otherwise. /// private static bool instantiated = false; /// /// Applies the latest version of all forwarded components. /// private static void ApplyLatest() { bool apply = false; if (instance != null) lock (instance) { if (!instantiated) apply = instantiated = true; } if (apply) instance.Instantiate(); } /// /// Stores shared mod data which needs single instance existence. Available to all /// PLib consumers through PLib API. /// public IDictionary ModData { get; } /// /// The Harmony instance used by PLib patching. /// public Harmony PLibInstance { get; } /// /// The candidate components with versions, from multiple assemblies. /// private readonly ConcurrentDictionary forwardedComponents; /// /// The components actually instantiated (latest version of each). /// private readonly ConcurrentDictionary instantiatedComponents; /// /// The latest versions of each component. /// private readonly ConcurrentDictionary latestComponents; internal PRegistryComponent() { if (instance == null) instance = this; else { #if DEBUG PRegistry.LogPatchWarning("Multiple PLocalRegistry created!"); #endif } ModData = new ConcurrentDictionary(2, 64); forwardedComponents = new ConcurrentDictionary(2, 32); instantiatedComponents = new ConcurrentDictionary(2, 32); latestComponents = new ConcurrentDictionary(2, 32); PLibInstance = new Harmony(PLIB_HARMONY); } public void AddCandidateVersion(PForwardedComponent instance) { if (instance == null) throw new ArgumentNullException(nameof(instance)); AddCandidateVersion(instance.ID, instance); } /// /// Adds a remote or local forwarded component by ID. /// /// The real ID of the component. /// The candidate instance to add. private void AddCandidateVersion(string id, PForwardedComponent instance) { var versions = forwardedComponents.GetOrAdd(id, (_) => new PVersionList()); if (versions == null) PRegistry.LogPatchWarning("Missing version info for component type " + id); else { var list = versions.Components; bool first = list.Count < 1; list.Add(instance); #if DEBUG PRegistry.LogPatchDebug("Candidate version of {0} from {1}".F(id, instance. GetOwningAssembly())); #endif if (first) instance.Bootstrap(PLibInstance); } } /// /// Applies a bootstrapper patch which will complete forwarded component initialization /// before mods are post-loaded. /// internal void ApplyBootstrapper() { try { PLibInstance.Patch(typeof(KMod.Mod), nameof(KMod.Mod.PostLoad), prefix: new HarmonyMethod(typeof(PRegistryComponent), nameof(ApplyLatest))); } catch (AmbiguousMatchException e) { PUtil.LogException(e); } catch (ArgumentException e) { PUtil.LogException(e); } catch (TypeLoadException e) { PUtil.LogException(e); } } /// /// Called from other mods to add a candidate version of a particular component. /// /// The component to be added. internal void DoAddCandidateVersion(object instance) { AddCandidateVersion(instance.GetType().FullName, new PRemoteComponent(instance)); } /// /// Called from other mods to get a list of all components with the given ID. /// /// The component ID to retrieve. /// The instantiated instance of that component, or null if no component by /// that name was found or ever registered. internal System.Collections.ICollection DoGetAllComponents(string id) { if (!forwardedComponents.TryGetValue(id, out PVersionList all)) all = null; return all?.Components; } /// /// Called from other mods to get the instantiated version of a particular component. /// /// The component ID to retrieve. /// The instantiated instance of that component, or null if no component by /// that name was found or successfully instantiated. internal object DoGetLatestVersion(string id) { if (!instantiatedComponents.TryGetValue(id, out object component)) component = null; return component; } public IEnumerable GetAllComponents(string id) { if (string.IsNullOrEmpty(id)) throw new ArgumentNullException(nameof(id)); if (!forwardedComponents.TryGetValue(id, out PVersionList all)) all = null; return all?.Components; } public PForwardedComponent GetLatestVersion(string id) { if (string.IsNullOrEmpty(id)) throw new ArgumentNullException(nameof(id)); if (!latestComponents.TryGetValue(id, out PForwardedComponent remoteComponent)) { #if DEBUG PRegistry.LogPatchWarning("Unable to find a component matching: " + id); #endif remoteComponent = null; } return remoteComponent; } public object GetSharedData(string id) { if (!forwardedComponents.TryGetValue(id, out PVersionList all)) all = null; return all?.SharedData; } /// /// Goes through the forwarded components, and picks the latest version of each to /// instantiate. /// public void Instantiate() { foreach (var pair in forwardedComponents) { // Sort value by version var versions = pair.Value.Components; int n = versions.Count; if (n > 0) { string id = pair.Key; versions.Sort(); var component = versions[n - 1]; latestComponents.GetOrAdd(id, component); #if DEBUG PRegistry.LogPatchDebug("Instantiating component {0} using version {1} from assembly {2}".F( id, component.Version, component.GetOwningAssembly().FullName)); #endif try { instantiatedComponents.GetOrAdd(id, component?.DoInitialize( PLibInstance)); } catch (Exception e) { PRegistry.LogPatchWarning("Error when instantiating component " + id + ":"); PUtil.LogException(e); } } } // Post initialize for component compatibility foreach (var pair in latestComponents) try { pair.Value.PostInitialize(PLibInstance); } catch (Exception e) { PRegistry.LogPatchWarning("Error when instantiating component " + pair.Key + ":"); PUtil.LogException(e); } } public void SetSharedData(string id, object data) { if (forwardedComponents.TryGetValue(id, out PVersionList all)) all.SharedData = data; } public override string ToString() { return forwardedComponents.ToString(); } } }