ConfigEntryBase.cs 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. using System;
  2. using System.IO;
  3. using System.Linq;
  4. using BepInEx.Logging;
  5. namespace BepInEx.Configuration
  6. {
  7. /// <summary>
  8. /// Provides access to a single setting inside of a <see cref="Configuration.ConfigFile"/>.
  9. /// </summary>
  10. /// <typeparam name="T">Type of the setting.</typeparam>
  11. public sealed class ConfigEntry<T> : ConfigEntryBase
  12. {
  13. /// <summary>
  14. /// Fired when the setting is changed. Does not detect changes made outside from this object.
  15. /// </summary>
  16. public event EventHandler SettingChanged;
  17. private T _typedValue;
  18. /// <summary>
  19. /// Value of this setting.
  20. /// </summary>
  21. public T Value
  22. {
  23. get => _typedValue;
  24. set
  25. {
  26. value = ClampValue(value);
  27. if (Equals(_typedValue, value))
  28. return;
  29. _typedValue = value;
  30. OnSettingChanged(this);
  31. }
  32. }
  33. /// <inheritdoc />
  34. public override object BoxedValue
  35. {
  36. get => Value;
  37. set => Value = (T)value;
  38. }
  39. internal ConfigEntry(ConfigFile configFile, ConfigDefinition definition, T defaultValue, ConfigDescription configDescription) : base(configFile, definition, typeof(T), defaultValue, configDescription)
  40. {
  41. configFile.SettingChanged += (sender, args) =>
  42. {
  43. if (args.ChangedSetting == this) SettingChanged?.Invoke(sender, args);
  44. };
  45. }
  46. }
  47. /// <summary>
  48. /// Container for a single setting of a <see cref="Configuration.ConfigFile"/>.
  49. /// Each config entry is linked to one config file.
  50. /// </summary>
  51. public abstract class ConfigEntryBase
  52. {
  53. /// <summary>
  54. /// Types of defaultValue and definition.AcceptableValues have to be the same as settingType.
  55. /// </summary>
  56. internal ConfigEntryBase(ConfigFile configFile, ConfigDefinition definition, Type settingType, object defaultValue, ConfigDescription configDescription)
  57. {
  58. ConfigFile = configFile ?? throw new ArgumentNullException(nameof(configFile));
  59. Definition = definition ?? throw new ArgumentNullException(nameof(definition));
  60. SettingType = settingType ?? throw new ArgumentNullException(nameof(settingType));
  61. Description = configDescription ?? ConfigDescription.Empty;
  62. if (Description.AcceptableValues != null && !SettingType.IsAssignableFrom(Description.AcceptableValues.ValueType))
  63. throw new ArgumentException("configDescription.AcceptableValues is for a different type than the type of this setting");
  64. DefaultValue = defaultValue;
  65. // Free type check and automatically calls ClampValue in case AcceptableValues were provided
  66. BoxedValue = defaultValue;
  67. }
  68. /// <summary>
  69. /// Config file this entry is a part of.
  70. /// </summary>
  71. public ConfigFile ConfigFile { get; }
  72. /// <summary>
  73. /// Category and name of this setting. Used as a unique key for identification within a <see cref="Configuration.ConfigFile"/>.
  74. /// </summary>
  75. public ConfigDefinition Definition { get; }
  76. /// <summary>
  77. /// Description / metadata of this setting.
  78. /// </summary>
  79. public ConfigDescription Description { get; }
  80. /// <summary>
  81. /// Type of the <see cref="BoxedValue"/> that this setting holds.
  82. /// </summary>
  83. public Type SettingType { get; }
  84. /// <summary>
  85. /// Default value of this setting (set only if the setting was not changed before).
  86. /// </summary>
  87. public object DefaultValue { get; }
  88. /// <summary>
  89. /// Get or set the value of the setting.
  90. /// </summary>
  91. public abstract object BoxedValue { get; set; }
  92. /// <summary>
  93. /// Get the serialized representation of the value.
  94. /// </summary>
  95. public string GetSerializedValue()
  96. {
  97. return TomlTypeConverter.ConvertToString(BoxedValue, SettingType);
  98. }
  99. /// <summary>
  100. /// Set the value by using its serialized form.
  101. /// </summary>
  102. public void SetSerializedValue(string value)
  103. {
  104. try
  105. {
  106. var newValue = TomlTypeConverter.ConvertToValue(value, SettingType);
  107. BoxedValue = newValue;
  108. }
  109. catch (Exception e)
  110. {
  111. Logger.Log(LogLevel.Warning, $"Config value of setting \"{Definition}\" could not be parsed and will be ignored. Reason: {e.Message}; Value: {value}");
  112. }
  113. }
  114. /// <summary>
  115. /// If necessary, clamp the value to acceptable value range. T has to be equal to settingType.
  116. /// </summary>
  117. protected T ClampValue<T>(T value)
  118. {
  119. if (Description.AcceptableValues != null)
  120. return (T)Description.AcceptableValues.Clamp(value);
  121. return value;
  122. }
  123. /// <summary>
  124. /// Trigger setting changed event.
  125. /// </summary>
  126. protected void OnSettingChanged(object sender)
  127. {
  128. ConfigFile.OnSettingChanged(sender, this);
  129. }
  130. /// <summary>
  131. /// Write a description of this setting using all available metadata.
  132. /// </summary>
  133. public void WriteDescription(StreamWriter writer)
  134. {
  135. if (!string.IsNullOrEmpty(Description.Description))
  136. writer.WriteLine($"## {Description.Description.Replace("\n", "\n## ")}");
  137. writer.WriteLine("# Setting type: " + SettingType.Name);
  138. writer.WriteLine("# Default value: " + TomlTypeConverter.ConvertToString(DefaultValue, SettingType));
  139. if (Description.AcceptableValues != null)
  140. {
  141. writer.WriteLine(Description.AcceptableValues.ToDescriptionString());
  142. }
  143. else if (SettingType.IsEnum)
  144. {
  145. writer.WriteLine("# Acceptable values: " + string.Join(", ", Enum.GetNames(SettingType)));
  146. if (SettingType.GetCustomAttributes(typeof(FlagsAttribute), true).Any())
  147. writer.WriteLine("# Multiple values can be set at the same time by separating them with , (e.g. Debug, Warning)");
  148. }
  149. }
  150. }
  151. }