/*
* 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();
}
}
}