Skip to content

Discoverable System

Shmellyorc edited this page Aug 30, 2026 · 1 revision

Discoverable System

The Discoverable System is a reflection-based discovery system that allows the engine to find and load types automatically at runtime. It is the foundation for mod support, plugin systems, and dynamic type registration.


Overview

The Discoverable System solves a common problem in game development: how to find and load types without hardcoding references. Instead of manually registering every class, you mark them with an attribute and the engine finds them automatically.

Without Discoverable With Discoverable
Hardcode every class reference Mark types with an attribute
Manual registration required Automatic discovery
Mods cannot add new types Mods can add any type
Hard to extend Easy to extend

How It Works

The Discovery Process

  1. Mark types with the DiscoverableAttribute attribute
  2. Engine scans all loaded assemblies for types with this attribute
  3. Types are cached and made available for queries
  4. You can find types by name, category, or type constraint
[Discoverable(Name = "MyEntity", Category = "Entities")]
public class MyEntity : MapEntity { }

[Discoverable(Name = "MySystem", Category = "Systems")]
public class MySystem : ISystem { }

The Scanning Process

The engine scans assemblies when the game starts. It filters out system assemblies and only scans game assemblies.

// The engine automatically scans for discoverable types
// You don't need to do anything

// Filtering modes:
// All - Scan all assemblies
// ExcludeFramework - Skip system assemblies (default)
// Whitelist - Only scan specified assemblies
// Blacklist - Skip specified assemblies
// Custom - Use a custom filter

DiscoverableAttribute

Properties

Property Description Example
Name The identifier for this type "MyMod"
Category Grouping for organization "Mods"
Priority Ordering within searches (lower = higher priority) 10
Enabled Whether this type is active true
Metadata Additional custom data new { Version = "1.0" }

Basic Usage

[Discoverable(Name = "MyEntity", Category = "Entities")]
public class MyEntity : MapEntity
{
    // Implementation
}

With Priority

[Discoverable(Name = "MyMod", Category = "Mods", Priority = 10)]
public class MyMod : IMod
{
    // Higher priority (lower number) = discovered first
}

[Discoverable(Name = "MyModAddon", Category = "Mods", Priority = 20)]
public class MyModAddon : IMod
{
    // Lower priority (higher number) = discovered after
}

With Metadata

[Discoverable(Name = "MyMod", Category = "Mods", Metadata = new { Version = "1.0", Author = "Me" })]
public class MyMod : IMod
{
    // Access metadata: attribute.MetadataAs<YourType>()
}

Finding Types

Find All Types

// Find all discoverable types that implement or inherit from T
var allEntities = DiscoverableHelper.FindAll<MapEntity>();
var allSystems = DiscoverableHelper.FindAll<ISystem>();
var allMods = DiscoverableHelper.FindAll<IMod>();

Find by Name

// Find a single type by name
var myModType = DiscoverableHelper.FindSingleByName<IMod>("MyMod");

// Find multiple types by name
var types = DiscoverableHelper.FindManyByName<MapEntity>("Enemy");

Find by Category

// Find a single type by category
var entityType = DiscoverableHelper.FindSingleByCategory<MapEntity>("Entities");

// Find multiple types by category
var types = DiscoverableHelper.FindManyByCategory<IMod>("Mods");

Find by Name and Category

// Find a single type by name and category
var type = DiscoverableHelper.FindSingleByNameAndCategory<IMod>("MyMod", "Mods");

// Find multiple types by name and category
var types = DiscoverableHelper.FindManyByNameAndCategory<MapEntity>("Enemy", "Entities");

Using Enums

// You can also use enums for cleaner code
public enum ModNames { MyMod, MyModAddon }
public enum ModCategories { Mods, Libraries }

var type = DiscoverableHelper.FindSingleByNameAndCategory<IMod>(
    ModNames.MyMod, 
    ModCategories.Mods
);

Instantiating Discovered Types

Using InstanceHelper

// Find the type
var modType = DiscoverableHelper.FindSingleByName<IMod>("MyMod");

// Create an instance
var mod = InstanceHelper.CreateInstanceFromType<IMod>(modType, null);
mod.Initialize();

Complete Example

public class ModLoader
{
    public void LoadAllMods()
    {
        // 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();
                Console.WriteLine($"Loaded mod: {modType.Name}");
            }
        }
    }
}

Assembly Scanning Modes

Configuration

The scanning mode can be configured in GameSettings.

var settings = GameSettings.Instance
    .SetDiscoverableScanMode(AssemblyScanMode.All) // Default: ExcludeFramework
    .AddDiscoverableAssembly("MyModAssembly") // For Whitelist mode
    .SetDiscoverableAssemblyFilter(assembly => assembly.GetName().Name.StartsWith("My"))
    .Build();

Scanning Modes

Mode Description
All Scan all loaded assemblies
ExcludeFramework Skip system and Void assemblies (default)
Whitelist Only scan assemblies explicitly added
Blacklist Scan all except excluded assemblies
Custom Use a custom filter function

Whitelist Example

settings.SetDiscoverableScanMode(AssemblyScanMode.Whitelist)
        .AddDiscoverableAssembly("MyGame")
        .AddDiscoverableAssembly("MyMod1")
        .AddDiscoverableAssembly("MyMod2");

Custom Filter Example

settings.SetDiscoverableScanMode(AssemblyScanMode.Custom)
        .SetDiscoverableAssemblyFilter(assembly => 
        {
            var name = assembly.GetName().Name;
            return name != null && name.StartsWith("My");
        });

Caching

The Discoverable System caches results for performance.

