Skip to content

Instance Helper

Shmellyorc edited this page Aug 30, 2026 · 1 revision

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.


Overview

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.


Why Instance Helper?

The Problem

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 not

The Solution

The 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);

How It Works

Assembly Scanning

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

Caching

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);

Assembly Refresh

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);

Constructor Matching

The Instance Helper automatically finds the right constructor for your arguments.

How It Works

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 parameters

Constructor Ambiguity

If 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)

No Matching Constructor

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 null

Methods

CreateInstance From Name

Creates 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);

TryCreateInstance

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.");
}

CreateInstanceFromType

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);

TryCreateInstanceFromType

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();
}

CreateInstanceFromObject

Creates an instance of the same type as an existing object.

var existingEntity = new GoblinEntity();
var newEntity = InstanceHelper.CreateInstanceFromObject<MapEntity>(existingEntity, null);

TryCreateInstanceFromObject

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
}

Constructor Arguments

No Arguments (Parameterless Constructor)

var mod = InstanceHelper.CreateInstance<IMod>("MyMod", true, null);

With Arguments

// 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 });

Dealing with Constructor Ambiguity

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" });

Creating Non-Public Types

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);

Working with Generic Types

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);

Generic Type with Arguments

// 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);

Complete Example

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();
    }
}

Error Handling

Type Not Found

var mod = InstanceHelper.CreateInstance<IMod>("NonExistent", true, null);
if (mod == null)
{
    Console.WriteLine("Mod not found!");
    return;
}

Try Pattern

if (InstanceHelper.TryCreateInstance<IMod>("MyMod", true, null, out var mod))
{
    // Success
}
else
{
    // Failure
}

Constructor Mismatch

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
}

Full Error Handling

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}");
}

Performance Considerations

Caching

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);

Cache the Type Lookup

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 time

Assembly Refresh

When 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);

Reflection Limitations

Instance Helper cannot create instances of certain types.

Abstract Classes

// Abstract classes cannot be instantiated
var abstractType = InstanceHelper.CreateInstance<AbstractClass>("MyAbstract", true, null);
// Returns null

Interfaces

// Interfaces cannot be instantiated
var interfaceType = InstanceHelper.CreateInstance<IInterface>("MyInterface", true, null);
// Returns null

Static Classes

// Static classes cannot be instantiated
var staticType = InstanceHelper.CreateInstance<StaticClass>("MyStatic", true, null);
// Returns null

Types Without Matching Constructors

// 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

Types with Private Constructors

// Private constructors are accessible via reflection
// This will work even though the constructor is private
var mod = InstanceHelper.CreateInstance<MyMod>("MyMod", true, null);

Best Practices

1. Use Try Pattern

Use the Try methods to handle failures gracefully.

if (InstanceHelper.TryCreateInstance<IMod>("MyMod", true, null, out var mod))
{
    // Success
}
else
{
    // Fallback
}

2. Cache Created Instances

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;
}

3. Use IgnoreCase Carefully

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);

4. Refresh Assemblies After Loading Mods

public void LoadModAssembly(string path)
{
    Assembly.LoadFrom(path);
    InstanceHelper.RefreshAssemblies();
}

5. Validate Constructor Arguments

// 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 });
    }
}

Summary

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

Next Steps

Clone this wiki locally