-
Notifications
You must be signed in to change notification settings - Fork 0
Instance Helper
The Instance Helper provides reflection-based object creation and type discovery across all game assemblies. It works alongside the Discoverable System to create instances of discovered types at runtime.
The Discoverable System finds types. The Instance Helper creates instances of those types.
| System | What It Does |
|---|---|
| Discoverable System | Finds types marked with [Discoverable]
|
| Instance Helper | Creates instances of those types |
Together, they enable dynamic loading of mods, plugins, and custom systems.
Creating instances of types at runtime is not always straightforward.
// This works if you know the type at compile time
var mod = new MyMod();
// But what if you only know the type name at runtime?
var type = Type.GetType("MyMod");
var mod = Activator.CreateInstance(type); // Might work, might notThe Instance Helper handles the complexity of runtime instance creation.
// Find the type
var type = DiscoverableHelper.FindSingleByName<IMod>("MyMod");
// Create an instance
var mod = InstanceHelper.CreateInstanceFromType<IMod>(type, null);The Instance Helper scans all game assemblies for types. It filters out system assemblies to improve performance.
| Assembly Type | Scanned? |
|---|---|
| Game assemblies | Yes |
| Mod assemblies | Yes |
| System assemblies | No |
| Framework assemblies | No |
Successful type lookups are cached for performance. Failed lookups are also cached to avoid repeated searches.
// First call: Scans all assemblies (slow)
var mod = InstanceHelper.CreateInstance<IMod>("MyMod", true, null);
// Second call: Uses cache (fast)
var mod2 = InstanceHelper.CreateInstance<IMod>("MyMod", true, null);Call RefreshAssemblies() when assemblies are loaded or unloaded at runtime.
// After loading a new mod assembly
Assembly.LoadFrom("MyNewMod.dll");
InstanceHelper.RefreshAssemblies();
// Now the new assembly will be scanned
var mod = InstanceHelper.CreateInstance<IMod>("MyNewMod", true, null);The Instance Helper automatically finds the right constructor for your arguments.
Instance Helper looks for a constructor that matches the argument types in order.
// This will find and call: public MyMod(string name, int value)
var mod = InstanceHelper.CreateInstance<MyMod>("MyMod", true, new object[] { "test", 42 });
// Constructor must match the argument types exactly
// string, int matches the constructor parametersIf multiple constructors exist, Instance Helper picks the best match.
// Constructor: MyMod(string name)
// Constructor: MyMod(int id)
// Both are valid. Which one gets called depends on argument type.
var mod1 = InstanceHelper.CreateInstance<MyMod>("MyMod", true, new object[] { "test" });
// Calls MyMod(string name)
var mod2 = InstanceHelper.CreateInstance<MyMod>("MyMod", true, new object[] { 42 });
// Calls MyMod(int id)If no matching constructor is found, the instance creation fails.
// MyMod only has a constructor that takes (string, int)
// This will fail because the arguments don't match
var mod = InstanceHelper.CreateInstance<MyMod>("MyMod", true, new object[] { "test" });
// Returns nullCreates an instance of a type by name.
// Basic usage
var mod = InstanceHelper.CreateInstance<IMod>("MyMod", ignoreCase: true, args: null);
// With constructor arguments
var entity = InstanceHelper.CreateInstance<MapEntity>("Goblin", true, new object[] { position, health });
// Case sensitive
var mod2 = InstanceHelper.CreateInstance<IMod>("MyMod", ignoreCase: false, args: null);Attempts to create an instance and returns a boolean indicating success.
if (InstanceHelper.TryCreateInstance<IMod>("MyMod", true, null, out var mod))
{
mod.Initialize();
Console.WriteLine("Mod loaded successfully!");
}
else
{
Console.WriteLine("Failed to load mod.");
}Creates an instance from a type reference.
// Find the type first
var type = DiscoverableHelper.FindSingleByName<IMod>("MyMod");
// Create an instance from the type
var mod = InstanceHelper.CreateInstanceFromType<IMod>(type, null);Attempts to create an instance from a type reference.
var type = DiscoverableHelper.FindSingleByName<IMod>("MyMod");
if (InstanceHelper.TryCreateInstanceFromType<IMod>(type, null, out var mod))
{
mod.Initialize();
}Creates an instance of the same type as an existing object.
var existingEntity = new GoblinEntity();
var newEntity = InstanceHelper.CreateInstanceFromObject<MapEntity>(existingEntity, null);Attempts to create an instance of the same type as an existing object.
var existingEntity = new GoblinEntity();
if (InstanceHelper.TryCreateInstanceFromObject<MapEntity>(existingEntity, null, out var newEntity))
{
// newEntity is a new GoblinEntity instance
}var mod = InstanceHelper.CreateInstance<IMod>("MyMod", true, null);// Single argument
var entity = InstanceHelper.CreateInstance<MapEntity>("Goblin", true, new object[] { position });
// Multiple arguments
var entity = InstanceHelper.CreateInstance<MapEntity>("Goblin", true, new object[] { position, health, name });If multiple constructors match, Instance Helper picks the best match based on argument types.
// Constructor: MyMod(string name)
// Constructor: MyMod(int id)
var mod1 = InstanceHelper.CreateInstance<MyMod>("MyMod", true, new object[] { "test" });
// Calls MyMod(string name)
var mod2 = InstanceHelper.CreateInstance<MyMod>("MyMod", true, new object[] { 42 });
// Calls MyMod(int id)If you need to force a specific constructor, use CreateInstanceFromType with the exact type.
var type = typeof(MyMod);
var constructor = type.GetConstructor(new[] { typeof(string) });
var mod = constructor.Invoke(new object[] { "test" });Instance Helper can create instances of internal or private types as long as they are in a scanned assembly.
// Internal class
internal class InternalMod : IMod { }
// This still works because the type is in a scanned assembly
var mod = InstanceHelper.CreateInstance<IMod>("InternalMod", true, null);Instance Helper works with generic types as long as the type is fully specified.
// This works:
var handler = InstanceHelper.CreateInstance<IHandler<MyEvent>>("MyEventHandler", true, null);
// This does NOT work:
// var handler = InstanceHelper.CreateInstance<IHandler<>>("MyEventHandler", true, null);// Define a generic type
public class GenericMod<T> : IMod where T : new()
{
public T Data { get; set; }
public void Initialize() { }
}
// Create an instance with a specific generic argument
var type = typeof(GenericMod<>).MakeGenericType(typeof(MyData));
var mod = InstanceHelper.CreateInstanceFromType<IMod>(type, null);using Void.Engine;
using Void.Engine.Helpers;
public class ModLoader
{
private List<IMod> _loadedMods = new List<IMod>();
public void LoadMods()
{
// Find all mod types
var modTypes = DiscoverableHelper.FindAll<IMod>();
foreach (var modType in modTypes)
{
// Create an instance of each mod
var mod = InstanceHelper.CreateInstanceFromType<IMod>(modType, null);
if (mod != null)
{
mod.Initialize();
_loadedMods.Add(mod);
Console.WriteLine($"Loaded mod: {modType.Name}");
}
}
}
public void LoadModByName(string modName)
{
// Find a specific mod by name
var modType = DiscoverableHelper.FindSingleByName<IMod>(modName);
if (modType == null)
{
Console.WriteLine($"Mod '{modName}' not found.");
return;
}
// Create an instance
var mod = InstanceHelper.CreateInstanceFromType<IMod>(modType, null);
if (mod != null)
{
mod.Initialize();
_loadedMods.Add(mod);
Console.WriteLine($"Loaded mod: {modName}");
}
}
public T LoadModWithArgs<T>(string modName, object[] args) where T : class
{
var mod = InstanceHelper.CreateInstance<T>(modName, true, args);
if (mod != null)
{
Console.WriteLine($"Loaded mod: {modName}");
return mod;
}
return null;
}
public void UnloadMods()
{
foreach (var mod in _loadedMods)
{
mod.Unload();
}
_loadedMods.Clear();
}
}var mod = InstanceHelper.CreateInstance<IMod>("NonExistent", true, null);
if (mod == null)
{
Console.WriteLine("Mod not found!");
return;
}if (InstanceHelper.TryCreateInstance<IMod>("MyMod", true, null, out var mod))
{
// Success
}
else
{
// Failure
}If the constructor arguments do not match, the instance creation will fail.
// This will fail if GoblinEntity does not have a constructor that takes (Vect2, int, string)
var entity = InstanceHelper.CreateInstance<MapEntity>("Goblin", true, new object[] { position, health, name });
// Use Try pattern to handle failure gracefully
if (InstanceHelper.TryCreateInstance<MapEntity>("Goblin", true, new object[] { position, health, name }, out var entity2))
{
// Success
}
else
{
// Constructor mismatch or other error
}try
{
var mod = InstanceHelper.CreateInstance<IMod>("MyMod", true, new object[] { "test" });
if (mod == null)
{
Console.WriteLine("Failed to create instance. Check constructor arguments.");
}
}
catch (Exception ex)
{
Console.WriteLine($"Exception during creation: {ex.Message}");
}Instance Helper caches type lookups for performance. The first lookup is slower, subsequent lookups are fast.
// Slow (first lookup)
var mod1 = InstanceHelper.CreateInstance<IMod>("MyMod", true, null);
// Fast (cached)
var mod2 = InstanceHelper.CreateInstance<IMod>("MyMod", true, null);If you need multiple instances, cache the type lookup.
// Cache the type lookup
var type = DiscoverableHelper.FindSingleByName<IMod>("MyMod");
// Create multiple instances from the cached type
var mod1 = InstanceHelper.CreateInstanceFromType<IMod>(type, null);
var mod2 = InstanceHelper.CreateInstanceFromType<IMod>(type, null);
var mod3 = InstanceHelper.CreateInstanceFromType<IMod>(type, null);
// This is faster than doing the name lookup each timeWhen loading new assemblies at runtime, call RefreshAssemblies() to clear the cache.
// Load a new assembly
Assembly.LoadFrom("NewMod.dll");
// Refresh the cache
InstanceHelper.RefreshAssemblies();
// Now the new assembly is available
var mod = InstanceHelper.CreateInstance<IMod>("NewMod", true, null);Instance Helper cannot create instances of certain types.
// Abstract classes cannot be instantiated
var abstractType = InstanceHelper.CreateInstance<AbstractClass>("MyAbstract", true, null);
// Returns null// Interfaces cannot be instantiated
var interfaceType = InstanceHelper.CreateInstance<IInterface>("MyInterface", true, null);
// Returns null// Static classes cannot be instantiated
var staticType = InstanceHelper.CreateInstance<StaticClass>("MyStatic", true, null);
// Returns null// If no matching constructor is found, returns null
var mod = InstanceHelper.CreateInstance<MyMod>("MyMod", true, new object[] { 42 });
// Returns null if MyMod doesn't have a constructor that takes int// Private constructors are accessible via reflection
// This will work even though the constructor is private
var mod = InstanceHelper.CreateInstance<MyMod>("MyMod", true, null);Use the Try methods to handle failures gracefully.
if (InstanceHelper.TryCreateInstance<IMod>("MyMod", true, null, out var mod))
{
// Success
}
else
{
// Fallback
}private Dictionary<string, IMod> _modCache = new();
public IMod GetMod(string name)
{
if (_modCache.TryGetValue(name, out var mod))
return mod;
mod = InstanceHelper.CreateInstance<IMod>(name, true, null);
if (mod != null)
_modCache[name] = mod;
return mod;
}Case-insensitive lookups are convenient but can cause ambiguity.
// This might find "MyMod" or "mymod" or "MYMOD"
var mod = InstanceHelper.CreateInstance<IMod>("mymod", true, null);
// Use case-sensitive when you need exact matches
var mod2 = InstanceHelper.CreateInstance<IMod>("MyMod", false, null);public void LoadModAssembly(string path)
{
Assembly.LoadFrom(path);
InstanceHelper.RefreshAssemblies();
}// Check if the type has the expected constructor
var type = DiscoverableHelper.FindSingleByName<IMod>("MyMod");
if (type != null)
{
var constructor = type.GetConstructor(new[] { typeof(string), typeof(int) });
if (constructor != null)
{
var mod = InstanceHelper.CreateInstance<IMod>("MyMod", true, new object[] { "test", 42 });
}
}| Feature | Description |
|---|---|
| CreateInstance | Creates an instance by type name |
| TryCreateInstance | Attempts to create an instance |
| CreateInstanceFromType | Creates an instance from a type reference |
| CreateInstanceFromObject | Creates an instance of the same type as an object |
| Constructor Matching | Automatically finds the right constructor |
| Caching | Type lookups are cached for performance |
| Assembly Refresh | Refresh when assemblies are loaded or unloaded |
| Non-Public Types | Can create instances of internal/private types |
| Generic Types | Works with fully specified generic types |