ソースを参照

Added AcceptableValue stuff

ManlyMarco 4 年 前
コミット
d9bf3ba225

+ 3 - 0
BepInEx/BepInEx.csproj

@@ -67,6 +67,9 @@
     </Reference>
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="Configuration\AcceptableValueBase.cs" />
+    <Compile Include="Configuration\AcceptableValueList.cs" />
+    <Compile Include="Configuration\AcceptableValueRange.cs" />
     <Compile Include="Configuration\ConfigDefinition.cs" />
     <Compile Include="Configuration\ConfigDescription.cs" />
     <Compile Include="Configuration\ConfigEntry.cs" />

+ 23 - 0
BepInEx/Configuration/AcceptableValueBase.cs

@@ -0,0 +1,23 @@
+namespace BepInEx.Configuration
+{
+	/// <summary>
+	/// Base type of all classes represeting and enforcing acceptable values of config settings.
+	/// </summary>
+	public abstract class AcceptableValueBase
+	{
+		/// <summary>
+		/// Change the value to be acceptable, if it's not already.
+		/// </summary>
+		public abstract object Clamp(object value);
+
+		/// <summary>
+		/// Check if the value is an acceptable value.
+		/// </summary>
+		public abstract bool IsValid(object value);
+
+		/// <summary>
+		/// Get the string for use in config files.
+		/// </summary>
+		public abstract string ToSerializedString();
+	}
+}

+ 46 - 0
BepInEx/Configuration/AcceptableValueList.cs

@@ -0,0 +1,46 @@
+using System;
+using System.Linq;
+
+namespace BepInEx.Configuration
+{
+	/// <summary>
+	/// Specify the list of acceptable values for a setting.
+	/// </summary>
+	public sealed class AcceptableValueList<T> : AcceptableValueBase where T : IEquatable<T>
+	{
+		private readonly T[] _acceptableValues;
+
+		/// <summary>
+		/// Specify the list of acceptable values for a setting.
+		/// If the setting does not equal any of the values, it will be set to the first one.
+		/// </summary>
+		public AcceptableValueList(params T[] acceptableValues)
+		{
+			if (acceptableValues == null) throw new ArgumentNullException(nameof(acceptableValues));
+			if (acceptableValues.Length == 0) throw new ArgumentException("At least one acceptable value is needed", nameof(acceptableValues));
+
+			_acceptableValues = acceptableValues;
+		}
+
+		/// <inheritdoc />
+		public override object Clamp(object value)
+		{
+			if (value is T v && _acceptableValues.Any(x => x.Equals(v)))
+				return value;
+
+			return _acceptableValues[0];
+		}
+
+		/// <inheritdoc />
+		public override bool IsValid(object value)
+		{
+			return value is T v && _acceptableValues.Any(x => x.Equals(v));
+		}
+
+		/// <inheritdoc />
+		public override string ToSerializedString()
+		{
+			return "# Acceptable values: " + string.Join(", ", _acceptableValues.Select(x => x.ToString()).ToArray());
+		}
+	}
+}

+ 59 - 0
BepInEx/Configuration/AcceptableValueRange.cs

@@ -0,0 +1,59 @@
+using System;
+
+namespace BepInEx.Configuration
+{
+	/// <summary>
+	/// Specify the range of acceptable values for a setting.
+	/// </summary>
+	public sealed class AcceptableValueRange<T> : AcceptableValueBase where T : IComparable
+	{
+		/// <param name="minValue">Lowest acceptable value</param>
+		/// <param name="maxValue">Highest acceptable value</param>
+		public AcceptableValueRange(T minValue, T maxValue)
+		{
+			if (maxValue == null)
+				throw new ArgumentNullException(nameof(maxValue));
+			if (minValue == null)
+				throw new ArgumentNullException(nameof(minValue));
+			if (minValue.CompareTo(maxValue) >= 0)
+				throw new ArgumentException($"{nameof(minValue)} has to be lower than {nameof(maxValue)}");
+
+			MinValue = minValue;
+			MaxValue = maxValue;
+		}
+
+		/// <summary>
+		/// Lowest acceptable value
+		/// </summary>
+		public T MinValue { get; }
+
+		/// <summary>
+		/// Highest acceptable value
+		/// </summary>
+		public T MaxValue { get; }
+
+		/// <inheritdoc />
+		public override object Clamp(object value)
+		{
+			if (MinValue.CompareTo(value) > 0)
+				return MinValue;
+
+			if (MaxValue.CompareTo(value) < 0)
+				return MaxValue;
+
+			return value;
+		}
+
+		/// <inheritdoc />
+		public override bool IsValid(object value)
+		{
+			return MinValue.CompareTo(value) <= 0 && MaxValue.CompareTo(value) >= 0;
+		}
+
+		/// <inheritdoc />
+		public override string ToSerializedString()
+		{
+			return $"# Acceptable value range: From {MinValue} to {MaxValue}";
+		}
+	}
+}

+ 16 - 3
BepInEx/Configuration/ConfigDescription.cs

