Skip to content

Commit

Permalink
Merge branch 'dev' into release
Browse files Browse the repository at this point in the history
  • Loading branch information
Noggog committed Nov 6, 2024
2 parents 0b65bda + 5deca53 commit e1fd24c
Show file tree
Hide file tree
Showing 19 changed files with 561 additions and 148 deletions.
2 changes: 2 additions & 0 deletions Mutagen.Bethesda.Core/Mutagen.Bethesda.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@
<Compile Include="Installs\SteamGameSource.cs" />
<Compile Include="Plugins\Analysis\DI\MultiModFileSplitter.cs" />
<Compile Include="Plugins\Analysis\DI\RecordCompactionCompatibilityDetector.cs" />
<Compile Include="Plugins\Analysis\RecordCompactionCompatibilityDetection.cs" />
<Compile Include="Plugins\Analysis\RecordLocationMarker.cs" />
<Compile Include="Plugins\Analysis\RecordLocator.cs">
<CodeLanguage>cs</CodeLanguage>
Expand Down Expand Up @@ -551,6 +552,7 @@
<Compile Include="Plugins\Utility\CategoryToGenericCallHelper.cs" />
<Compile Include="Plugins\Utility\DI\ModCompactor.cs" />
<Compile Include="Plugins\Utility\HeaderVersionHelper.cs" />
<Compile Include="Plugins\Utility\ModCompaction.cs" />
<Compile Include="Plugins\Warmup.cs">
<CodeLanguage>cs</CodeLanguage>
<DefaultPackFolder>content</DefaultPackFolder>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ void ThrowIfIncompatible(

public class RecordCompactionCompatibilityDetector : IRecordCompactionCompatibilityDetector
{

public bool IsSmallMasterCompatible(IModGetter mod)
{
var range = GetSmallMasterRange(mod);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using Mutagen.Bethesda.Plugins.Analysis.DI;
using Mutagen.Bethesda.Plugins.Records;
using Noggog;

namespace Mutagen.Bethesda.Plugins.Analysis;

public static class RecordCompactionCompatibilityDetection
{
private static RecordCompactionCompatibilityDetector _detector = new();

public static bool IsSmallMasterCompatible(IModGetter mod)
{
return _detector.IsSmallMasterCompatible(mod);
}

public static bool CouldBeSmallMasterCompatible(IModGetter mod)
{
return _detector.CouldBeSmallMasterCompatible(mod);
}

public static bool IsMediumMasterCompatible(IModGetter mod)
{
return _detector.IsMediumMasterCompatible(mod);
}

public static bool CouldBeMediumMasterCompatible(IModGetter mod)
{
return _detector.CouldBeMediumMasterCompatible(mod);
}

public static RangeUInt32? GetSmallMasterRange(IModGetter mod)
{
return _detector.GetSmallMasterRange(mod);
}

public static RangeUInt32? GetMediumMasterRange(IModGetter mod)
{
return _detector.GetMediumMasterRange(mod);
}

public static RangeUInt32 GetFullMasterRange(IModGetter mod, bool potential)
{
return _detector.GetFullMasterRange(mod, potential);
}

public static RangeUInt32? GetAllowedRange(IModGetter mod, bool potential)
{
return _detector.GetAllowedRange(mod, potential);
}

public static void IterateAndThrowIfIncompatible(IModGetter mod, bool potential)
{
_detector.IterateAndThrowIfIncompatible(mod, potential);
}
}
28 changes: 28 additions & 0 deletions Mutagen.Bethesda.Core/Plugins/Exceptions/RecordException.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Loqui;
using Mutagen.Bethesda.Plugins.Cache;
using Mutagen.Bethesda.Plugins.Records;

namespace Mutagen.Bethesda.Plugins.Exceptions;
Expand Down Expand Up @@ -57,6 +58,8 @@ public RecordException(FormKey? formKey, Type? recordType, ModKey? modKey, strin
/// <summary>
/// Wraps an exception to associate it with a specific major record
/// </summary>
/// <param name="ex">Exception to enrich</param>
/// <param name="majorRec">Major Record to pull information from</param>
public static RecordException Enrich(Exception ex, IMajorRecordGetter? majorRec)
{
return Enrich(ex, majorRec?.FormKey, majorRec?.Registration.ClassType, majorRec?.EditorID);
Expand All @@ -65,6 +68,9 @@ public static RecordException Enrich(Exception ex, IMajorRecordGetter? majorRec)
/// <summary>
/// Wraps an exception to associate it with a specific major record
/// </summary>
/// <param name="ex">Exception to enrich</param>
/// <param name="modKey">ModKey to mark as containing the record</param>
/// <param name="majorRec">Major Record to pull information from</param>
public static RecordException Enrich(Exception ex, ModKey? modKey, IMajorRecordGetter? majorRec)
{
return Enrich(ex, majorRec?.FormKey, majorRec?.Registration.ClassType, majorRec?.EditorID, modKey);
Expand All @@ -73,6 +79,11 @@ public static RecordException Enrich(Exception ex, ModKey? modKey, IMajorRecordG
/// <summary>
/// Wraps an exception to associate it with a specific major record
/// </summary>
/// <param name="ex">Exception to enrich</param>
/// <param name="formKey">FormKey to mark the exception to be associated with</param>
/// <param name="recordType">C# Type that the record is</param>
/// <param name="edid">EditorID to mark the exception to be associated with</param>
/// <param name="modKey">ModKey to mark as containing the record</param>
public static RecordException Enrich(Exception ex, FormKey? formKey, Type? recordType, string? edid = null, ModKey? modKey = null)
{
if (ex is RecordException rec)
Expand Down Expand Up @@ -114,6 +125,10 @@ private static Type GetRecordType(Type t)
/// <summary>
/// Wraps an exception to associate it with a specific major record
/// </summary>
/// <param name="ex">Exception to enrich</param>
/// <param name="formKey">FormKey to mark the exception to be associated with</param>
/// <param name="edid">EditorID to mark the exception to be associated with</param>
/// <param name="modKey">ModKey to mark as containing the record</param>
public static RecordException Enrich<TMajor>(Exception ex, FormKey? formKey, string? edid, ModKey? modKey = null)
where TMajor : IMajorRecordGetter
{
Expand All @@ -128,6 +143,8 @@ public static RecordException Enrich<TMajor>(Exception ex, FormKey? formKey, str
/// <summary>
/// Wraps an exception to associate it with a specific major record
/// </summary>
/// <param name="ex">Exception to enrich</param>
/// <param name="modKey">ModKey to mark as containing the record</param>
public static RecordException Enrich(Exception ex, ModKey modKey)
{
if (ex is RecordException rec)
Expand All @@ -146,6 +163,17 @@ public static RecordException Enrich(Exception ex, ModKey modKey)
innerException: ex);
}

/// <summary>
/// Wraps an exception to associate it with a specific major record
/// </summary>
/// <param name="ex">Exception to enrich</param>
/// <param name="majorRecordContext">ModContext to pull information from</param>
public static RecordException Enrich<TMajor>(Exception ex, IModContext<TMajor> majorRecordContext)
where TMajor : IMajorRecordGetter
{
return Enrich(ex, modKey: majorRecordContext.ModKey, majorRec: majorRecordContext.Record);
}

#endregion

#region Create
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
using System.Reflection;
using Loqui;
using Mutagen.Bethesda.Plugins.Records;

namespace Mutagen.Bethesda.Plugins.Utility;

public static class ModToGenericCallHelper
{
public static object? InvokeFromCategory(IModGetter mod, MethodInfo methodInfo, params object?[] parameters)
public static object? InvokeFromCategory<TSource>(TSource sourceObj, GameCategory category, MethodInfo methodInfo, params object?[] parameters)
{
var category = mod.GameRelease.ToCategory();
var typeStr = $"Mutagen.Bethesda.{category}.{category}Mod";

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

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

return genMethod.Invoke(mod, parameters);
return genMethod.Invoke(sourceObj, parameters);
}

public static async Task InvokeFromCategoryAsync(IModGetter mod, MethodInfo methodInfo, params object?[] parameters)
public static async Task InvokeFromCategoryAsync<TSource>(TSource sourceObj, GameCategory category, MethodInfo methodInfo, params object?[] parameters)
{
var obj = InvokeFromCategory(mod, methodInfo, parameters);
var obj = InvokeFromCategory(sourceObj, category, methodInfo, parameters);
if (obj is Task t)
{
await t;
}
}

public static async Task<TRet> InvokeFromCategoryAsync<TRet>(IModGetter mod, MethodInfo methodInfo, params object?[] parameters)
public static async Task<TRet> InvokeFromCategoryAsync<TSource, TRet>(TSource sourceObj, GameCategory category, MethodInfo methodInfo, params object?[] parameters)
{
var obj = InvokeFromCategory(mod, methodInfo, parameters);
var obj = InvokeFromCategory(sourceObj, category, methodInfo, parameters);
if (obj is Task<TRet> t)
{
await t;
Expand Down
11 changes: 7 additions & 4 deletions Mutagen.Bethesda.Core/Plugins/Utility/DI/ModCompactor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ public interface IModCompactor
public class ModCompactor : IModCompactor
{
private readonly IRecordCompactionCompatibilityDetector _compactionCompatibilityDetector;
private readonly MethodInfo _methodInfo;

public ModCompactor(IRecordCompactionCompatibilityDetector compactionCompatibilityDetector)
{
_compactionCompatibilityDetector = compactionCompatibilityDetector;
_methodInfo = typeof(ModCompactor).GetMethod(nameof(DoCompactingGeneric), BindingFlags.NonPublic | BindingFlags.Instance)!;
}

public void CompactToSmallMaster(IMod mod)
Expand Down Expand Up @@ -127,15 +129,16 @@ public void CompactToWithFallback(IMod mod, MasterStyle style)
CompactToFullMaster(mod);
}

private static void DoCompacting(IMod mod, RangeUInt32 range)
private void DoCompacting(IMod mod, RangeUInt32 range)
{
ModToGenericCallHelper.InvokeFromCategory(
mod,
typeof(ModCompactor).GetMethod(nameof(DoCompactingGeneric), BindingFlags.NonPublic | BindingFlags.Static)!,
this,
mod.GameRelease.ToCategory(),
_methodInfo,
new object[] { mod, range });
}

private static void DoCompactingGeneric<TMod, TModGetter>(TMod mod, RangeUInt32 range)
private void DoCompactingGeneric<TMod, TModGetter>(TMod mod, RangeUInt32 range)
where TModGetter : IModGetter
where TMod : IMod, TModGetter, IMajorRecordContextEnumerable<TMod, TModGetter>
{
Expand Down
36 changes: 36 additions & 0 deletions Mutagen.Bethesda.Core/Plugins/Utility/ModCompaction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using Mutagen.Bethesda.Plugins.Analysis.DI;
using Mutagen.Bethesda.Plugins.Records;
using Mutagen.Bethesda.Plugins.Utility.DI;

namespace Mutagen.Bethesda.Plugins.Utility;

public static class ModCompaction
{
private static readonly ModCompactor _compactor = new(
new RecordCompactionCompatibilityDetector());

public static void CompactToSmallMaster(IMod mod)
{
_compactor.CompactToSmallMaster(mod);
}

public static void CompactToMediumMaster(IMod mod)
{
_compactor.CompactToMediumMaster(mod);
}

public static void CompactToFullMaster(IMod mod)
{
_compactor.CompactToFullMaster(mod);
}

public static void CompactTo(IMod mod, MasterStyle style)
{
_compactor.CompactTo(mod, style);
}

public static void CompactToWithFallback(IMod mod, MasterStyle style)
{
_compactor.CompactToWithFallback(mod, style);
}
}
41 changes: 40 additions & 1 deletion docs/Big-Cheat-Sheet.md
Original file line number Diff line number Diff line change
Expand Up @@ -322,4 +322,43 @@ foreach (var recTypes in MajorRecordTypeEnumerator.GetTopLevelMajorRecordTypesFo
Console.WriteLine($"Setter: {recTypes.SetterType}");
Console.WriteLine($"Class: {recTypes.ClassType}");
}
```
```

## Enrich Exceptions
```cs
var majorRecordContext = ...;
try
{
// Access majorRecordContext and potentially throw
}
catch (Exception e)
{
throw RecordException.Enrich(e, majorRecordContext);
}
```

[:octicons-arrow-right-24: Exception Enrichment](best-practices/Enrich-Exceptions.md)

## Call Generic Function by Mod Type
```cs
public class MyClass
{
public void DoSomeThings(IMod mod)
{
ModToGenericCallHelper.InvokeFromCategory(
this,
mod.GameRelease.ToCategory(),
typeof(MyClass).GetMethod(nameof(DoSomeThingsGeneric), BindingFlags.NonPublic | BindingFlags.Instance)!,
new object[] { mod });
}

private void DoSomeThingsGeneric<TMod, TModGetter>(TMod mod)
where TModGetter : IModGetter
where TMod : IMod, TModGetter, IMajorRecordContextEnumerable<TMod, TModGetter>
{
// Actual logic
}
}
```

[:octicons-arrow-right-24: Common to Generic Crossover](plugins/other-utility.md#common-to-generic-crossover)
56 changes: 56 additions & 0 deletions docs/best-practices/Enrich-Exceptions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Exception Enrichment

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:

- FormKey of the record it was from
- ModKey of the mod it was from

It is recommended that you wrap access code in a try/catch that enriches the exception with that extra information.

## RecordException Enrichment

This enriches an exception relative to a major record's information

```cs
foreach (var npc in state.LoadOrder.PriorityOrder.Npc().WinningOverrides())
{
try
{
var overrideNpc = state.PatchMod.Npcs.GetOrAddAsOverride(npc);
overrideNpc.Height *= 1.3f;
}
catch (Exception e)
{
throw RecordException.Enrich(e, npc);
}
}
```

This will make the exception include:

- EditorID
- RecordType (NPC_)
- FormKey

### ModKey Inclusion
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.

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.
```cs
foreach (var npcContext in state.LoadOrder.PriorityOrder.Npc().WinningContextOverrides())
{
try
{
var overrideNpc = npcContext.GetOrAddAsOverride(state.PatchMod);
overrideNpc.Height *= 1.3f;
}
catch (Exception e)
{
throw RecordException.Enrich(e, npcContext);
}
}
```

## Subrecord Exception

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.
2 changes: 1 addition & 1 deletion docs/best-practices/FormLinks-Target-Getter-Interfaces.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ if (myTargetNpc.TryResolve(myLinkCache, out var npc))
// Found a INpc!
}
```
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.
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.

You can solve this issue by modifying the TryResolve scope:
```cs
Expand Down
2 changes: 1 addition & 1 deletion docs/best-practices/Read-Only.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ In most example code, APIs, and projects you look at the code will mostly be dea
The best practice is to convert from readonly interfaces to mutable version as late as possible. This allows the systems to avoid parsing the whole record when it's not applicable.

### Readonly Increases Speed
A lot of Mutagen's speed comes from short circuiting unnecessary work. A big way it does this is by exposing records via [Binary Overlays](../plugins/Binary-Importing.md). These are record objects that are very lightweight and fast. But one of their downsides is they are read only.
A lot of Mutagen's speed comes from short circuiting unnecessary work. A big way it does this is by exposing records via [Binary Overlays](../plugins/Importing-and-Construction.md). These are record objects that are very lightweight and fast. But one of their downsides is they are read only.

As soon as you want to modify something, you have to first convert it to a settable version of the record. This means creating a more "normal" settable `Npc` class, and reading ALL the data within that record to fill out each field one by one. This is often a waste of time.

Expand Down
Loading

0 comments on commit e1fd24c

Please sign in to comment.