-
Notifications
You must be signed in to change notification settings - Fork 0
Discoverable System
Shmellyorc edited this page Aug 30, 2026
·
1 revision
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.
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 |
-
Mark types with the
DiscoverableAttributeattribute - Engine scans all loaded assemblies for types with this attribute
- Types are cached and made available for queries
- 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 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| 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" } |
[Discoverable(Name = "MyEntity", Category = "Entities")]
public class MyEntity : MapEntity
{
// Implementation
}[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
}[Discoverable(Name = "MyMod", Category = "Mods", Metadata = new { Version = "1.0", Author = "Me" })]
public class MyMod : IMod
{
// Access metadata: attribute.MetadataAs<YourType>()
}// 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 a single type by name
var myModType = DiscoverableHelper.FindSingleByName<IMod>("MyMod");
// Find multiple types by name
var types = DiscoverableHelper.FindManyByName<MapEntity>("Enemy");// Find a single type by category
var entityType = DiscoverableHelper.FindSingleByCategory<MapEntity>("Entities");
// Find multiple types by category
var types = DiscoverableHelper.FindManyByCategory<IMod>("Mods");// 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");// 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
);// Find the type
var modType = DiscoverableHelper.FindSingleByName<IMod>("MyMod");
// Create an instance
var mod = InstanceHelper.CreateInstanceFromType<IMod>(modType, null);
mod.Initialize();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}");
}
}
}
}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();| 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 |
settings.SetDiscoverableScanMode(AssemblyScanMode.Whitelist)
.AddDiscoverableAssembly("MyGame")
.AddDiscoverableAssembly("MyMod1")
.AddDiscoverableAssembly("MyMod2");settings.SetDiscoverableScanMode(AssemblyScanMode.Custom)
.SetDiscoverableAssemblyFilter(assembly =>
{
var name = assembly.GetName().Name;
return name != null && name.StartsWith("My");
});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>();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>();// 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();
}// 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);// 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);
}// 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);
}// 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);
}[Discoverable(Name = "MyStudio.MyMod", Category = "Mods")]
public class MyMod : IMod { }
// Include your studio name to avoid collisions[Discoverable(Category = "Entities.Enemies")] // Use dot notation for nesting
[Discoverable(Category = "Entities.Players")]
[Discoverable(Category = "Systems.Physics")]
[Discoverable(Category = "Systems.AI")][Discoverable(Priority = 100)] // Libraries (load first)
[Discoverable(Priority = 50)] // Core mods
[Discoverable(Priority = 10)] // Content mods (load last)[Discoverable(Metadata = new { Version = "1.0", Author = "Me", Website = "https://..." })]
public class MyMod : IMod { }var type = DiscoverableHelper.FindSingleByName<IMod>("NonExistent");
if (type == null)
{
Console.WriteLine("Mod not found!");
return;
}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");
}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();
}
}
}| 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 |