// First call scans assemblies (slow)
var allMods = DiscoverableHelper.FindAll<IMod>();

// Subsequent calls use the cache (fast)
var allModsAgain = DiscoverableHelper.FindAll<IMod>();

Invalidate Cache

Call this when assemblies are loaded or unloaded at runtime.

// After loading a new assembly
Assembly.LoadFrom("MyNewMod.dll");
DiscoverableHelper.InvalidateCaches();

// Now the new assembly will be scanned
var newMods = DiscoverableHelper.FindAll<IMod>();

Use Cases

1. Mod Loading

// Mods register themselves
[Discoverable(Name = "MyMod", Category = "Mods")]
public class MyMod : IMod
{
    public void Initialize() { }
}

// Engine finds and loads them
var modTypes = DiscoverableHelper.FindAll<IMod>();
foreach (var type in modTypes)
{
    var mod = Activator.CreateInstance(type) as IMod;
    mod.Initialize();
}

2. Entity Registration

// Entities register themselves
[Discoverable(Name = "Goblin", Category = "Entities")]
public class GoblinEntity : MapEntity
{
    // Goblin behavior
}

[Discoverable(Name = "Orc", Category = "Entities")]
public class OrcEntity : MapEntity
{
    // Orc behavior
}

// Level loader can find entities by name
var entityType = DiscoverableHelper.FindSingleByName<MapEntity>("Goblin");
var entity = Activator.CreateInstance(entityType);

3. System Registration

// Systems register themselves
[Discoverable(Name = "WeatherSystem", Category = "Systems")]
public class WeatherSystem : ISystem
{
    public void Initialize() { }
}

// Engine finds and loads all systems
var systemTypes = DiscoverableHelper.FindAll<ISystem>();
foreach (var type in systemTypes)
{
    var system = Activator.CreateInstance(type) as ISystem;
    system.Initialize();
    _systems.Add(system);
}

4. Service Discovery

// Services register themselves
[Discoverable(Name = "SaveService", Category = "Services")]
public class SaveService : IService
{
    public void Load() { }
    public void Save() { }
}

// Service container finds all services
var serviceTypes = DiscoverableHelper.FindAll<IService>();
foreach (var type in serviceTypes)
{
    var service = Activator.CreateInstance(type) as IService;
    ServiceContainer.Register(service);
}

5. Command Discovery

// Commands register themselves
[Discoverable(Name = "Kill", Category = "Commands")]
public class KillCommand : ICommand
{
    public void Execute(string[] args) { }
}

// Console finds all commands
var commandTypes = DiscoverableHelper.FindAll<ICommand>();
foreach (var type in commandTypes)
{
    var command = Activator.CreateInstance(type) as ICommand;
    Console.RegisterCommand(command);
}

Best Practices

1. Use Unique Names

[Discoverable(Name = "MyStudio.MyMod", Category = "Mods")]
public class MyMod : IMod { }

// Include your studio name to avoid collisions

2. Use Categories for Organization

[Discoverable(Category = "Entities.Enemies")] // Use dot notation for nesting
[Discoverable(Category = "Entities.Players")]
[Discoverable(Category = "Systems.Physics")]
[Discoverable(Category = "Systems.AI")]

3. Set Priority for Load Order

[Discoverable(Priority = 100)] // Libraries (load first)
[Discoverable(Priority = 50)]  // Core mods
[Discoverable(Priority = 10)]  // Content mods (load last)

4. Use Metadata for Additional Info

[Discoverable(Metadata = new { Version = "1.0", Author = "Me", Website = "https://..." })]
public class MyMod : IMod { }

Error Handling

Type Not Found

var type = DiscoverableHelper.FindSingleByName<IMod>("NonExistent");
if (type == null)
{
    Console.WriteLine("Mod not found!");
    return;
}

Try Pattern

if (DiscoverableHelper.TryFindSingleByName<IMod>("MyMod", out var type))
{
    var mod = Activator.CreateInstance(type) as IMod;
    mod.Initialize();
}
else
{
    Console.WriteLine("MyMod not found, continuing without it");
}

Complete Example

using Void.Engine;
using Void.Engine.Helpers;

[Discoverable(Name = "MyGameMod", Category = "Mods", Priority = 10)]
public class MyGameMod : IMod
{
    public void Initialize()
    {
        Console.WriteLine("MyGameMod initialized!");
        RegisterEntities();
        RegisterSystems();
    }

    private void RegisterEntities()
    {
        // Register custom entities
        var entityTypes = DiscoverableHelper.FindAll<MapEntity>();
        foreach (var type in entityTypes)
        {
            EntityRegistry.Register(type);
        }
    }

    private void RegisterSystems()
    {
        // Register custom systems
        var systemTypes = DiscoverableHelper.FindAll<ISystem>();
        foreach (var type in systemTypes)
        {
            var system = InstanceHelper.CreateInstanceFromType<ISystem>(type, null);
            SystemManager.Register(system);
        }
    }

    public void Load() { }
    public void Unload() { }
}

// Usage
public class Game
{
    public void LoadMods()
    {
        var modTypes = DiscoverableHelper.FindAll<IMod>();
        foreach (var type in modTypes)
        {
            var mod = InstanceHelper.CreateInstanceFromType<IMod>(type, null);
            mod?.Initialize();
        }
    }
}

Summary

Concept Description
DiscoverableAttribute Marks types for automatic discovery
DiscoverableHelper Provides methods for finding types
InstanceHelper Creates instances of discovered types
AssemblyScanMode Controls which assemblies are scanned
Caching Results are cached for performance
Mod Support Foundation for mod loading

Next Steps

Clone this wiki locally