Skip to content

Commit cd12c35

Browse files
authored
Fix #321 : Fix exceptions when calling EnumExtensions.displayDescription() (#322)
Fix #321 : Fix exceptions when calling the stock `EnumExtensions.displayDescription()` whith an enum value that isn't defined, a stock bug occuring more consistently when the KSPCF KSPFieldEnumDesc patch is enabled. As part of this new FastAndFixedEnumExtensions patch, also implement a cache for the enum descriptions.
1 parent 8bfec76 commit cd12c35

File tree

6 files changed

+114
-3
lines changed

6 files changed

+114
-3
lines changed

GameData/KSPCommunityFixes/Settings.cfg

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,10 @@ KSP_COMMUNITY_FIXES
233233
// Fix exception spam when a radiator set to `parentCoolingOnly` is detached from the vessel
234234
ModuleActiveRadiatorNoParentException = true
235235
236+
// Fix exceptions when calling the `EnumExtensions.*Description()` methods with a non-defined
237+
// enum value, and implement a cache for faster and less allocating execution of those methods.
238+
FastAndFixedEnumExtensions = true
239+
236240
// Fix the Alt+F12 console input field stealing input when a console entry is added
237241
DebugConsoleDontStealInput = true
238242
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
using KSP.Localization;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.ComponentModel;
5+
using System.Reflection;
6+
7+
namespace KSPCommunityFixes
8+
{
9+
internal class FastAndFixedEnumExtensions : BasePatch
10+
{
11+
internal static Dictionary<Type, Dictionary<long, EnumMemberDescription>> enumMemberDescriptionCache = new Dictionary<Type, Dictionary<long, EnumMemberDescription>>();
12+
13+
internal class EnumMemberDescription
14+
{
15+
public string description;
16+
public string localizedDescription;
17+
18+
public EnumMemberDescription(string memberName, DescriptionAttribute descriptionAttribute = null)
19+
{
20+
description = descriptionAttribute?.Description ?? memberName;
21+
localizedDescription = Localizer.Format(description);
22+
}
23+
}
24+
25+
protected override void ApplyPatches()
26+
{
27+
AddPatch(PatchType.Override, typeof(EnumExtensions), nameof(EnumExtensions.Description));
28+
AddPatch(PatchType.Override, typeof(EnumExtensions), nameof(EnumExtensions.displayDescription));
29+
}
30+
31+
internal static bool TryGetEnumMemberDescription(Enum enumValue, out EnumMemberDescription enumMemberDescription)
32+
{
33+
Type enumType = enumValue.GetType();
34+
if (!enumMemberDescriptionCache.TryGetValue(enumType, out Dictionary<long, EnumMemberDescription> enumDescriptions))
35+
{
36+
enumDescriptions = new Dictionary<long, EnumMemberDescription>();
37+
string[] names = enumType.GetEnumNames();
38+
39+
foreach (string enumMemberName in names)
40+
{
41+
MemberInfo[] enumMembers = enumType.GetMember(enumMemberName);
42+
if (enumMembers.Length == 0)
43+
continue;
44+
45+
DescriptionAttribute descriptionAttribute = enumMembers[0].GetCustomAttribute<DescriptionAttribute>();
46+
Enum enumMember = (Enum)Enum.Parse(enumType, enumMemberName);
47+
enumDescriptions.Add(enumMember.GetSignedBoxedEnumValue(), new EnumMemberDescription(enumMemberName, descriptionAttribute));
48+
}
49+
50+
enumMemberDescriptionCache.Add(enumType, enumDescriptions);
51+
}
52+
53+
if (enumDescriptions.TryGetValue(enumValue.GetSignedBoxedEnumValue(), out enumMemberDescription))
54+
return true;
55+
56+
return false;
57+
}
58+
59+
internal static string EnumExtensions_Description_Override(Enum e)
60+
{
61+
if (!TryGetEnumMemberDescription(e, out EnumMemberDescription enumMemberDescription))
62+
return e.ToString();
63+
64+
return enumMemberDescription.description;
65+
}
66+
67+
internal static string EnumExtensions_displayDescription_Override(Enum e)
68+
{
69+
if (!TryGetEnumMemberDescription(e, out EnumMemberDescription enumMemberDescription))
70+
return Localizer.Format(e.ToString());
71+
72+
return enumMemberDescription.localizedDescription;
73+
}
74+
}
75+
}

