/*
* 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 System;
using System.Collections.Generic;
using System.Reflection;
using PeterHan.PLib.Core;
using PeterHan.PLib.Detours;
using UnityEngine;
namespace PeterHan.PLib.Options {
///
/// Registers types to options entry classes that can handle them.
///
public static class OptionsHandlers {
private delegate IOptionsEntry CreateOption(string field, IOptionSpec spec);
private delegate IOptionsEntry CreateOptionType(string field, IOptionSpec spec,
Type fieldType);
private delegate IOptionsEntry CreateOptionLimit(string field, IOptionSpec spec,
LimitAttribute limit);
///
/// Maps types to the constructor delegate that can create an options entry for them.
///
private static readonly IDictionary OPTIONS_HANDLERS =
new Dictionary(64);
///
/// Adds a custom type to handle all options entries of a specific type. The change
/// will only affect this mod's options.
///
/// The property type to be handled.
/// The type which will handle all option attributes of
/// this type. It must subclass from IOptionsEntry and have a constructor of the
/// signature HandlerType(string, IOptionSpec).
public static void AddOptionClass(Type optionType, Type handlerType) {
if (optionType != null && handlerType != null && !OPTIONS_HANDLERS.
ContainsKey(optionType) && typeof(IOptionsEntry).IsAssignableFrom(
handlerType)) {
var constructors = handlerType.GetConstructors();
int n = constructors.Length;
for (int i = 0; i < n; i++) {
var del = CreateDelegate(handlerType, constructors[i]);
if (del != null) {
OPTIONS_HANDLERS[optionType] = del;
break;
}
}
}
}
///
/// If a candidate options entry constructor is valid, creates a delegate which can
/// call the constructor.
///
/// The constructor to wrap.
/// The type which will handle all option attributes of this type.
/// If the constructor can be used to create an options entry, a delegate
/// which calls the constructor using one of the delegate types declared in this class;
/// otherwise, null.
private static Delegate CreateDelegate(Type handlerType, ConstructorInfo constructor) {
var param = constructor.GetParameters();
int n = param.Length;
Delegate result = null;
// Must begin with string, IOptionsSpec
if (n > 1 && param[0].ParameterType.IsAssignableFrom(typeof(string)) &&
param[1].ParameterType.IsAssignableFrom(typeof(IOptionSpec))) {
switch (n) {
case 2:
result = constructor.Detour();
break;
case 3:
var extraType = param[2].ParameterType;
if (extraType.IsAssignableFrom(typeof(LimitAttribute)))
result = constructor.Detour();
else if (extraType.IsAssignableFrom(typeof(Type)))
result = constructor.Detour();
break;
default:
PUtil.LogWarning("Constructor on options handler type " + handlerType +
" cannot be constructed by OptionsHandlers");
break;
}
}
return result;
}
///
/// Creates an options entry wrapper for the specified property.
///
/// The property to wrap.
/// The option title and tool tip.
/// An options wrapper, or null if none can handle this type.
public static IOptionsEntry FindOptionClass(IOptionSpec spec, PropertyInfo info) {
IOptionsEntry entry = null;
if (spec != null && info != null) {
var type = info.PropertyType;
string field = info.Name;
if (type.IsEnum)
// Enumeration type
entry = new SelectOneOptionsEntry(field, spec, type);
else if (OPTIONS_HANDLERS.TryGetValue(type, out var handler)) {
if (handler is CreateOption createOption)
entry = createOption.Invoke(field, spec);
else if (handler is CreateOptionLimit createOptionLimit)
entry = createOptionLimit.Invoke(field, spec, info.
GetCustomAttribute());
else if (handler is CreateOptionType createOptionType)
entry = createOptionType.Invoke(field, spec, type);
}
}
return entry;
}
///
/// Adds the predefined options classes.
///
internal static void InitPredefinedOptions() {
if (OPTIONS_HANDLERS.Count < 1) {
AddOptionClass(typeof(bool), typeof(CheckboxOptionsEntry));
AddOptionClass(typeof(int), typeof(IntOptionsEntry));
AddOptionClass(typeof(int?), typeof(NullableIntOptionsEntry));
AddOptionClass(typeof(float), typeof(FloatOptionsEntry));
AddOptionClass(typeof(float?), typeof(NullableFloatOptionsEntry));
AddOptionClass(typeof(Color32), typeof(Color32OptionsEntry));
AddOptionClass(typeof(Color), typeof(ColorOptionsEntry));
AddOptionClass(typeof(int), typeof(IntOptionsEntry));
AddOptionClass(typeof(Action