Engine Abstraction
Namespace root: RisingV.Shared.Engines
Assemblies: RisingV.Shared
The Engine abstraction is a lightweight framework for building modular,
self‑contained subsystems (“engines”) that plug into a Manager–driven
lifecycle. Each engine encapsulates a single concern—AI, combat, analytics,
UI overlay, etc.—and relies on an EngineManager
(which in turn derives from
the generic manager base) to orchestrate start‑up, hot‑reload, and shut‑down.
Think of an engine as a service and
EngineManager
as the service host.
1. Building Blocks
Component | Role | Key Properties / Methods |
---|---|---|
Dependency record |
Describes a hard or soft link on another engine. | Type Type , string? Version |
IEngineConfig |
BepInEx‑backed feature config that every engine owns. | Enabled , AutoLoadDependencies |
EngineConfig |
Standard concrete config with sensible defaults. | Deconstruct for pattern matching |
IEngine |
Minimal marker + lifecycle bridge to the EngineManager . |
IEngineConfig Config , bool Enabled , GetDependencies() , GetRecommended() |
Engine<TConfig> |
Base class that implements 90 % of the boilerplate. | Virtual Load , abstract Ready , helper filters |
EngineManager |
Specialised TypeMapManager<IEngine> that solves dependency graphs and calls lifecycle hooks in order. |
AddEngine , OrderEnginesByDependencies , CheckDependencies |
The abstraction inherits all advantages of the Manager layer: deterministic lifecycle, hot‑reload hooks, and pluggable storage.
2. Dependency Model
- Declare Hard dependencies in
Dependencies
, soft links inRecommended
. - Validate
EngineManager.CheckDependencies
gathers missing items and throwsMissingDependenciesException
(or logs a warning for recommended). - Auto‑load If both
EngineManager.AllowAutoLoad
and the engine’sConfig.AutoLoadDependencies
aretrue
, missing engines are instantiated on the fly and injected into the manager.
public sealed class CombatEngine : Engine<EngineConfig>
{
public CombatEngine() : base(new EngineConfig("Combat", "Core combat loop"))
{
Dependencies.Add(new Dependency(typeof(PhysicsEngine)));
Recommended.Add(new Dependency(typeof(AnalyticsEngine)));
}
}
3. Lifecycle Walkthrough
EngineManager forwards the familiar six‑stage lifecycle to each engine:
Stage | Typical work in an engine |
---|---|
Initialize |
Minimal allocation, subscribe to global events. |
Load |
Read config, build internal data tables, resolve external handles. |
Ready ❗ |
Hook into other engines, register gameplay systems. Must be overridden. |
Reload |
Refresh runtime state when config/files change. |
Unload |
Dispose timers, unregister events. |
Terminate |
Final GC‑safe clean‑up (rarely needed in practice). |
Ready
is kept abstract in Engine<TConfig>
so you never forget to wire
the core behaviour.
4. Authoring a New Engine
public sealed class ChatEngine : Engine<EngineConfig>
{
public ChatEngine() : base(new EngineConfig(
name: "Chat",
description: "Player‑to‑player text chat",
version: "1.2.0",
author: "Ben Dol",
relativePath:"RisingV/Core"))
{
// No hard deps, optional analytics
Recommended.Add(new Dependency(typeof(AnalyticsEngine)));
}
public override void Ready(EngineManager mgr, List<IPlugin> plugins)
{
// Register chat commands, message bus, etc.
mgr.Log.Info("ChatEngine ready – commands registered.");
}
}
Register with your plugin:
_engineManager.AddEngine<ChatEngine>(this);
// This is called by the EngineManager no need to manually call, but just to demonstrate the order
_engineManager.Initialize(this);
_engineManager.Load(this);
_engineManager.Ready(this); // after all engines have been added
5. Runtime Hot‑Reload
- Edit
BepInEx/config/RisingV/Core/Chat.cfg
. EngineManager.Reload
propagates aReloadReason
to every engine.- Override
Reload
to pick up new values:
public override void Reload(EngineManager m, ReloadReason r)
{
base.Reload(m, r);
Log.Info("ChatEngine reloaded: {}", r.Reason);
_throttle = Config.GetValue("ThrottlePerMinute", 20);
}
6. Best Practices & Tips
- Single Responsibility – keep each engine focused; split large ones.
- Prefer Recommended – reserve hard dependencies for must‑have links.
- Guard With
Enabled
– short‑circuit early if the engine is disabled. - Leverage Auto‑Load – great for optional gameplay features users can toggle.
- Test Dependency Graphs – unit‑test
EngineManager.OrderEnginesByDependencies
.
7. FAQ
Q: Do I need a separate manager for every engine type?
A: No. A single EngineManager
can host any number of heterogeneous engines.
Q: Can engines depend on managers?
A: Indirectly – they retrieve managers via the plugin context, but circular
dependencies (Engine ⇄ Manager) should be avoided.
Q: How heavy is auto‑load?
A: Instantiation is a single Activator.CreateInstance
call plus registration,
so negligible for typical plugin sizes.
TL;DR
Engines are modular, reload‑friendly mini‑services.
Engine<TConfig>
gives you the boilerplate; EngineManager
keeps them
orchestrated and dependency‑aware.
Focus on domain logic—let the abstraction handle the rest. 🚀