diff --git a/Source/Client/MultiplayerStatic.cs b/Source/Client/MultiplayerStatic.cs
index 550864a4..6e1f8345 100644
--- a/Source/Client/MultiplayerStatic.cs
+++ b/Source/Client/MultiplayerStatic.cs
@@ -305,7 +305,7 @@ void LogError(string str)
("DesignateThing", new[]{ typeof(Thing) }),
};
- foreach (Type t in typeof(Designator).AllSubtypesAndSelf()
+ foreach (Type t in typeof(Designator).AllSubtypesAndSelf().TryMakeGenericTypes()
.Except(typeof(Designator_MechControlGroup))) // Opens float menu, sync that instead
{
foreach ((string m, Type[] args) in designatorMethods)
@@ -381,7 +381,7 @@ void LogError(string str)
("Kill", new[]{ typeof(DamageInfo?), typeof(Hediff) })
};
- foreach (Type t in typeof(Thing).AllSubtypesAndSelf())
+ foreach (Type t in typeof(Thing).AllSubtypesAndSelf().TryMakeGenericTypes())
{
// SpawnSetup is patched separately because it sets the map
var spawnSetupMethod = t.GetMethod("SpawnSetup", BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);
@@ -415,7 +415,7 @@ void LogError(string str)
("Tick", Type.EmptyTypes)
};
- foreach (Type t in typeof(WorldObject).AllSubtypesAndSelf())
+ foreach (Type t in typeof(WorldObject).AllSubtypesAndSelf().TryMakeGenericTypes())
{
foreach ((string m, Type[] args) in thingMethods)
{
@@ -454,7 +454,7 @@ void LogError(string str)
foreach (string m in windowMethods)
harmony.PatchMeasure(typeof(MainTabWindow_Inspect).GetMethod(m), setMapTimePrefix, setMapTimePostfix);
- foreach (var t in typeof(InspectTabBase).AllSubtypesAndSelf())
+ foreach (var t in typeof(InspectTabBase).AllSubtypesAndSelf().TryMakeGenericTypes())
{
var method = t.GetMethod("FillTab", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly, null, Type.EmptyTypes, null);
if (method != null && !method.IsAbstract)
diff --git a/Source/Client/Syncing/Game/SyncMethods.cs b/Source/Client/Syncing/Game/SyncMethods.cs
index c92d3224..e2936997 100644
--- a/Source/Client/Syncing/Game/SyncMethods.cs
+++ b/Source/Client/Syncing/Game/SyncMethods.cs
@@ -61,7 +61,7 @@ public static void Init()
SyncMethod.Register(typeof(Building_Bed), nameof(Building_Bed.Medical));
{
- var types = typeof(CompAssignableToPawn).AllSubtypesAndSelf().ToArray();
+ var types = typeof(CompAssignableToPawn).AllSubtypesAndSelf().TryMakeGenericTypes().ToArray();
var assignMethods = types
.Select(t => t.GetMethod(nameof(CompAssignableToPawn.TryAssignPawn), AccessTools.allDeclared, null, new[] { typeof(Pawn) }, null))
.AllNotNull();
diff --git a/Source/Client/Syncing/Handler/SyncAction.cs b/Source/Client/Syncing/Handler/SyncAction.cs
index 21ad38bf..ff4354d2 100644
--- a/Source/Client/Syncing/Handler/SyncAction.cs
+++ b/Source/Client/Syncing/Handler/SyncAction.cs
@@ -93,7 +93,7 @@ public override void Handle(ByteReader data)
public void PatchAll(string methodName)
{
- foreach (var type in typeof(A).AllSubtypesAndSelf())
+ foreach (var type in typeof(A).AllSubtypesAndSelf().TryMakeGenericTypes())
{
if (type.IsAbstract) continue;
diff --git a/Source/Client/Util/TypeUtil.cs b/Source/Client/Util/TypeUtil.cs
index 2d2f6b92..9907747d 100644
--- a/Source/Client/Util/TypeUtil.cs
+++ b/Source/Client/Util/TypeUtil.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Linq;
using Verse;
@@ -20,5 +21,124 @@ public static Type[] AllSubclassesNonAbstractOrdered(Type type) {
.OrderBy(t => t.Name)
.ToArray();
}
+
+ ///
+ /// Attempts to construct generic types (by calling ) on the
+ /// provided types (as long as they're generic and not already constructed). It'll attempt to find
+ /// a type which satisfies all generic type constraints, if a type matching them exists. All
+ /// unsuccessfully created generic types will be omitted from the result.
+ ///
+ /// The list of types which should be made generic (if needed and possible).
+ /// Determines if errors caused by no types found matching constraints should be shown.
+ ///
+ /// where each provided type is:
+ ///
+ /// - the same as it was if it's not generic type or was already constructed
+ /// - omitted if the method fails (likely due to no type matching the constraints)
+ /// - a successful result from calling (with all constraints matched)
+ ///
+ ///
+ public static IEnumerable TryMakeGenericTypes(this IEnumerable types, bool ignoreErrors = false)
+ => types.Select(t => t.TryMakeGenericType(ignoreErrors)).Where(t => t != null);
+
+ ///
+ /// Attempts to construct a generic type (by calling ) on the
+ /// provided type (as long as it's generic and not already constructed). It'll attempt to find
+ /// a type which satisfies all generic type constraints, if a type matching them all exists.
+ ///
+ /// The type which should be made generic (if needed and possible).
+ /// Determines if errors caused by no types found matching constraints should be shown.
+ ///
+ ///
+ /// - provided argument itself if it's not generic type or was already constructed
+ /// - if the method fails (likely due to no type matching the constraints)
+ /// - successful result from calling (with all constraints matched)
+ ///
+ ///
+ public static Type TryMakeGenericType(this Type type, bool ignoreErrors = false)
+ {
+ // Non-generic type or already constructed generic types
+ // don't need to be constructed, return as-is.
+ if (!type.IsGenericType || type.IsConstructedGenericType)
+ return type;
+
+ var genericArgs = type.GetGenericArguments();
+ if (genericArgs.TryGetMatchingConstraints(out var targetArgs, out var errorMessage))
+ return type.MakeGenericType(targetArgs);
+
+ // Only log errors if the type is non-abstract or has no subclasses,
+ // assuming abstract classes with no subclasses are unused.
+ if (!ignoreErrors && (!type.IsAbstract || type.AllSubclassesNonAbstract().Any()))
+ Log.Error($"Failed making generic type for type {type} with message: {errorMessage}");
+
+ return null;
+ }
+
+ ///
+ /// Attempts to find types matching restraints of generic arguments passed to this method.
+ ///
+ /// Array of generic arguments for which the constraints should be matched and returned as .
+ /// Array of types matching the provided generic argument constraints, or empty array if method failed.
+ /// Explains why the method failed, or an empty string if successful.
+ /// if types matching constraints were found, otherwise .
+ private static bool TryGetMatchingConstraints(this IReadOnlyList genericArgs, out Type[] args, out string error)
+ {
+ if (genericArgs.EnumerableNullOrEmpty())
+ {
+ args = Type.EmptyTypes;
+ error = "Trying to find constraints for generic arguments failed - the list of arguments was null or empty.";
+ return false;
+ }
+
+ args = new Type[genericArgs.Count];
+
+ for (var genericIndex = 0; genericIndex < genericArgs.Count; genericIndex++)
+ {
+ var constraints = genericArgs[genericIndex].GetGenericParameterConstraints();
+ if (constraints.NullOrEmpty())
+ {
+ // No constraints, just use object as argument and skip to next generic type
+ args[genericIndex] = typeof(object);
+ continue;
+ }
+
+ if (constraints.Length == 1)
+ {
+ // Only one constraint, just use it and skip to next generic type
+ args[genericIndex] = constraints[0];
+ continue;
+ }
+
+ // Start off with all subtypes/implementations (including self) of our first constraint
+ IEnumerable possibleMatches = constraints[0]
+ .IsInterface
+ ? constraints[0].AllImplementing().Concat(constraints[0])
+ : constraints[0].AllSubtypesAndSelf();
+
+ // Go through each constraint (besides the first)
+ // and limit the possible matches based on it.
+ for (int constraintIndex = 1; constraintIndex < constraints.Length; constraintIndex++)
+ {
+ var current = constraints[constraintIndex];
+ possibleMatches = possibleMatches.Where(t => current.IsAssignableFrom(t));
+ }
+
+ // As long as we have any result, grab and use it.
+ // We don't really care which one we use here.
+ var result = possibleMatches.FirstOrDefault();
+ if (result == null)
+ {
+ error = $"Could not find type matching specific constraint: {constraints.ToStringSafeEnumerable()}";
+ args = Type.EmptyTypes;
+ return false;
+ }
+
+ // Set the result for specific argument
+ args[genericIndex] = result;
+ }
+
+ error = string.Empty;
+ return true;
+ }
}
}