KSPCommunityFixes/KSPCommunityFixes.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
<Publicize Include="UnityEngine.CoreModule:UnityEngine.Object.GetOffsetOfInstanceIDInCPlusPlusObject" />
100100
<Publicize Include="UnityEngine.IMGUIModule" />
101101
<Publicize Include="UnityEngine.CoreModule:Unity.Collections.NativeArray`1.m_Buffer" />
102+
<Publicize Include="mscorlib:System.Runtime.CompilerServices.Unsafe" />
102103
<Publicize Include="mscorlib:System.IO.MonoIO" />
103104
<Publicize Include="mscorlib:System.IO.MonoIOError" />
104105
<Publicize Include="mscorlib:System.IO.MonoIOStat" />
@@ -129,6 +130,7 @@
129130
<Compile Include="BugFixes\DebugConsoleDontStealInput.cs" />
130131
<Compile Include="BugFixes\DragCubeLoadException.cs" />
131132
<Compile Include="BugFixes\EVAConstructionMass.cs" />
133+
<Compile Include="BugFixes\FastAndFixedEnumExtensions.cs" />
132134
<Compile Include="BugFixes\InventoryPartMass.cs" />
133135
<Compile Include="BugFixes\ModuleActiveRadiatorNoParentException.cs" />
134136
<Compile Include="BugFixes\ModulePartVariantsNodePersistence.cs" />

KSPCommunityFixes/Library/Extensions.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,35 @@ public static bool IsPAWOpen(this Part part)
2929
{
3030
return part.PartActionWindow.IsNotNullOrDestroyed() && part.PartActionWindow.isActiveAndEnabled;
3131
}
32+
33+
/// <summary>
34+
/// Get the value of a boxed enum, as a 64 bit signed integer.
35+
/// If the enum underlying type is an unsigned 64 bit integer and the value is greater than long.MaxValue,
36+
/// the result will overflow and be negative.
37+
/// </summary>
38+
public static long GetSignedBoxedEnumValue(this Enum enumInstance)
39+
{
40+
Type underlyingType = enumInstance.GetType().GetEnumUnderlyingType();
41+
42+
if (underlyingType == typeof(int))
43+
return (int)(object)enumInstance;
44+
else if (underlyingType == typeof(sbyte))
45+
return (sbyte)(object)enumInstance;
46+
else if (underlyingType == typeof(short))
47+
return (short)(object)enumInstance;
48+
else if (underlyingType == typeof(long))
49+
return (long)(object)enumInstance;
50+
else if (underlyingType == typeof(uint))
51+
return (uint)(object)enumInstance;
52+
else if (underlyingType == typeof(byte))
53+
return (byte)(object)enumInstance;
54+
else if (underlyingType == typeof(ushort))
55+
return (ushort)(object)enumInstance;
56+
else if (underlyingType == typeof(ulong))
57+
return (long)(ulong)(object)enumInstance;
58+
59+
throw new Exception($"Enum {enumInstance.GetType()} is of unknown size");
60+
}
3261
}
3362

3463
static class ParticleBuffer

KSPCommunityFixes/Modding/KSPFieldEnumDesc.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ internal static bool BaseField_GetStringValue_Prefix(BaseField __instance, objec
1818
Type fieldType = __instance.FieldInfo.FieldType;
1919
if (fieldType.IsEnum)
2020
{
21-
var val = (Enum)__instance.GetValue(host);
22-
__result = val.displayDescription();
21+
Enum val = (Enum)__instance.GetValue(host);
22+
__result = FastAndFixedEnumExtensions.EnumExtensions_displayDescription_Override(val);
2323
return false;
2424
}
2525

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ User options are available from the "ESC" in-game settings menu :<br/><img src="
9494
- [**DragCubeLoadException**](https://github.com/KSPModdingLibs/KSPCommunityFixes/pull/232) [KSP 1.8.0 - 1.12.5]<br/>Fix loading of drag cubes without a name failing with an IndexOutOfRangeException
9595
- [**TimeWarpBodyCollision**](https://github.com/KSPModdingLibs/KSPCommunityFixes/pull/259) [KSP 1.12.0 - 1.12.5]<br/>Fix timewarp rate not always being limited on SOI transistions, sometimes resulting in failure to detect an encounter/collision with the body in the next SOI.
9696
- [**ModuleActiveRadiatorNoParentException**](https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/249) [KSP 1.12.3 - 1.12.5]<br/>Fix exception spam when a radiator set to `parentCoolingOnly` is detached from the vessel
97-
- **DebugConsoleDontStealInput** [KSP 1.12.3 - 1.12.5]<br/>Fix the Alt+F12 console input field stealing input when a console entry is added
97+
- [**FastAndFixedEnumExtensions**](https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/321) [KSP 1.12.3 - 1.12.5]<br/>Fix exceptions when calling the `EnumExtensions.*Description()` methods with a non-defined enum value, and implement a cache for faster and less allocating execution of those methods.
98+
- [**DebugConsoleDontStealInput**](https://github.com/KSPModdingLibs/KSPCommunityFixes/pull/322) [KSP 1.12.3 - 1.12.5]<br/>Fix the Alt+F12 console input field stealing input when a console entry is added
9899

99100
#### Quality of Life tweaks
100101

0 commit comments

Comments
 (0)