Skip to content

Commit 6dcbb7e

Browse files
authored
Improve PatchCategory/UnpatchCategory (#704)
* Add more tests for Patch/UnpatchCategory * Implement Cached Patch/Unpatch Category * Remove nullable annotation to prevent warning
1 parent 3c753b9 commit 6dcbb7e

2 files changed

Lines changed: 123 additions & 17 deletions

File tree

Harmony/Public/Harmony.cs

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Diagnostics;
44
using System.Linq;
55
using System.Reflection;
6+
using System.Runtime.CompilerServices;
67

78
namespace HarmonyLib
89
{
@@ -132,21 +133,41 @@ public void PatchCategory(string category)
132133
PatchCategory(assembly, category);
133134
}
134135

136+
private static readonly ConditionalWeakTable<Assembly, Dictionary<string, List<Type>>> AssemblyCachedCategories = new();
137+
135138
/// <summary>Searches an assembly for HarmonyPatch-annotated classes/structs with a specific category and uses them to create patches</summary>
136139
/// <param name="assembly">The assembly</param>
137140
/// <param name="category">Name of patch category</param>
138141
///
139142
public void PatchCategory(Assembly assembly, string category)
140143
{
141-
AccessTools.GetTypesFromAssembly(assembly)
142-
.Where(type =>
144+
var categoryCache = AssemblyCachedCategories.GetValue(assembly, BuildCategoryCache);
145+
if (categoryCache.TryGetValue(category, out var toPatch))
146+
{
147+
toPatch.Do(type => CreateClassProcessor(type).Patch());
148+
}
149+
}
150+
151+
private static Dictionary<string, List<Type>> BuildCategoryCache(Assembly assembly)
152+
{
153+
Dictionary<string, List<Type>> toBuild = [];
154+
foreach (var type in AccessTools.GetTypesFromAssembly(assembly))
155+
{
156+
var harmonyAttributes = HarmonyMethodExtensions.GetFromType(type);
157+
if (harmonyAttributes.Count == 0) continue;
158+
var containerAttributes = HarmonyMethod.Merge(harmonyAttributes);
159+
var category = containerAttributes.category;
160+
if (!string.IsNullOrEmpty(category))
143161
{
144-
var harmonyAttributes = HarmonyMethodExtensions.GetFromType(type);
145-
if (harmonyAttributes.Count == 0) return false;
146-
var containerAttributes = HarmonyMethod.Merge(harmonyAttributes);
147-
return containerAttributes.category == category;
148-
})
149-
.Do(type => CreateClassProcessor(type).Patch());
162+
if (!toBuild.TryGetValue(category, out var typeList))
163+
{
164+
typeList ??= [];
165+
}
166+
typeList.Add(type);
167+
toBuild[category] = typeList;
168+
}
169+
}
170+
return toBuild;
150171
}
151172

152173
/// <summary>Creates patches by manually specifying the methods</summary>
@@ -239,15 +260,11 @@ public void UnpatchCategory(string category)
239260
///
240261
public void UnpatchCategory(Assembly assembly, string category)
241262
{
242-
AccessTools.GetTypesFromAssembly(assembly)
243-
.Where(type =>
244-
{
245-
var harmonyAttributes = HarmonyMethodExtensions.GetFromType(type);
246-
if (harmonyAttributes.Count == 0) return false;
247-
var containerAttributes = HarmonyMethod.Merge(harmonyAttributes);
248-
return containerAttributes.category == category;
249-
})
250-
.Do(type => CreateClassProcessor(type).Unpatch());
263+
var categoryCache = AssemblyCachedCategories.GetValue(assembly, BuildCategoryCache);
264+
if (categoryCache.TryGetValue(category, out var toPatch))
265+
{
266+
toPatch.Do(type => CreateClassProcessor(type).Unpatch());
267+
}
251268
}
252269

253270
/// <summary>Test for patches from a specific Harmony ID</summary>
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
using HarmonyLib;
2+
using NUnit.Framework;
3+
using System.Runtime.CompilerServices;
4+
5+
namespace HarmonyLibTests.Patching
6+
{
7+
[TestFixture, NonParallelizable]
8+
public class CategoryPatches : TestLogger
9+
{
10+
[Test]
11+
public void Test_HarmonyPatchAll()
12+
{
13+
var harmony = new Harmony("test");
14+
harmony.PatchCategory("CategoryA");
15+
16+
Assert.AreEqual(2, Get1());
17+
Assert.AreEqual(false, GetTrue());
18+
Assert.AreEqual("Hello World", GetHelloWorld());
19+
Assert.AreEqual(18, Multiply(3, 6));
20+
21+
22+
harmony.PatchCategory("CategoryB");
23+
24+
Assert.AreEqual(2, Get1());
25+
Assert.AreEqual(false, GetTrue());
26+
Assert.AreEqual("Hello World!", GetHelloWorld());
27+
Assert.AreEqual(36, Multiply(3, 6));
28+
29+
harmony.UnpatchCategory("CategoryA");
30+
31+
Assert.AreEqual(1, Get1());
32+
Assert.AreEqual(true, GetTrue());
33+
Assert.AreEqual("Hello World!", GetHelloWorld());
34+
Assert.AreEqual(36, Multiply(3, 6));
35+
36+
harmony.UnpatchCategory("CategoryB");
37+
38+
Assert.AreEqual(1, Get1());
39+
Assert.AreEqual(true, GetTrue());
40+
Assert.AreEqual("Hello World", GetHelloWorld());
41+
Assert.AreEqual(18, Multiply(3, 6));
42+
}
43+
[MethodImpl(MethodImplOptions.NoInlining)]
44+
public static int Get1() => 1;
45+
46+
[MethodImpl(MethodImplOptions.NoInlining)]
47+
public static bool GetTrue() => true;
48+
49+
[MethodImpl(MethodImplOptions.NoInlining)]
50+
public static string GetHelloWorld() => "Hello World";
51+
52+
[MethodImpl(MethodImplOptions.NoInlining)]
53+
public static int Multiply(int a, int b) => a * b;
54+
55+
[HarmonyPatch]
56+
[HarmonyPatch(typeof(CategoryPatches))]
57+
[HarmonyPatchCategory("CategoryA")]
58+
static class CategoryAPatches
59+
{
60+
[HarmonyPatch(nameof(Get1)), HarmonyPrefix]
61+
public static bool Get1Patch(ref int __result)
62+
{
63+
__result = 2;
64+
return false;
65+
}
66+
[HarmonyPatch(nameof(GetTrue)), HarmonyPostfix]
67+
public static void GetTruePatch(ref bool __result)
68+
{
69+
__result = false;
70+
}
71+
}
72+
73+
[HarmonyPatch]
74+
[HarmonyPatchCategory("CategoryB")]
75+
static class CategoryBPatches
76+
{
77+
[HarmonyPatch(typeof(CategoryPatches), nameof(GetHelloWorld)), HarmonyPostfix]
78+
public static void GetHelloWorldPatch(ref string __result)
79+
{
80+
__result = __result + "!";
81+
}
82+
[HarmonyPatch(typeof(CategoryPatches), nameof(Multiply)), HarmonyPrefix]
83+
public static void Multiply(ref int a)
84+
{
85+
a *= 2;
86+
}
87+
}
88+
}
89+
}

0 commit comments

Comments
 (0)