@@ -2,7 +2,6 @@
 
 namespace BepInEx.Configuration
 {
-	//todo value range
 	/// <summary>
 	/// Metadata of a <see cref="ConfigEntry"/>.
 	/// </summary>
@@ -12,8 +11,12 @@ namespace BepInEx.Configuration
 		/// Create a new description.
 		/// </summary>
 		/// <param name="description">Text describing the function of the setting and any notes or warnings.</param>
-		public ConfigDescription(string description)
+		/// <param name="acceptableValues">Range of values that this setting can take. The setting's value will be automatically clamped.</param>
+		/// <param name="tags">Objects that can be used by user-made classes to add functionality.</param>
+		public ConfigDescription(string description, AcceptableValueBase acceptableValues = null, params object[] tags)
 		{
+			AcceptableValues = acceptableValues;
+			Tags = tags;
 			Description = description ?? throw new ArgumentNullException(nameof(description));
 		}
 
@@ -23,6 +26,16 @@ namespace BepInEx.Configuration
 		public string Description { get; }
 
 		/// <summary>
+		/// Range of acceptable values for a setting.
+		/// </summary>
+		public AcceptableValueBase AcceptableValues { get; }
+
+		/// <summary>
+		/// Objects that can be used by user-made classes to add functionality.
+		/// </summary>
+		public object[] Tags { get; }
+
+		/// <summary>
 		/// Convert the description object into a form suitable for writing into a config file.
 		/// </summary>
 		public string ToSerializedString()
@@ -30,4 +43,4 @@ namespace BepInEx.Configuration
 			return $"# {Description.Replace("\n", "\n# ")}";
 		}
 	}
-}
+}

+ 42 - 10
BepInEx/Configuration/ConfigEntry.cs

@@ -36,8 +36,8 @@ namespace BepInEx.Configuration
 			if (SettingType != null)
 			{
 				throw new ArgumentException($"Tried to define setting \"{Definition}\" as type {settingType.Name} " +
-				                            $"while it was already defined as type {SettingType.Name}. Use the same " +
-				                            "Type for all Wrappers of a single setting.");
+											$"while it was already defined as type {SettingType.Name}. Use the same " +
+											"Type for all Wrappers of a single setting.");
 			}
 
 			if (defaultValue == null && settingType.IsValueType)
@@ -66,7 +66,7 @@ namespace BepInEx.Configuration
 		/// <summary>
 		/// Description / metadata of this setting.
 		/// </summary>
-		public ConfigDescription Description { get; internal set; }
+		public ConfigDescription Description { get; private set; }
 
 		/// <summary>
 		/// Type of the <see cref="Value"/> that this setting holds.
@@ -102,6 +102,7 @@ namespace BepInEx.Configuration
 		internal void SetValue(object newValue, bool fireEvent, object sender)
 		{
 			bool wasChanged = ProcessSerializedValue();
+			newValue = ClampValue(newValue);
 			wasChanged = wasChanged || !Equals(newValue, _convertedValue);
 
 			if (wasChanged)
@@ -140,7 +141,7 @@ namespace BepInEx.Configuration
 			_serializedValue = newValue;
 
 			if (!IsDefined) return;
-			
+
 			if (ProcessSerializedValue())
 			{
 				if (fireEvent)
@@ -163,6 +164,7 @@ namespace BepInEx.Configuration
 					try
 					{
 						var newValue = TomlTypeConverter.ConvertToValue(value, SettingType);
+						newValue = ClampValue(newValue);
 						if (!Equals(newValue, _convertedValue))
 						{
 							_convertedValue = newValue;
@@ -173,7 +175,7 @@ namespace BepInEx.Configuration
 					catch (Exception e)
 					{
 						Logger.Log(LogLevel.Warning, $"Config value of setting \"{Definition}\" could not be " +
-						                             $"parsed and will be ignored. Reason: {e.Message}; Value: {value}");
+													 $"parsed and will be ignored. Reason: {e.Message}; Value: {value}");
 					}
 				}
 			}
@@ -187,6 +189,19 @@ namespace BepInEx.Configuration
 			return false;
 		}
 
+		internal void SetDescription(ConfigDescription configDescription)
+		{
+			Description = configDescription;
+			SetValue(ClampValue(Value), true, this);
+		}
+
+		private object ClampValue(object value)
+		{
+			if (Description?.AcceptableValues != null)
+				return Description.AcceptableValues.Clamp(value);
+			return value;
+		}
+
 		private void OnSettingChanged(object sender)
 		{
 			ConfigFile.OnSettingChanged(sender, this);
@@ -197,18 +212,35 @@ namespace BepInEx.Configuration
 		/// </summary>
 		public void WriteDescription(StreamWriter writer)
 		{
-			if (Description != null)
+			bool hasDescription = Description != null;
+			bool hasType = SettingType != null;
+
+			if (hasDescription)
 				writer.WriteLine(Description.ToSerializedString());
 
-			if (SettingType != null)
+			if (hasType)
 			{
 				writer.WriteLine("# Setting type: " + SettingType.Name);
-				writer.WriteLine("# Default value: " + DefaultValue);
-
-				// todo acceptable values
 
 				if (SettingType.IsEnum && SettingType.GetCustomAttributes(typeof(FlagsAttribute), true).Any())
 					writer.WriteLine("# Multiple values can be set at the same time by separating them with , (e.g. Debug, Warning)");
+
+				writer.WriteLine("# Default value: " + DefaultValue);
+			}
+
+			if (hasDescription)
+			{
+				if (Description.AcceptableValues != null)
+				{
+					writer.WriteLine(Description.AcceptableValues.ToSerializedString());
+				}
+				else if (hasType)
+				{
+					if (SettingType == typeof(bool))
+						writer.WriteLine("# Acceptable values: True, False");
+					else if (SettingType.IsEnum)
+						writer.WriteLine("# Acceptable values: " + string.Join(", ", Enum.GetNames(SettingType)));
+				}
 			}
 		}
 	}

