Skip to content

Commit e1fd24c

Browse files
committed
Merge branch 'dev' into release
2 parents 0b65bda + 5deca53 commit e1fd24c

19 files changed

+561
-148
lines changed

Mutagen.Bethesda.Core/Mutagen.Bethesda.Core.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@
150150
<Compile Include="Installs\SteamGameSource.cs" />
151151
<Compile Include="Plugins\Analysis\DI\MultiModFileSplitter.cs" />
152152
<Compile Include="Plugins\Analysis\DI\RecordCompactionCompatibilityDetector.cs" />
153+
<Compile Include="Plugins\Analysis\RecordCompactionCompatibilityDetection.cs" />
153154
<Compile Include="Plugins\Analysis\RecordLocationMarker.cs" />
154155
<Compile Include="Plugins\Analysis\RecordLocator.cs">
155156
<CodeLanguage>cs</CodeLanguage>
@@ -551,6 +552,7 @@
551552
<Compile Include="Plugins\Utility\CategoryToGenericCallHelper.cs" />
552553
<Compile Include="Plugins\Utility\DI\ModCompactor.cs" />
553554
<Compile Include="Plugins\Utility\HeaderVersionHelper.cs" />
555+
<Compile Include="Plugins\Utility\ModCompaction.cs" />
554556
<Compile Include="Plugins\Warmup.cs">
555557
<CodeLanguage>cs</CodeLanguage>
556558
<DefaultPackFolder>content</DefaultPackFolder>