+ 8 - 1
BepInEx/Configuration/ConfigFile.cs

@@ -200,7 +200,14 @@ namespace BepInEx.Configuration
 				if (entry.Description != null)
 					Logger.Log(LogLevel.Warning, $"Tried to add configDescription to setting {configDefinition} when it already had one defined. Only add configDescription once or a random one will be used.");
 
-				entry.Description = configDescription;
+				if (configDescription.AcceptableValues != null)
+				{
+					var genericArguments = configDescription.AcceptableValues.GetType().GetGenericArguments();
+					if (genericArguments.Length > 0 && genericArguments[0] != typeof(T))
+						throw new ArgumentException("AcceptableValues has a different type than the setting type", nameof(configDefinition));
+				}
+
+				entry.SetDescription(configDescription);
 			}
 
 			return new ConfigWrapper<T>(entry);

+ 3 - 0
BepInExTests/BepInExTests.csproj

@@ -52,6 +52,9 @@
       <HintPath>..\packages\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll</HintPath>
     </Reference>
     <Reference Include="System" />
+    <Reference Include="UnityEngine">
+      <HintPath>..\lib\UnityEngine.dll</HintPath>
+    </Reference>
   </ItemGroup>
   <Choose>
     <When Condition="('$(VisualStudioVersion)' == '10.0' or '$(VisualStudioVersion)' == '') and '$(TargetFrameworkVersion)' == 'v3.5'">

+ 68 - 1
BepInExTests/Configuration/ConfigFileTests.cs

@@ -168,5 +168,72 @@ namespace BepInEx.Configuration.Tests
 
 			Assert.IsTrue(eventFired);
 		}
+
+		[TestMethod]
+		public void ValueRangeTest()
+		{
+			var c = MakeConfig();
+			var w = c.Wrap("Cat", "Key", 0, new ConfigDescription("Test", new AcceptableValueRange<int>(0, 2)));
+
+			Assert.AreEqual(0, w.Value);
+			w.Value = 2;
+			Assert.AreEqual(2, w.Value);
+			w.Value = -2;
+			Assert.AreEqual(0, w.Value);
+			w.Value = 4;
+			Assert.AreEqual(2, w.Value);
+		}
+
+		[TestMethod]
+		[ExpectedException(typeof(ArgumentException))]
+		public void ValueRangeBadTypeTest()
+		{
+			var c = MakeConfig();
+			c.Wrap("Cat", "Key", 0, new ConfigDescription("Test", new AcceptableValueRange<float>(1, 2)));
+			Assert.Fail();
+		}
+
+		[TestMethod]
+		public void ValueRangeDefaultTest()
+		{
+			var c = MakeConfig();
+			var w = c.Wrap("Cat", "Key", 0, new ConfigDescription("Test", new AcceptableValueRange<int>(1, 2)));
+
+			Assert.AreEqual(w.Value, 1);
+		}
+
+		[TestMethod]
+		public void ValueRangeLoadTest()
+		{
+			var c = MakeConfig();
+			c.StopWatching();
+
+			File.WriteAllText(c.ConfigFilePath, "[Cat]\nKey = 1\n");
+			c.Reload();
+
+			var w = c.Wrap("Cat", "Key", 0, new ConfigDescription("Test", new AcceptableValueRange<int>(0, 2)));
+
+			Assert.AreEqual(w.Value, 1);
+
+			File.WriteAllText(c.ConfigFilePath, "[Cat]\nKey = 5\n");
+			c.Reload();
+
+			Assert.AreEqual(w.Value, 2);
+		}
+
+		[TestMethod]
+		public void ValueListTest()
+		{
+			var c = MakeConfig();
+			var w = c.Wrap<string>("Cat", "Key", "kek", new ConfigDescription("Test", new AcceptableValueList<string>("lel", "kek", "wew", "why")));
+
+			Assert.AreEqual("kek", w.Value);
+			w.Value = "wew";
+			Assert.AreEqual("wew", w.Value);
+			w.Value = "no";
+			Assert.AreEqual("lel", w.Value);
+			w.Value = null;
+			Assert.AreEqual("lel", w.Value);
+		}
 	}
-}
+}