Mutagen.Bethesda.Core/Plugins/Analysis/DI/RecordCompactionCompatibilityDetector.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ void ThrowIfIncompatible(
2323

2424
public class RecordCompactionCompatibilityDetector : IRecordCompactionCompatibilityDetector
2525
{
26-
2726
public bool IsSmallMasterCompatible(IModGetter mod)
2827
{
2928
var range = GetSmallMasterRange(mod);
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using Mutagen.Bethesda.Plugins.Analysis.DI;
2+
using Mutagen.Bethesda.Plugins.Records;
3+
using Noggog;
4+
5+
namespace Mutagen.Bethesda.Plugins.Analysis;
6+
7+
public static class RecordCompactionCompatibilityDetection
8+
{
9+
private static RecordCompactionCompatibilityDetector _detector = new();
10+
11+
public static bool IsSmallMasterCompatible(IModGetter mod)
12+
{
13+
return _detector.IsSmallMasterCompatible(mod);
14+
}
15+
16+
public static bool CouldBeSmallMasterCompatible(IModGetter mod)
17+
{
18+
return _detector.CouldBeSmallMasterCompatible(mod);
19+
}
20+
21+
public static bool IsMediumMasterCompatible(IModGetter mod)
22+
{
23+
return _detector.IsMediumMasterCompatible(mod);
24+
}
25+
26+
public static bool CouldBeMediumMasterCompatible(IModGetter mod)
27+
{
28+
return _detector.CouldBeMediumMasterCompatible(mod);
29+
}
30+
31+
public static RangeUInt32? GetSmallMasterRange(IModGetter mod)
32+
{
33+
return _detector.GetSmallMasterRange(mod);
34+
}
35+
36+
public static RangeUInt32? GetMediumMasterRange(IModGetter mod)
37+
{
38+
return _detector.GetMediumMasterRange(mod);
39+
}
40+
41+
public static RangeUInt32 GetFullMasterRange(IModGetter mod, bool potential)
42+
{
43+
return _detector.GetFullMasterRange(mod, potential);
44+
}
45+
46+
public static RangeUInt32? GetAllowedRange(IModGetter mod, bool potential)
47+
{
48+
return _detector.GetAllowedRange(mod, potential);
49+
}
50+
51+
public static void IterateAndThrowIfIncompatible(IModGetter mod, bool potential)
52+
{
53+
_detector.IterateAndThrowIfIncompatible(mod, potential);
54+
}
55+
}

Mutagen.Bethesda.Core/Plugins/Exceptions/RecordException.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Loqui;
2+
using Mutagen.Bethesda.Plugins.Cache;
23
using Mutagen.Bethesda.Plugins.Records;
34

45
namespace Mutagen.Bethesda.Plugins.Exceptions;
@@ -57,6 +58,8 @@ public RecordException(FormKey? formKey, Type? recordType, ModKey? modKey, strin
5758
/// <summary>
5859
/// Wraps an exception to associate it with a specific major record
5960
/// </summary>
61+
/// <param name="ex">Exception to enrich</param>
62+
/// <param name="majorRec">Major Record to pull information from</param>
6063
public static RecordException Enrich(Exception ex, IMajorRecordGetter? majorRec)
6164
{
6265
return Enrich(ex, majorRec?.FormKey, majorRec?.Registration.ClassType, majorRec?.EditorID);
@@ -65,6 +68,9 @@ public static RecordException Enrich(Exception ex, IMajorRecordGetter? majorRec)
6568
/// <summary>
6669
/// Wraps an exception to associate it with a specific major record
6770
/// </summary>
71+
/// <param name="ex">Exception to enrich</param>
72+
/// <param name="modKey">ModKey to mark as containing the record</param>
73+
/// <param name="majorRec">Major Record to pull information from</param>
6874
public static RecordException Enrich(Exception ex, ModKey? modKey, IMajorRecordGetter? majorRec)
6975
{
7076
return Enrich(ex, majorRec?.FormKey, majorRec?.Registration.ClassType, majorRec?.EditorID, modKey);
@@ -73,6 +79,11 @@ public static RecordException Enrich(Exception ex, ModKey? modKey, IMajorRecordG
7379
/// <summary>
7480
/// Wraps an exception to associate it with a specific major record
7581
/// </summary>
82+
/// <param name="ex">Exception to enrich</param>
83+
/// <param name="formKey">FormKey to mark the exception to be associated with</param>
84+
/// <param name="recordType">C# Type that the record is</param>
85+
/// <param name="edid">EditorID to mark the exception to be associated with</param>
86+
/// <param name="modKey">ModKey to mark as containing the record</param>
7687
public static RecordException Enrich(Exception ex, FormKey? formKey, Type? recordType, string? edid = null, ModKey? modKey = null)
7788
{
7889
if (ex is RecordException rec)
@@ -114,6 +125,10 @@ private static Type GetRecordType(Type t)
114125
/// <summary>
115126
/// Wraps an exception to associate it with a specific major record
116127
/// </summary>
128+
/// <param name="ex">Exception to enrich</param>
129+
/// <param name="formKey">FormKey to mark the exception to be associated with</param>
130+
/// <param name="edid">EditorID to mark the exception to be associated with</param>
131+
/// <param name="modKey">ModKey to mark as containing the record</param>
117132
public static RecordException Enrich<TMajor>(Exception ex, FormKey? formKey, string? edid, ModKey? modKey = null)
118133
where TMajor : IMajorRecordGetter
119134
{
@@ -128,6 +143,8 @@ public static RecordException Enrich<TMajor>(Exception ex, FormKey? formKey, str
128143
/// <summary>
129144
/// Wraps an exception to associate it with a specific major record
130145
/// </summary>
146+
/// <param name="ex">Exception to enrich</param>
147+
/// <param name="modKey">ModKey to mark as containing the record</param>
131148
public static RecordException Enrich(Exception ex, ModKey modKey)
132149
{
133150
if (ex is RecordException rec)
@@ -146,6 +163,17 @@ public static RecordException Enrich(Exception ex, ModKey modKey)
146163
innerException: ex);
147164
}
148165

166+
/// <summary>
167+
/// Wraps an exception to associate it with a specific major record
168+
/// </summary>
169+
/// <param name="ex">Exception to enrich</param>
170+
/// <param name="majorRecordContext">ModContext to pull information from</param>
171+
public static RecordException Enrich<TMajor>(Exception ex, IModContext<TMajor> majorRecordContext)
172+
where TMajor : IMajorRecordGetter
173+
{
174+
return Enrich(ex, modKey: majorRecordContext.ModKey, majorRec: majorRecordContext.Record);
175+
}
176+
149177
#endregion
150178

151179
#region Create

Mutagen.Bethesda.Core/Plugins/Utility/CategoryToGenericCallHelper.cs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
using System.Reflection;
22
using Loqui;
3-
using Mutagen.Bethesda.Plugins.Records;
43

54
namespace Mutagen.Bethesda.Plugins.Utility;
65

76
public static class ModToGenericCallHelper
87
{
9-
public static object? InvokeFromCategory(IModGetter mod, MethodInfo methodInfo, params object?[] parameters)
8+
public static object? InvokeFromCategory<TSource>(TSource sourceObj, GameCategory category, MethodInfo methodInfo, params object?[] parameters)
109
{
11-
var category = mod.GameRelease.ToCategory();
1210
var typeStr = $"Mutagen.Bethesda.{category}.{category}Mod";
1311

1412
Warmup.Init();
@@ -20,21 +18,21 @@ public static class ModToGenericCallHelper
2018

2119
var genMethod = methodInfo.MakeGenericMethod(new Type[] { regis.SetterType, regis.GetterType });
2220

23-
return genMethod.Invoke(mod, parameters);
21+
return genMethod.Invoke(sourceObj, parameters);
2422
}
2523

26-
public static async Task InvokeFromCategoryAsync(IModGetter mod, MethodInfo methodInfo, params object?[] parameters)
24+
public static async Task InvokeFromCategoryAsync<TSource>(TSource sourceObj, GameCategory category, MethodInfo methodInfo, params object?[] parameters)
2725
{
28-
var obj = InvokeFromCategory(mod, methodInfo, parameters);
26+
var obj = InvokeFromCategory(sourceObj, category, methodInfo, parameters);
2927
if (obj is Task t)
3028
{
3129
await t;
3230
}
3331
}
3432

35-
public static async Task<TRet> InvokeFromCategoryAsync<TRet>(IModGetter mod, MethodInfo methodInfo, params object?[] parameters)
33+
public static async Task<TRet> InvokeFromCategoryAsync<TSource, TRet>(TSource sourceObj, GameCategory category, MethodInfo methodInfo, params object?[] parameters)
3634
{
37-
var obj = InvokeFromCategory(mod, methodInfo, parameters);
35+
var obj = InvokeFromCategory(sourceObj, category, methodInfo, parameters);
3836
if (obj is Task<TRet> t)
3937
{
4038
await t;

Mutagen.Bethesda.Core/Plugins/Utility/DI/ModCompactor.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@ public interface IModCompactor
1818
public class ModCompactor : IModCompactor
1919
{
2020
private readonly IRecordCompactionCompatibilityDetector _compactionCompatibilityDetector;
21+
private readonly MethodInfo _methodInfo;
2122

2223
public ModCompactor(IRecordCompactionCompatibilityDetector compactionCompatibilityDetector)
2324
{
2425
_compactionCompatibilityDetector = compactionCompatibilityDetector;
26+
_methodInfo = typeof(ModCompactor).GetMethod(nameof(DoCompactingGeneric), BindingFlags.NonPublic | BindingFlags.Instance)!;
2527
}
2628

2729
public void CompactToSmallMaster(IMod mod)
@@ -127,15 +129,16 @@ public void CompactToWithFallback(IMod mod, MasterStyle style)
127129
CompactToFullMaster(mod);
128130
}
129131

130-
private static void DoCompacting(IMod mod, RangeUInt32 range)
132+
private void DoCompacting(IMod mod, RangeUInt32 range)
131133
{
132134
ModToGenericCallHelper.InvokeFromCategory(
133-
mod,
134-
typeof(ModCompactor).GetMethod(nameof(DoCompactingGeneric), BindingFlags.NonPublic | BindingFlags.Static)!,
135+
this,
136+
mod.GameRelease.ToCategory(),
137+
_methodInfo,
135138
new object[] { mod, range });
136139
}
137140

138-
private static void DoCompactingGeneric<TMod, TModGetter>(TMod mod, RangeUInt32 range)
141+
private void DoCompactingGeneric<TMod, TModGetter>(TMod mod, RangeUInt32 range)
139142
where TModGetter : IModGetter
140143
where TMod : IMod, TModGetter, IMajorRecordContextEnumerable<TMod, TModGetter>
141144
{
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using Mutagen.Bethesda.Plugins.Analysis.DI;
2+
using Mutagen.Bethesda.Plugins.Records;
3+
using Mutagen.Bethesda.Plugins.Utility.DI;
4+
5+
namespace Mutagen.Bethesda.Plugins.Utility;
6+
7+
public static class ModCompaction
8+
{
9+
private static readonly ModCompactor _compactor = new(
10+
new RecordCompactionCompatibilityDetector());
11+
12+
public static void CompactToSmallMaster(IMod mod)
13+
{
14+
_compactor.CompactToSmallMaster(mod);
15+
}
16+
17+
public static void CompactToMediumMaster(IMod mod)
18+
{
19+
_compactor.CompactToMediumMaster(mod);
20+
}
21+
22+
public static void CompactToFullMaster(IMod mod)
23+
{
24+
_compactor.CompactToFullMaster(mod);
25+
}
26+
27+
public static void CompactTo(IMod mod, MasterStyle style)
28+
{
29+
_compactor.CompactTo(mod, style);
30+
}
31+
32+
public static void CompactToWithFallback(IMod mod, MasterStyle style)
33+
{
34+
_compactor.CompactToWithFallback(mod, style);
35+
}
36+
}

docs/Big-Cheat-Sheet.md

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,4 +322,43 @@ foreach (var recTypes in MajorRecordTypeEnumerator.GetTopLevelMajorRecordTypesFo
322322
Console.WriteLine($"Setter: {recTypes.SetterType}");
323323
Console.WriteLine($"Class: {recTypes.ClassType}");
324324
}
325-
```
325+
```
326+
327+
## Enrich Exceptions
328+
```cs
329+
var majorRecordContext = ...;
330+
try
331+
{
332+
// Access majorRecordContext and potentially throw
333+
}
334+
catch (Exception e)
335+
{
336+
throw RecordException.Enrich(e, majorRecordContext);
337+
}
338+
```
339+
340+
[:octicons-arrow-right-24: Exception Enrichment](best-practices/Enrich-Exceptions.md)
341+
342+
## Call Generic Function by Mod Type
343+
```cs
344+
public class MyClass
345+
{
346+
public void DoSomeThings(IMod mod)
347+
{
348+
ModToGenericCallHelper.InvokeFromCategory(
349+
this,
350+
mod.GameRelease.ToCategory(),
351+
typeof(MyClass).GetMethod(nameof(DoSomeThingsGeneric), BindingFlags.NonPublic | BindingFlags.Instance)!,
352+
new object[] { mod });
353+
}
354+
355+
private void DoSomeThingsGeneric<TMod, TModGetter>(TMod mod)
356+
where TModGetter : IModGetter
357+
where TMod : IMod, TModGetter, IMajorRecordContextEnumerable<TMod, TModGetter>
358+
{
359+
// Actual logic
360+
}
361+
}
362+
```
363+
364+
[:octicons-arrow-right-24: Common to Generic Crossover](plugins/other-utility.md#common-to-generic-crossover)
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Exception Enrichment
2+
3+
Mutagen's code is written in a lightweight way that means its exceptions are not fully filled out with all the extra information that might be interesting. Often, an exception will just print the very specific field that failed, but not include other important details such as:
4+
5+
- FormKey of the record it was from
6+
- ModKey of the mod it was from
7+
8+
It is recommended that you wrap access code in a try/catch that enriches the exception with that extra information.
9+
10+
## RecordException Enrichment
11+
12+
This enriches an exception relative to a major record's information
13+
14+
```cs
15+
foreach (var npc in state.LoadOrder.PriorityOrder.Npc().WinningOverrides())
16+
{
17+
try
18+
{
19+
var overrideNpc = state.PatchMod.Npcs.GetOrAddAsOverride(npc);
20+
overrideNpc.Height *= 1.3f;
21+
}
22+
catch (Exception e)
23+
{
24+
throw RecordException.Enrich(e, npc);
25+
}
26+
}
27+
```
28+
29+
This will make the exception include:
30+
31+
- EditorID
32+
- RecordType (NPC_)
33+
- FormKey
34+
35+
### ModKey Inclusion
36+
The above code will -NOT- include `ModKey`. The ModKey that the record override originated from cannot be inferred automatically and so must be passed in. The above call has a `modKey` parameter that you can pass this information to if you have it.
37+
38+
More than likely, though, the best way to do this is to use [ModContexts](../linkcache/ModContexts.md), which contain the information about what Mod the record originated from.
39+
```cs
40+
foreach (var npcContext in state.LoadOrder.PriorityOrder.Npc().WinningContextOverrides())
41+
{
42+
try
43+
{
44+
var overrideNpc = npcContext.GetOrAddAsOverride(state.PatchMod);
45+
overrideNpc.Height *= 1.3f;
46+
}
47+
catch (Exception e)
48+
{
49+
throw RecordException.Enrich(e, npcContext);
50+
}
51+
}
52+
```
53+
54+
## Subrecord Exception
55+
56+
This is an even more specialized version of RecordException that also includes the Subrecord type. Typically this is just used by the Mutagen engine itself, and not applicable to user code.

docs/best-practices/FormLinks-Target-Getter-Interfaces.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ if (myTargetNpc.TryResolve(myLinkCache, out var npc))
3636
// Found a INpc!
3737
}
3838
```
39-
The `TryResolve` call wants to return an `INpc` type to you. But if all it can find is a [readonly `INpcGetter`](../plugins/Binary-Importing.md#read-only-mod-importing), it cannot pretend that it's settable, and so fails to match. This is the result of you asking the system to find an Npc that is settable, when the ones that exist are only getters.
39+
The `TryResolve` call wants to return an `INpc` type to you. But if all it can find is a [readonly `INpcGetter`](../plugins/Importing-and-Construction.md#read-only-mod-importing), it cannot pretend that it's settable, and so fails to match. This is the result of you asking the system to find an Npc that is settable, when the ones that exist are only getters.
4040

4141
You can solve this issue by modifying the TryResolve scope:
4242
```cs

0 commit comments

Comments
 (0)